mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 19:08:58 +03:00
Lint qml with qml-format
This commit is contained in:
parent
517a126a44
commit
1a029112d9
31 changed files with 2863 additions and 2179 deletions
|
@ -1,17 +1,18 @@
|
|||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
|
||||
import im.nheko 1.0
|
||||
|
||||
Rectangle {
|
||||
id: activeCallBar
|
||||
|
||||
visible: TimelineManager.callState != WebRTCState.DISCONNECTED
|
||||
color: "#2ECC71"
|
||||
implicitHeight: rowLayout.height + 8
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
@ -20,7 +21,6 @@ Rectangle {
|
|||
Avatar {
|
||||
width: avatarSize
|
||||
height: avatarSize
|
||||
|
||||
url: TimelineManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
displayName: TimelineManager.callPartyName
|
||||
}
|
||||
|
@ -38,50 +38,54 @@ Rectangle {
|
|||
|
||||
Label {
|
||||
id: callStateLabel
|
||||
|
||||
font.pointSize: fontMetrics.font.pointSize * 1.1
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: TimelineManager
|
||||
function onCallStateChanged(state) {
|
||||
switch (state) {
|
||||
case WebRTCState.INITIATING:
|
||||
callStateLabel.text = qsTr("Initiating...")
|
||||
callStateLabel.text = qsTr("Initiating...");
|
||||
break;
|
||||
case WebRTCState.OFFERSENT:
|
||||
callStateLabel.text = qsTr("Calling...")
|
||||
callStateLabel.text = qsTr("Calling...");
|
||||
break;
|
||||
case WebRTCState.CONNECTING:
|
||||
callStateLabel.text = qsTr("Connecting...")
|
||||
callStateLabel.text = qsTr("Connecting...");
|
||||
break;
|
||||
case WebRTCState.CONNECTED:
|
||||
callStateLabel.text = "00:00"
|
||||
var d = new Date()
|
||||
callTimer.startTime = Math.floor(d.getTime() / 1000)
|
||||
callStateLabel.text = "00:00";
|
||||
var d = new Date();
|
||||
callTimer.startTime = Math.floor(d.getTime() / 1000);
|
||||
break;
|
||||
case WebRTCState.DISCONNECTED:
|
||||
callStateLabel.text = ""
|
||||
callStateLabel.text = "";
|
||||
}
|
||||
}
|
||||
|
||||
target: TimelineManager
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: callTimer
|
||||
|
||||
property int startTime
|
||||
|
||||
function pad(n) {
|
||||
return (n < 10) ? ("0" + n) : n;
|
||||
}
|
||||
|
||||
interval: 1000
|
||||
running: TimelineManager.callState == WebRTCState.CONNECTED
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
var d = new Date()
|
||||
let seconds = Math.floor(d.getTime() / 1000 - startTime)
|
||||
let s = Math.floor(seconds % 60)
|
||||
let m = Math.floor(seconds / 60) % 60
|
||||
let h = Math.floor(seconds / 3600)
|
||||
callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s)
|
||||
}
|
||||
|
||||
function pad(n) {
|
||||
return (n < 10) ? ("0" + n) : n
|
||||
var d = new Date();
|
||||
let seconds = Math.floor(d.getTime() / 1000 - startTime);
|
||||
let s = Math.floor(seconds % 60);
|
||||
let m = Math.floor(seconds / 60) % 60;
|
||||
let h = Math.floor(seconds / 3600);
|
||||
callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,19 +97,17 @@ Rectangle {
|
|||
width: 24
|
||||
height: 24
|
||||
buttonTextColor: "#000000"
|
||||
image: TimelineManager.isMicMuted ?
|
||||
":/icons/icons/ui/microphone-unmute.png" :
|
||||
":/icons/icons/ui/microphone-mute.png"
|
||||
|
||||
image: TimelineManager.isMicMuted ? ":/icons/icons/ui/microphone-unmute.png" : ":/icons/icons/ui/microphone-mute.png"
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: TimelineManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic")
|
||||
|
||||
onClicked: TimelineManager.toggleMicMute()
|
||||
}
|
||||
|
||||
Item {
|
||||
implicitWidth: 16
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
import QtGraphicalEffects 1.0
|
||||
import QtQuick 2.6
|
||||
import QtQuick.Controls 2.3
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import im.nheko 1.0
|
||||
|
||||
Rectangle {
|
||||
id: avatar
|
||||
width: 48
|
||||
height: 48
|
||||
radius: Settings.avatarCircles ? height/2 : 3
|
||||
|
||||
property alias url: img.source
|
||||
property string userid
|
||||
property string displayName
|
||||
|
||||
width: 48
|
||||
height: 48
|
||||
radius: Settings.avatarCircles ? height / 2 : 3
|
||||
color: colors.base
|
||||
|
||||
Label {
|
||||
anchors.fill: parent
|
||||
text: TimelineManager.escapeEmoji(displayName ? String.fromCodePoint(displayName.codePointAt(0)) : "")
|
||||
|
@ -27,23 +28,25 @@ Rectangle {
|
|||
|
||||
Image {
|
||||
id: img
|
||||
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
mipmap: true
|
||||
smooth: false
|
||||
|
||||
sourceSize.width: avatar.width
|
||||
sourceSize.height: avatar.height
|
||||
|
||||
layer.enabled: true
|
||||
|
||||
layer.effect: OpacityMask {
|
||||
|
||||
maskSource: Rectangle {
|
||||
anchors.fill: parent
|
||||
width: avatar.width
|
||||
height: avatar.height
|
||||
radius: Settings.avatarCircles ? height / 2 : 3
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -51,19 +54,22 @@ Rectangle {
|
|||
Rectangle {
|
||||
anchors.bottom: avatar.bottom
|
||||
anchors.right: avatar.right
|
||||
|
||||
visible: !!userid
|
||||
|
||||
height: avatar.height / 6
|
||||
width: height
|
||||
radius: Settings.avatarCircles ? height / 2 : height / 4
|
||||
color: switch (TimelineManager.userPresence(userid)) {
|
||||
case "online": return "#00cc66"
|
||||
case "unavailable": return "#ff9933"
|
||||
case "offline": // return "#a82353" don't show anything if offline, since it is confusing, if presence is disabled
|
||||
default: "transparent"
|
||||
color: {
|
||||
switch (TimelineManager.userPresence(userid)) {
|
||||
case "online":
|
||||
return "#00cc66";
|
||||
case "unavailable":
|
||||
return "#ff9933";
|
||||
case "offline":
|
||||
default:
|
||||
// return "#a82353" don't show anything if offline, since it is confusing, if presence is disabled
|
||||
"transparent";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
color: colors.base
|
||||
}
|
||||
|
|
|
@ -3,39 +3,42 @@ import QtQuick.Controls 2.1
|
|||
import im.nheko 1.0
|
||||
|
||||
Rectangle {
|
||||
property bool encrypted: false
|
||||
id: indicator
|
||||
|
||||
property bool encrypted: false
|
||||
|
||||
function getEncryptionImage() {
|
||||
if (encrypted)
|
||||
return "image://colorimage/:/icons/icons/ui/lock.png?" + colors.buttonText;
|
||||
else
|
||||
return "image://colorimage/:/icons/icons/ui/unlock.png?#dd3d3d";
|
||||
}
|
||||
|
||||
function getEncryptionTooltip() {
|
||||
if (encrypted)
|
||||
return qsTr("Encrypted");
|
||||
else
|
||||
return qsTr("This message is not encrypted!");
|
||||
}
|
||||
|
||||
color: "transparent"
|
||||
width: 16
|
||||
height: 16
|
||||
|
||||
ToolTip.visible: ma.containsMouse && indicator.visible
|
||||
ToolTip.text: getEncryptionTooltip()
|
||||
|
||||
MouseArea {
|
||||
id: ma
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
}
|
||||
|
||||
Image {
|
||||
id: stateImg
|
||||
|
||||
anchors.fill: parent
|
||||
source: getEncryptionImage()
|
||||
}
|
||||
|
||||
function getEncryptionImage() {
|
||||
if (encrypted)
|
||||
return "image://colorimage/:/icons/icons/ui/lock.png?"+colors.buttonText
|
||||
else
|
||||
return "image://colorimage/:/icons/icons/ui/unlock.png?#dd3d3d"
|
||||
}
|
||||
|
||||
function getEncryptionTooltip() {
|
||||
if (encrypted)
|
||||
return qsTr("Encrypted")
|
||||
else
|
||||
return qsTr("This message is not encrypted!")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,25 +2,29 @@ import QtQuick 2.3
|
|||
import QtQuick.Controls 2.3
|
||||
|
||||
AbstractButton {
|
||||
id: button
|
||||
|
||||
property string image: undefined
|
||||
property color highlightColor: colors.highlight
|
||||
property color buttonTextColor: colors.buttonText
|
||||
|
||||
width: 16
|
||||
height: 16
|
||||
id: button
|
||||
|
||||
Image {
|
||||
id: buttonImg
|
||||
|
||||
// Workaround, can't get icon.source working for now...
|
||||
anchors.fill: parent
|
||||
source: "image://colorimage/" + image + "?" + (button.hovered ? highlightColor : buttonTextColor)
|
||||
}
|
||||
|
||||
MouseArea
|
||||
{
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
onPressed: mouse.accepted = false
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
import im.nheko 1.0
|
||||
|
||||
TextEdit {
|
||||
|
@ -10,26 +9,29 @@ TextEdit {
|
|||
selectByMouse: true
|
||||
activeFocusOnPress: false
|
||||
color: colors.text
|
||||
|
||||
onLinkActivated: {
|
||||
if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) chat.model.openUserProfile(/^https:\/\/matrix.to\/#\/(@.*)$/.exec(link)[1])
|
||||
else if (/^https:\/\/matrix.to\/#\/(![^\/]*)$/.test(link)) TimelineManager.setHistoryView(/^https:\/\/matrix.to\/#\/(!.*)$/.exec(link)[1])
|
||||
else if (/^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.test(link)) {
|
||||
var match = /^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.exec(link)
|
||||
TimelineManager.setHistoryView(match[1])
|
||||
chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain)
|
||||
if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) {
|
||||
chat.model.openUserProfile(/^https:\/\/matrix.to\/#\/(@.*)$/.exec(link)[1]);
|
||||
} else if (/^https:\/\/matrix.to\/#\/(![^\/]*)$/.test(link)) {
|
||||
TimelineManager.setHistoryView(/^https:\/\/matrix.to\/#\/(!.*)$/.exec(link)[1]);
|
||||
} else if (/^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.test(link)) {
|
||||
var match = /^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.exec(link);
|
||||
TimelineManager.setHistoryView(match[1]);
|
||||
chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain);
|
||||
} else {
|
||||
TimelineManager.openLink(link);
|
||||
}
|
||||
else TimelineManager.openLink(link)
|
||||
}
|
||||
MouseArea
|
||||
{
|
||||
ToolTip.visible: hoveredLink
|
||||
ToolTip.text: hoveredLink
|
||||
|
||||
MouseArea {
|
||||
id: ma
|
||||
|
||||
anchors.fill: parent
|
||||
propagateComposedEvents: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
|
||||
ToolTip.visible: hoveredLink
|
||||
ToolTip.text: hoveredLink
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import QtQuick 2.6
|
||||
import QtQuick.Controls 2.2
|
||||
|
||||
import im.nheko 1.0
|
||||
|
||||
// This class is for showing Reactions in the timeline row, not for
|
||||
|
@ -12,33 +11,29 @@ Flow {
|
|||
property real highlightHue: colors.highlight.hslHue
|
||||
property real highlightSat: colors.highlight.hslSaturation
|
||||
property real highlightLight: colors.highlight.hslLightness
|
||||
|
||||
property string eventId
|
||||
property alias reactions: repeater.model
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: 4
|
||||
|
||||
property alias reactions: repeater.model
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
|
||||
delegate: AbstractButton {
|
||||
id: reaction
|
||||
|
||||
hoverEnabled: true
|
||||
implicitWidth: contentItem.childrenRect.width + contentItem.leftPadding * 2
|
||||
implicitHeight: contentItem.childrenRect.height
|
||||
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: modelData.users
|
||||
|
||||
onClicked: {
|
||||
console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + ". selfReactedEvent: " + modelData.selfReactedEvent)
|
||||
TimelineManager.queueReactionMessage(reactionFlow.eventId, modelData.key)
|
||||
console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + ". selfReactedEvent: " + modelData.selfReactedEvent);
|
||||
TimelineManager.queueReactionMessage(reactionFlow.eventId, modelData.key);
|
||||
}
|
||||
|
||||
|
||||
contentItem: Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: reactionText.implicitHeight / 4
|
||||
|
@ -47,6 +42,7 @@ Flow {
|
|||
|
||||
TextMetrics {
|
||||
id: textMetrics
|
||||
|
||||
font.family: Settings.emojiFont
|
||||
elide: Text.ElideRight
|
||||
elideWidth: 150
|
||||
|
@ -54,8 +50,9 @@ Flow {
|
|||
}
|
||||
|
||||
Text {
|
||||
anchors.baseline: reactionCounter.baseline
|
||||
id: reactionText
|
||||
|
||||
anchors.baseline: reactionCounter.baseline
|
||||
text: textMetrics.elidedText + (textMetrics.elidedText == modelData.key ? "" : "…")
|
||||
font.family: Settings.emojiFont
|
||||
color: reaction.hovered ? colors.highlight : colors.text
|
||||
|
@ -64,31 +61,35 @@ Flow {
|
|||
|
||||
Rectangle {
|
||||
id: divider
|
||||
|
||||
height: Math.floor(reactionCounter.implicitHeight * 1.4)
|
||||
width: 1
|
||||
color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: divider.verticalCenter
|
||||
id: reactionCounter
|
||||
|
||||
anchors.verticalCenter: divider.verticalCenter
|
||||
text: modelData.count
|
||||
font: reaction.font
|
||||
color: reaction.hovered ? colors.highlight : colors.text
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
anchors.centerIn: parent
|
||||
|
||||
implicitWidth: reaction.implicitWidth
|
||||
implicitHeight: reaction.implicitHeight
|
||||
border.color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text
|
||||
color: modelData.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.20) : colors.base
|
||||
color: modelData.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.2) : colors.base
|
||||
border.width: 1
|
||||
radius: reaction.height / 2.0
|
||||
}
|
||||
}
|
||||
}
|
||||
radius: reaction.height / 2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,10 +16,6 @@
|
|||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
/*
|
||||
* Shamelessly stolen from:
|
||||
* https://cgit.kde.org/kube.git/tree/framework/qml/ScrollHelper.qml
|
||||
|
@ -31,81 +27,82 @@ import QtQuick.Controls 2.3
|
|||
* ScrollView.qml in qtquickcontrols
|
||||
* qquickwheelarea.cpp in qtquickcontrols
|
||||
*/
|
||||
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
MouseArea {
|
||||
// console.warn("Delta: ", wheel.pixelDelta.y);
|
||||
// console.warn("Old position: ", flickable.contentY);
|
||||
// console.warn("New position: ", newPos);
|
||||
|
||||
id: root
|
||||
propagateComposedEvents: true
|
||||
|
||||
property Flickable flickable
|
||||
property alias enabled: root.enabled
|
||||
|
||||
//Place the mouse area under the flickable
|
||||
z: -1
|
||||
onFlickableChanged: {
|
||||
if (enabled) {
|
||||
flickable.maximumFlickVelocity = 100000
|
||||
flickable.boundsBehavior = Flickable.StopAtBounds
|
||||
root.parent = flickable
|
||||
}
|
||||
}
|
||||
|
||||
acceptedButtons: Qt.NoButton
|
||||
|
||||
function calculateNewPosition(flickableItem, wheel) {
|
||||
//Nothing to scroll
|
||||
if (flickableItem.contentHeight < flickableItem.height) {
|
||||
if (flickableItem.contentHeight < flickableItem.height)
|
||||
return flickableItem.contentY;
|
||||
}
|
||||
|
||||
//Ignore 0 events (happens at least with Christians trackpad)
|
||||
if (wheel.pixelDelta.y == 0 && wheel.angleDelta.y == 0) {
|
||||
if (wheel.pixelDelta.y == 0 && wheel.angleDelta.y == 0)
|
||||
return flickableItem.contentY;
|
||||
}
|
||||
|
||||
//pixelDelta seems to be the same as angleDelta/8
|
||||
var pixelDelta = 0
|
||||
var pixelDelta = 0;
|
||||
//The pixelDelta is a smaller number if both are provided, so pixelDelta can be 0 while angleDelta is still something. So we check the angleDelta
|
||||
if (wheel.angleDelta.y) {
|
||||
var wheelScrollLines = 3 //Default value of QApplication wheelScrollLines property
|
||||
var pixelPerLine = 20 //Default value in Qt, originally comes from QTextEdit
|
||||
var ticks = (wheel.angleDelta.y / 8) / 15.0 //Divide by 8 gives us pixels typically come in 15pixel steps.
|
||||
pixelDelta = ticks * pixelPerLine * wheelScrollLines
|
||||
var wheelScrollLines = 3; //Default value of QApplication wheelScrollLines property
|
||||
var pixelPerLine = 20; //Default value in Qt, originally comes from QTextEdit
|
||||
var ticks = (wheel.angleDelta.y / 8) / 15; //Divide by 8 gives us pixels typically come in 15pixel steps.
|
||||
pixelDelta = ticks * pixelPerLine * wheelScrollLines;
|
||||
} else {
|
||||
pixelDelta = wheel.pixelDelta.y
|
||||
pixelDelta = wheel.pixelDelta.y;
|
||||
}
|
||||
|
||||
pixelDelta = Math.round(pixelDelta)
|
||||
|
||||
if (!pixelDelta) {
|
||||
pixelDelta = Math.round(pixelDelta);
|
||||
if (!pixelDelta)
|
||||
return flickableItem.contentY;
|
||||
}
|
||||
|
||||
var minYExtent = flickableItem.originY + flickableItem.topMargin;
|
||||
var maxYExtent = (flickableItem.contentHeight + flickableItem.bottomMargin + flickableItem.originY) - flickableItem.height;
|
||||
|
||||
if (typeof(flickableItem.headerItem) !== "undefined" && flickableItem.headerItem) {
|
||||
minYExtent += flickableItem.headerItem.height
|
||||
}
|
||||
if (typeof (flickableItem.headerItem) !== "undefined" && flickableItem.headerItem)
|
||||
minYExtent += flickableItem.headerItem.height;
|
||||
|
||||
//Avoid overscrolling
|
||||
return Math.max(minYExtent, Math.min(maxYExtent, flickableItem.contentY - pixelDelta));
|
||||
}
|
||||
|
||||
propagateComposedEvents: true
|
||||
//Place the mouse area under the flickable
|
||||
z: -1
|
||||
onFlickableChanged: {
|
||||
if (enabled) {
|
||||
flickable.maximumFlickVelocity = 100000;
|
||||
flickable.boundsBehavior = Flickable.StopAtBounds;
|
||||
root.parent = flickable;
|
||||
}
|
||||
}
|
||||
acceptedButtons: Qt.NoButton
|
||||
onWheel: {
|
||||
var newPos = calculateNewPosition(flickable, wheel);
|
||||
// console.warn("Delta: ", wheel.pixelDelta.y);
|
||||
// console.warn("Old position: ", flickable.contentY);
|
||||
// console.warn("New position: ", newPos);
|
||||
|
||||
// Show the scrollbars
|
||||
flickable.flick(0, 0);
|
||||
flickable.contentY = newPos;
|
||||
cancelFlickStateTimer.start()
|
||||
cancelFlickStateTimer.start();
|
||||
}
|
||||
|
||||
|
||||
Timer {
|
||||
id: cancelFlickStateTimer
|
||||
|
||||
//How long the scrollbar will remain visible
|
||||
interval: 500
|
||||
// Hide the scrollbars
|
||||
onTriggered: { flickable.cancelFlick(); flickable.movementEnded(); }
|
||||
onTriggered: {
|
||||
flickable.cancelFlick();
|
||||
flickable.movementEnded();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,36 +4,54 @@ import im.nheko 1.0
|
|||
|
||||
Rectangle {
|
||||
id: indicator
|
||||
|
||||
property int state: 0
|
||||
|
||||
color: "transparent"
|
||||
width: 16
|
||||
height: 16
|
||||
|
||||
ToolTip.visible: ma.containsMouse && state != MtxEvent.Empty
|
||||
ToolTip.text: switch (state) {
|
||||
case MtxEvent.Failed: return qsTr("Failed")
|
||||
case MtxEvent.Sent: return qsTr("Sent")
|
||||
case MtxEvent.Received: return qsTr("Received")
|
||||
case MtxEvent.Read: return qsTr("Read")
|
||||
default: return ""
|
||||
ToolTip.text: {
|
||||
switch (state) {
|
||||
case MtxEvent.Failed:
|
||||
return qsTr("Failed");
|
||||
case MtxEvent.Sent:
|
||||
return qsTr("Sent");
|
||||
case MtxEvent.Received:
|
||||
return qsTr("Received");
|
||||
case MtxEvent.Read:
|
||||
return qsTr("Read");
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: ma
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
}
|
||||
|
||||
Image {
|
||||
id: stateImg
|
||||
|
||||
// Workaround, can't get icon.source working for now...
|
||||
anchors.fill: parent
|
||||
source: switch (indicator.state) {
|
||||
case MtxEvent.Failed: return "image://colorimage/:/icons/icons/ui/remove-symbol.png?" + colors.buttonText
|
||||
case MtxEvent.Sent: return "image://colorimage/:/icons/icons/ui/clock.png?" + colors.buttonText
|
||||
case MtxEvent.Received: return "image://colorimage/:/icons/icons/ui/checkmark.png?" + colors.buttonText
|
||||
case MtxEvent.Read: return "image://colorimage/:/icons/icons/ui/double-tick-indicator.png?" + colors.buttonText
|
||||
default: return ""
|
||||
source: {
|
||||
switch (indicator.state) {
|
||||
case MtxEvent.Failed:
|
||||
return "image://colorimage/:/icons/icons/ui/remove-symbol.png?" + colors.buttonText;
|
||||
case MtxEvent.Sent:
|
||||
return "image://colorimage/:/icons/icons/ui/clock.png?" + colors.buttonText;
|
||||
case MtxEvent.Received:
|
||||
return "image://colorimage/:/icons/icons/ui/checkmark.png?" + colors.buttonText;
|
||||
case MtxEvent.Read:
|
||||
return "image://colorimage/:/icons/icons/ui/double-tick-indicator.png?" + colors.buttonText;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import "./delegates"
|
||||
import "./emoji"
|
||||
import QtQuick 2.6
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import im.nheko 1.0
|
||||
|
||||
import "./delegates"
|
||||
import "./emoji"
|
||||
|
||||
Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
@ -18,20 +16,22 @@ Item {
|
|||
propagateComposedEvents: true
|
||||
preventStealing: true
|
||||
hoverEnabled: true
|
||||
|
||||
acceptedButtons: Qt.AllButtons
|
||||
onClicked: {
|
||||
if (mouse.button === Qt.RightButton)
|
||||
messageContextMenu.show(model.id, model.type, model.isEncrypted, row)
|
||||
messageContextMenu.show(model.id, model.type, model.isEncrypted, row);
|
||||
|
||||
}
|
||||
onPressAndHold: {
|
||||
messageContextMenu.show(model.id, model.type, model.isEncrypted, row, mapToItem(timelineRoot, mouse.x, mouse.y))
|
||||
messageContextMenu.show(model.id, model.type, model.isEncrypted, row, mapToItem(timelineRoot, mouse.x, mouse.y));
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: (Settings.messageHoverHighlight && parent.containsMouse) ? colors.base : "transparent"
|
||||
anchors.fill: row
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
|
||||
|
@ -39,7 +39,6 @@ Item {
|
|||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
|
||||
Column {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
@ -57,15 +56,16 @@ Item {
|
|||
id: contentItem
|
||||
|
||||
width: parent.width
|
||||
|
||||
modelData: model
|
||||
}
|
||||
|
||||
Reactions {
|
||||
id: reactionRow
|
||||
|
||||
reactions: model.reactions
|
||||
eventId: model.id
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StatusIndicator {
|
||||
|
@ -82,47 +82,46 @@ Item {
|
|||
Layout.preferredHeight: 16
|
||||
width: 16
|
||||
}
|
||||
|
||||
EmojiButton {
|
||||
id: reactButton
|
||||
|
||||
visible: Settings.buttonsInTimeline
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||
Layout.preferredHeight: 16
|
||||
width: 16
|
||||
id: reactButton
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("React")
|
||||
emojiPicker: emojiPopup
|
||||
event_id: model.id
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: replyButton
|
||||
|
||||
visible: Settings.buttonsInTimeline
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||
Layout.preferredHeight: 16
|
||||
width: 16
|
||||
id: replyButton
|
||||
hoverEnabled: true
|
||||
|
||||
|
||||
image: ":/icons/icons/ui/mail-reply.png"
|
||||
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Reply")
|
||||
|
||||
onClicked: chat.model.replyAction(model.id)
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: optionsButton
|
||||
|
||||
visible: Settings.buttonsInTimeline
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||
Layout.preferredHeight: 16
|
||||
width: 16
|
||||
id: optionsButton
|
||||
hoverEnabled: true
|
||||
|
||||
image: ":/icons/icons/ui/vertical-ellipsis.png"
|
||||
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Options")
|
||||
|
||||
onClicked: messageContextMenu.show(model.id, model.type, model.isEncrypted, optionsButton)
|
||||
}
|
||||
|
||||
|
@ -131,16 +130,19 @@ Item {
|
|||
text: model.timestamp.toLocaleTimeString("HH:mm")
|
||||
width: Math.max(implicitWidth, text.length * fontMetrics.maximumCharacterWidth)
|
||||
color: inactiveColors.text
|
||||
ToolTip.visible: ma.containsMouse
|
||||
ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate)
|
||||
|
||||
MouseArea {
|
||||
id: ma
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
}
|
||||
|
||||
ToolTip.visible: ma.containsMouse
|
||||
ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
import "./delegates"
|
||||
import "./device-verification"
|
||||
import "./emoji"
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import im.nheko 1.0
|
||||
import im.nheko.EmojiModel 1.0
|
||||
|
||||
import "./delegates"
|
||||
import "./emoji"
|
||||
import "./device-verification"
|
||||
|
||||
Page {
|
||||
id: timelineRoot
|
||||
|
||||
property var colors: currentActivePalette
|
||||
property var systemInactive: SystemPalette { colorGroup: SystemPalette.Disabled }
|
||||
property var systemInactive
|
||||
property var inactiveColors: currentInactivePalette ? currentInactivePalette : systemInactive
|
||||
property int avatarSize: 40
|
||||
property real highlightHue: colors.highlight.hslHue
|
||||
|
@ -30,69 +28,83 @@ Page {
|
|||
|
||||
EmojiPicker {
|
||||
id: emojiPopup
|
||||
|
||||
width: 7 * 52 + 20
|
||||
height: 6 * 52
|
||||
colors: palette
|
||||
|
||||
model: EmojiProxyModel {
|
||||
category: EmojiCategory.People
|
||||
sourceModel: EmojiModel {}
|
||||
|
||||
sourceModel: EmojiModel {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: messageContextMenu
|
||||
modal: true
|
||||
|
||||
function show(eventId_, eventType_, isEncrypted_, showAt_, position) {
|
||||
eventId = eventId_
|
||||
eventType = eventType_
|
||||
isEncrypted = isEncrypted_
|
||||
|
||||
if (position)
|
||||
popup(position, showAt_)
|
||||
else
|
||||
popup(showAt_)
|
||||
}
|
||||
|
||||
property string eventId
|
||||
property int eventType
|
||||
property bool isEncrypted
|
||||
|
||||
function show(eventId_, eventType_, isEncrypted_, showAt_, position) {
|
||||
eventId = eventId_;
|
||||
eventType = eventType_;
|
||||
isEncrypted = isEncrypted_;
|
||||
if (position)
|
||||
popup(position, showAt_);
|
||||
else
|
||||
popup(showAt_);
|
||||
}
|
||||
|
||||
modal: true
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("React")
|
||||
onClicked: emojiPopup.show(messageContextMenu.parent, messageContextMenu.eventId)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("Reply")
|
||||
onClicked: chat.model.replyAction(messageContextMenu.eventId)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("Read receipts")
|
||||
onTriggered: chat.model.readReceiptsAction(messageContextMenu.eventId)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("Mark as read")
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("View raw message")
|
||||
onTriggered: chat.model.viewRawMessage(messageContextMenu.eventId)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
visible: messageContextMenu.isEncrypted
|
||||
height: visible ? implicitHeight : 0
|
||||
text: qsTr("View decrypted raw message")
|
||||
onTriggered: chat.model.viewDecryptedRawMessage(messageContextMenu.eventId)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("Redact message")
|
||||
onTriggered: chat.model.redactEvent(messageContextMenu.eventId)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
|
||||
height: visible ? implicitHeight : 0
|
||||
text: qsTr("Save as")
|
||||
onTriggered: TimelineManager.timeline.saveMedia(messageContextMenu.eventId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
@ -101,21 +113,32 @@ Page {
|
|||
|
||||
Component {
|
||||
id: deviceVerificationDialog
|
||||
DeviceVerification {}
|
||||
|
||||
DeviceVerification {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: TimelineManager
|
||||
function onNewDeviceVerificationRequest(flow, transactionId, userId, deviceId, isRequest) {
|
||||
var dialog = deviceVerificationDialog.createObject(timelineRoot, {flow: flow});
|
||||
var dialog = deviceVerificationDialog.createObject(timelineRoot, {
|
||||
"flow": flow
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
target: TimelineManager
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: TimelineManager.timeline
|
||||
function onOpenProfile(profile) {
|
||||
var userProfile = userProfileComponent.createObject(timelineRoot,{profile: profile});
|
||||
var userProfile = userProfileComponent.createObject(timelineRoot, {
|
||||
"profile": profile
|
||||
});
|
||||
userProfile.show();
|
||||
}
|
||||
|
||||
target: TimelineManager.timeline
|
||||
}
|
||||
|
||||
Label {
|
||||
|
@ -137,21 +160,23 @@ Page {
|
|||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle {
|
||||
id: topBar
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: topLayout.height + 16
|
||||
z: 3
|
||||
|
||||
color: colors.base
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: TimelineManager.openRoomSettings();
|
||||
onClicked: TimelineManager.openRoomSettings()
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
//Layout.margins: 8
|
||||
|
||||
id: topLayout
|
||||
|
||||
anchors.left: parent.left
|
||||
|
@ -159,8 +184,6 @@ Page {
|
|||
anchors.margins: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
//Layout.margins: 8
|
||||
|
||||
ImageButton {
|
||||
id: backToRoomsButton
|
||||
|
||||
|
@ -168,14 +191,10 @@ Page {
|
|||
Layout.row: 0
|
||||
Layout.rowSpan: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
visible: TimelineManager.isNarrowView
|
||||
|
||||
image: ":/icons/icons/ui/angle-pointing-to-left.png"
|
||||
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Back to room list")
|
||||
|
||||
onClicked: TimelineManager.backToRooms()
|
||||
}
|
||||
|
||||
|
@ -184,17 +203,16 @@ Page {
|
|||
Layout.row: 0
|
||||
Layout.rowSpan: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
width: avatarSize
|
||||
height: avatarSize
|
||||
|
||||
url: chat.model ? chat.model.roomAvatarUrl.replace("mxc://", "image://MxcImage/") : ""
|
||||
displayName: chat.model ? chat.model.roomName : qsTr("No room selected")
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: TimelineManager.openRoomSettings();
|
||||
onClicked: TimelineManager.openRoomSettings()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Label {
|
||||
|
@ -202,23 +220,22 @@ Page {
|
|||
Layout.column: 2
|
||||
Layout.row: 0
|
||||
color: colors.text
|
||||
|
||||
font.pointSize: fontMetrics.font.pointSize * 1.1
|
||||
|
||||
text: chat.model ? chat.model.roomName : qsTr("No room selected")
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: TimelineManager.openRoomSettings();
|
||||
onClicked: TimelineManager.openRoomSettings()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
Layout.fillWidth: true
|
||||
Layout.column: 2
|
||||
Layout.row: 1
|
||||
Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
|
||||
clip: true
|
||||
|
||||
text: chat.model ? chat.model.roomTopic : ""
|
||||
}
|
||||
|
||||
|
@ -229,141 +246,116 @@ Page {
|
|||
Layout.row: 0
|
||||
Layout.rowSpan: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
image: ":/icons/icons/ui/vertical-ellipsis.png"
|
||||
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Room options")
|
||||
|
||||
onClicked: roomOptionsMenu.popup(roomOptionsButton)
|
||||
|
||||
Menu {
|
||||
id: roomOptionsMenu
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("Invite users")
|
||||
onTriggered: TimelineManager.openInviteUsersDialog();
|
||||
onTriggered: TimelineManager.openInviteUsersDialog()
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("Members")
|
||||
onTriggered: TimelineManager.openMemberListDialog();
|
||||
onTriggered: TimelineManager.openMemberListDialog()
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("Leave room")
|
||||
onTriggered: TimelineManager.openLeaveRoomDialog();
|
||||
onTriggered: TimelineManager.openLeaveRoomDialog()
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("Settings")
|
||||
onTriggered: TimelineManager.openRoomSettings();
|
||||
onTriggered: TimelineManager.openRoomSettings()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: chat
|
||||
|
||||
property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2)
|
||||
|
||||
visible: TimelineManager.timeline != null
|
||||
|
||||
cacheBuffer: 400
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
model: TimelineManager.timeline
|
||||
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
pixelAligned: true
|
||||
spacing: 4
|
||||
verticalLayoutDirection: ListView.BottomToTop
|
||||
onCountChanged: {
|
||||
if (atYEnd)
|
||||
model.currentIndex = 0;
|
||||
|
||||
} // Mark last event as read, since we are at the bottom
|
||||
|
||||
ScrollHelper {
|
||||
flickable: parent
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
pixelAligned: true
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.MoveToPreviousPage
|
||||
onActivated: { chat.contentY = chat.contentY - chat.height / 2; chat.returnToBounds(); }
|
||||
onActivated: {
|
||||
chat.contentY = chat.contentY - chat.height / 2;
|
||||
chat.returnToBounds();
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.MoveToNextPage
|
||||
onActivated: { chat.contentY = chat.contentY + chat.height / 2; chat.returnToBounds(); }
|
||||
onActivated: {
|
||||
chat.contentY = chat.contentY + chat.height / 2;
|
||||
chat.returnToBounds();
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
onActivated: chat.model.reply = undefined
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Alt+Up"
|
||||
onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0)
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Alt+Down"
|
||||
onActivated: {
|
||||
var idx = chat.model.reply? chat.model.idToIndex(chat.model.reply) - 1 : -1
|
||||
chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : undefined
|
||||
}
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
id: scrollbar
|
||||
}
|
||||
|
||||
spacing: 4
|
||||
verticalLayoutDirection: ListView.BottomToTop
|
||||
|
||||
onCountChanged: if (atYEnd) model.currentIndex = 0 // Mark last event as read, since we are at the bottom
|
||||
|
||||
property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width*2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width*2)
|
||||
|
||||
delegate: Item {
|
||||
// This would normally be previousSection, but our model's order is inverted.
|
||||
property bool sectionBoundary: (ListView.nextSection != "" && ListView.nextSection !== ListView.section) || model.index === chat.count - 1
|
||||
|
||||
id: wrapper
|
||||
property Item section
|
||||
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
||||
width: chat.delegateMaxWidth
|
||||
height: section ? section.height + timelinerow.height : timelinerow.height
|
||||
|
||||
TimelineRow {
|
||||
id: timelinerow
|
||||
y: section ? section.y + section.height : 0
|
||||
}
|
||||
|
||||
onSectionBoundaryChanged: {
|
||||
if (sectionBoundary) {
|
||||
var properties = {
|
||||
'modelData': model.dump,
|
||||
'section': ListView.section,
|
||||
'nextSection': ListView.nextSection
|
||||
}
|
||||
section = sectionHeader.createObject(wrapper, properties)
|
||||
} else {
|
||||
section.destroy()
|
||||
section = null
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: chat
|
||||
function onMovementEnded() {
|
||||
if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
|
||||
chat.model.currentIndex = index;
|
||||
}
|
||||
var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1;
|
||||
chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: userProfileComponent
|
||||
UserProfile{}
|
||||
|
||||
UserProfile {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
section {
|
||||
property: "section"
|
||||
}
|
||||
|
||||
Component {
|
||||
id: sectionHeader
|
||||
|
||||
Column {
|
||||
property var modelData
|
||||
property string section
|
||||
|
@ -372,28 +364,27 @@ Page {
|
|||
topPadding: 4
|
||||
bottomPadding: 4
|
||||
spacing: 8
|
||||
|
||||
visible: !!modelData
|
||||
|
||||
width: parent.width
|
||||
height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
|
||||
|
||||
Label {
|
||||
id: dateBubble
|
||||
|
||||
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
||||
visible: section.includes(" ")
|
||||
text: chat.model.formatDateSeparator(modelData.timestamp)
|
||||
color: colors.text
|
||||
|
||||
height: fontMetrics.height * 1.4
|
||||
width: contentWidth * 1.2
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
background: Rectangle {
|
||||
radius: parent.height / 2
|
||||
color: colors.base
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Row {
|
||||
|
@ -413,10 +404,12 @@ Page {
|
|||
cursorShape: Qt.PointingHandCursor
|
||||
propagateComposedEvents: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Label {
|
||||
id: userName
|
||||
|
||||
text: TimelineManager.escapeEmoji(modelData.userName)
|
||||
color: TimelineManager.userColor(modelData.userId, colors.window)
|
||||
textFormat: Text.RichText
|
||||
|
@ -428,9 +421,59 @@ Page {
|
|||
cursorShape: Qt.PointingHandCursor
|
||||
propagateComposedEvents: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
id: scrollbar
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
id: wrapper
|
||||
|
||||
// This would normally be previousSection, but our model's order is inverted.
|
||||
property bool sectionBoundary: (ListView.nextSection != "" && ListView.nextSection !== ListView.section) || model.index === chat.count - 1
|
||||
property Item section
|
||||
|
||||
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
||||
width: chat.delegateMaxWidth
|
||||
height: section ? section.height + timelinerow.height : timelinerow.height
|
||||
onSectionBoundaryChanged: {
|
||||
if (sectionBoundary) {
|
||||
var properties = {
|
||||
"modelData": model.dump,
|
||||
"section": ListView.section,
|
||||
"nextSection": ListView.nextSection
|
||||
};
|
||||
section = sectionHeader.createObject(wrapper, properties);
|
||||
} else {
|
||||
section.destroy();
|
||||
section = null;
|
||||
}
|
||||
}
|
||||
|
||||
TimelineRow {
|
||||
id: timelinerow
|
||||
|
||||
y: section ? section.y + section.height : 0
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onMovementEnded() {
|
||||
if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
|
||||
chat.model.currentIndex = index;
|
||||
|
||||
}
|
||||
|
||||
target: chat
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
footer: BusyIndicator {
|
||||
|
@ -440,6 +483,7 @@ Page {
|
|||
width: 50
|
||||
z: 3
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
|
@ -451,17 +495,22 @@ Page {
|
|||
|
||||
Column {
|
||||
id: footerContent
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
Rectangle {
|
||||
id: typingRect
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
color: (chat.model && chat.model.typingUsers.length > 0) ? colors.window : "transparent"
|
||||
height: typingDisplay.height
|
||||
|
||||
Label {
|
||||
id: typingDisplay
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
|
@ -470,20 +519,19 @@ Page {
|
|||
text: chat.model ? chat.model.formatTypingUsers(chat.model.typingUsers, colors.window) : ""
|
||||
textFormat: Text.RichText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
id: replyPopup
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
visible: chat.model && chat.model.reply
|
||||
// Height of child, plus margins, plus border
|
||||
height: replyPreview.height + 10
|
||||
color: colors.base
|
||||
|
||||
|
||||
Reply {
|
||||
id: replyPreview
|
||||
|
||||
|
@ -492,8 +540,8 @@ Page {
|
|||
anchors.right: closeReplyButton.left
|
||||
anchors.rightMargin: 20
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
modelData: chat.model ? chat.model.getDump(chat.model.reply, chat.model.id) : {}
|
||||
modelData: chat.model ? chat.model.getDump(chat.model.reply, chat.model.id) : {
|
||||
}
|
||||
userColor: TimelineManager.userColor(modelData.userId, colors.window)
|
||||
}
|
||||
|
||||
|
@ -506,21 +554,29 @@ Page {
|
|||
hoverEnabled: true
|
||||
width: 16
|
||||
height: 16
|
||||
|
||||
image: ":/icons/icons/ui/remove-symbol.png"
|
||||
ToolTip.visible: closeReplyButton.hovered
|
||||
ToolTip.text: qsTr("Close")
|
||||
|
||||
onClicked: chat.model.reply = undefined
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ActiveCallBar {
|
||||
Layout.fillWidth: true
|
||||
z: 3
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
systemInactive: SystemPalette {
|
||||
colorGroup: SystemPalette.Disabled
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
import "./device-verification"
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Window 2.3
|
||||
|
||||
import im.nheko 1.0
|
||||
|
||||
import "./device-verification"
|
||||
|
||||
ApplicationWindow {
|
||||
id: userProfileDialog
|
||||
|
||||
property var profile
|
||||
|
||||
id: userProfileDialog
|
||||
height: 650
|
||||
width: 420
|
||||
minimumHeight: 420
|
||||
|
||||
palette: colors
|
||||
|
||||
Component {
|
||||
id: deviceVerificationDialog
|
||||
DeviceVerification {}
|
||||
|
||||
DeviceVerification {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
|
@ -27,7 +28,6 @@ ApplicationWindow{
|
|||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
|
||||
spacing: 10
|
||||
|
||||
Avatar {
|
||||
|
@ -56,11 +56,11 @@ ApplicationWindow{
|
|||
|
||||
Button {
|
||||
id: verifyUserButton
|
||||
|
||||
text: qsTr("Verify")
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
enabled: !profile.isUserVerified
|
||||
visible: !profile.isUserVerified
|
||||
|
||||
onClicked: profile.verify()
|
||||
}
|
||||
|
||||
|
@ -76,6 +76,7 @@ ApplicationWindow{
|
|||
onClicked: profile.banUser()
|
||||
}
|
||||
// ImageButton{
|
||||
|
||||
// image:":/icons/icons/ui/volume-off-indicator.png"
|
||||
// Layout.margins: {
|
||||
// left: 5
|
||||
|
@ -94,6 +95,7 @@ ApplicationWindow{
|
|||
ToolTip.text: qsTr("Start a private chat")
|
||||
onClicked: profile.startChat()
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
image: ":/icons/icons/ui/round-remove-button.png"
|
||||
hoverEnabled: true
|
||||
|
@ -101,6 +103,7 @@ ApplicationWindow{
|
|||
ToolTip.text: qsTr("Kick the user")
|
||||
onClicked: profile.kickUser()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ListView {
|
||||
|
@ -109,11 +112,9 @@ ApplicationWindow{
|
|||
Layout.fillHeight: true
|
||||
Layout.minimumHeight: 200
|
||||
Layout.fillWidth: true
|
||||
|
||||
clip: true
|
||||
spacing: 8
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
model: profile.deviceList
|
||||
|
||||
delegate: RowLayout {
|
||||
|
@ -122,51 +123,53 @@ ApplicationWindow{
|
|||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
|
||||
elide: Text.ElideRight
|
||||
font.bold: true
|
||||
color: colors.text
|
||||
text: model.deviceId
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignRight
|
||||
|
||||
elide: Text.ElideRight
|
||||
color: colors.text
|
||||
text: model.deviceName
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Image {
|
||||
Layout.preferredHeight: 16
|
||||
Layout.preferredWidth: 16
|
||||
|
||||
source: ((model.verificationStatus == VerificationStatus.VERIFIED)?"image://colorimage/:/icons/icons/ui/lock.png?green":
|
||||
((model.verificationStatus == VerificationStatus.UNVERIFIED)?"image://colorimage/:/icons/icons/ui/unlock.png?yellow":
|
||||
"image://colorimage/:/icons/icons/ui/unlock.png?red"))
|
||||
source: ((model.verificationStatus == VerificationStatus.VERIFIED) ? "image://colorimage/:/icons/icons/ui/lock.png?green" : ((model.verificationStatus == VerificationStatus.UNVERIFIED) ? "image://colorimage/:/icons/icons/ui/unlock.png?yellow" : "image://colorimage/:/icons/icons/ui/unlock.png?red"))
|
||||
}
|
||||
|
||||
Button {
|
||||
id: verifyButton
|
||||
|
||||
text: (model.verificationStatus != VerificationStatus.VERIFIED) ? "Verify" : "Unverify"
|
||||
onClicked: {
|
||||
if(model.verificationStatus == VerificationStatus.VERIFIED){
|
||||
profile.unverify(model.deviceId)
|
||||
}else{
|
||||
if (model.verificationStatus == VerificationStatus.VERIFIED)
|
||||
profile.unverify(model.deviceId);
|
||||
else
|
||||
profile.verify(model.deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
footer: DialogButtonBox {
|
||||
standardButtons: DialogButtonBox.Ok
|
||||
|
||||
onAccepted: userProfileDialog.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import QtQuick 2.6
|
||||
import QtQuick.Layouts 1.2
|
||||
|
||||
import im.nheko 1.0
|
||||
|
||||
Item {
|
||||
|
@ -12,49 +11,57 @@ Item {
|
|||
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - 24
|
||||
|
||||
spacing: 15
|
||||
|
||||
Rectangle {
|
||||
id: button
|
||||
|
||||
color: colors.light
|
||||
radius: 22
|
||||
height: 44
|
||||
width: 44
|
||||
|
||||
Image {
|
||||
id: img
|
||||
anchors.centerIn: parent
|
||||
|
||||
anchors.centerIn: parent
|
||||
source: "qrc:/icons/icons/ui/arrow-pointing-down.png"
|
||||
fillMode: Image.Pad
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: TimelineManager.timeline.saveMedia(model.data.id)
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: col
|
||||
|
||||
Text {
|
||||
id: filename
|
||||
|
||||
Layout.fillWidth: true
|
||||
text: model.data.filename
|
||||
textFormat: Text.PlainText
|
||||
elide: Text.ElideRight
|
||||
color: colors.text
|
||||
}
|
||||
|
||||
Text {
|
||||
id: filesize
|
||||
|
||||
Layout.fillWidth: true
|
||||
text: model.data.filesize
|
||||
textFormat: Text.PlainText
|
||||
elide: Text.ElideRight
|
||||
color: colors.text
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import QtQuick 2.6
|
||||
|
||||
import im.nheko 1.0
|
||||
|
||||
Item {
|
||||
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? parent.width : model.data.width)
|
||||
property double tempHeight: tempWidth * model.data.proportionalHeight
|
||||
|
||||
property double divisor: model.isReply ? 4 : 2
|
||||
property bool tooHigh: tempHeight > timelineRoot.height / divisor
|
||||
|
||||
|
@ -14,21 +12,20 @@ Item {
|
|||
|
||||
Image {
|
||||
id: blurhash
|
||||
|
||||
anchors.fill: parent
|
||||
visible: img.status != Image.Ready
|
||||
|
||||
source: model.data.blurhash ? ("image://blurhash/" + model.data.blurhash) : ("image://colorimage/:/icons/icons/ui/do-not-disturb-rounded-sign@2x.png?" + colors.buttonText)
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
sourceSize.width: parent.width
|
||||
sourceSize.height: parent.height
|
||||
}
|
||||
|
||||
Image {
|
||||
id: img
|
||||
anchors.fill: parent
|
||||
|
||||
anchors.fill: parent
|
||||
source: model.data.url.replace("mxc://", "image://MxcImage/")
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
@ -38,5 +35,7 @@ Item {
|
|||
anchors.fill: parent
|
||||
onClicked: TimelineManager.openImageOverlay(model.data.url, model.data.id)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,215 +2,334 @@ import QtQuick 2.6
|
|||
import im.nheko 1.0
|
||||
|
||||
Item {
|
||||
property alias modelData: model.data
|
||||
property alias isReply: model.isReply
|
||||
property real implicitWidth: (chooser.child && chooser.child.implicitWidth) ? chooser.child.implicitWidth : width
|
||||
|
||||
height: chooser.childrenRect.height
|
||||
|
||||
// Workaround to have an assignable global property
|
||||
Item {
|
||||
id: model
|
||||
property var data;
|
||||
|
||||
property var data
|
||||
property bool isReply: false
|
||||
}
|
||||
|
||||
property alias modelData: model.data
|
||||
property alias isReply: model.isReply
|
||||
|
||||
height: chooser.childrenRect.height
|
||||
property real implicitWidth: (chooser.child && chooser.child.implicitWidth) ? chooser.child.implicitWidth : width
|
||||
|
||||
DelegateChooser {
|
||||
id: chooser
|
||||
|
||||
//role: "type" //< not supported in our custom implementation, have to use roleValue
|
||||
roleValue: model.data.type
|
||||
anchors.fill: parent
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.UnknownMessage
|
||||
Placeholder { text: "Unretrieved event" }
|
||||
|
||||
Placeholder {
|
||||
text: "Unretrieved event"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.TextMessage
|
||||
TextMessage {}
|
||||
|
||||
TextMessage {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.NoticeMessage
|
||||
NoticeMessage {}
|
||||
|
||||
NoticeMessage {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.EmoteMessage
|
||||
|
||||
NoticeMessage {
|
||||
formatted: TimelineManager.escapeEmoji(modelData.userName) + " " + model.data.formattedBody
|
||||
color: TimelineManager.userColor(modelData.userId, colors.window)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.ImageMessage
|
||||
ImageMessage {}
|
||||
|
||||
ImageMessage {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.Sticker
|
||||
ImageMessage {}
|
||||
|
||||
ImageMessage {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.FileMessage
|
||||
FileMessage {}
|
||||
|
||||
FileMessage {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.VideoMessage
|
||||
PlayableMediaMessage {}
|
||||
|
||||
PlayableMediaMessage {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.AudioMessage
|
||||
PlayableMediaMessage {}
|
||||
|
||||
PlayableMediaMessage {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.Redacted
|
||||
|
||||
Pill {
|
||||
text: qsTr("redacted")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.Redaction
|
||||
|
||||
Pill {
|
||||
text: qsTr("redacted")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.Encryption
|
||||
|
||||
Pill {
|
||||
text: qsTr("Encryption enabled")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.Name
|
||||
|
||||
NoticeMessage {
|
||||
text: model.data.roomName ? qsTr("room name changed to: %1").arg(model.data.roomName) : qsTr("removed room name")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.Topic
|
||||
|
||||
NoticeMessage {
|
||||
text: model.data.roomTopic ? qsTr("topic changed to: %1").arg(model.data.roomTopic) : qsTr("removed topic")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.RoomCreate
|
||||
|
||||
NoticeMessage {
|
||||
text: qsTr("%1 created and configured room: %2").arg(model.data.userName).arg(model.data.roomId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.CallInvite
|
||||
|
||||
NoticeMessage {
|
||||
text: switch(model.data.callType) {
|
||||
case "voice": return qsTr("%1 placed a voice call.").arg(model.data.userName)
|
||||
case "video": return qsTr("%1 placed a video call.").arg(model.data.userName)
|
||||
default: return qsTr("%1 placed a call.").arg(model.data.userName)
|
||||
text: {
|
||||
switch (model.data.callType) {
|
||||
case "voice":
|
||||
return qsTr("%1 placed a voice call.").arg(model.data.userName);
|
||||
case "video":
|
||||
return qsTr("%1 placed a video call.").arg(model.data.userName);
|
||||
default:
|
||||
return qsTr("%1 placed a call.").arg(model.data.userName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.CallAnswer
|
||||
|
||||
NoticeMessage {
|
||||
text: qsTr("%1 answered the call.").arg(model.data.userName)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.CallHangUp
|
||||
|
||||
NoticeMessage {
|
||||
text: qsTr("%1 ended the call.").arg(model.data.userName)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.CallCandidates
|
||||
|
||||
NoticeMessage {
|
||||
text: qsTr("Negotiating call...")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
// TODO: make a more complex formatter for the power levels.
|
||||
roleValue: MtxEvent.PowerLevels
|
||||
|
||||
NoticeMessage {
|
||||
text: TimelineManager.timeline.formatPowerLevelEvent(model.data.id)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.RoomJoinRules
|
||||
|
||||
NoticeMessage {
|
||||
text: TimelineManager.timeline.formatJoinRuleEvent(model.data.id)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.RoomHistoryVisibility
|
||||
|
||||
NoticeMessage {
|
||||
text: TimelineManager.timeline.formatHistoryVisibilityEvent(model.data.id)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.RoomGuestAccess
|
||||
|
||||
NoticeMessage {
|
||||
text: TimelineManager.timeline.formatGuestAccessEvent(model.data.id)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.Member
|
||||
|
||||
NoticeMessage {
|
||||
text: TimelineManager.timeline.formatMemberEvent(model.data.id);
|
||||
text: TimelineManager.timeline.formatMemberEvent(model.data.id)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.KeyVerificationRequest
|
||||
|
||||
NoticeMessage {
|
||||
text: "KeyVerificationRequest";
|
||||
text: "KeyVerificationRequest"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.KeyVerificationStart
|
||||
|
||||
NoticeMessage {
|
||||
text: "KeyVerificationStart";
|
||||
text: "KeyVerificationStart"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.KeyVerificationReady
|
||||
|
||||
NoticeMessage {
|
||||
text: "KeyVerificationReady";
|
||||
text: "KeyVerificationReady"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.KeyVerificationCancel
|
||||
|
||||
NoticeMessage {
|
||||
text: "KeyVerificationCancel";
|
||||
text: "KeyVerificationCancel"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.KeyVerificationKey
|
||||
|
||||
NoticeMessage {
|
||||
text: "KeyVerificationKey";
|
||||
text: "KeyVerificationKey"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.KeyVerificationMac
|
||||
|
||||
NoticeMessage {
|
||||
text: "KeyVerificationMac";
|
||||
text: "KeyVerificationMac"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.KeyVerificationDone
|
||||
|
||||
NoticeMessage {
|
||||
text: "KeyVerificationDone";
|
||||
text: "KeyVerificationDone"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.KeyVerificationDone
|
||||
|
||||
NoticeMessage {
|
||||
text: "KeyVerificationDone";
|
||||
text: "KeyVerificationDone"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.KeyVerificationAccept
|
||||
|
||||
NoticeMessage {
|
||||
text: "KeyVerificationAccept";
|
||||
text: "KeyVerificationAccept"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
Placeholder {}
|
||||
Placeholder {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,11 +4,12 @@ import QtQuick.Controls 2.1
|
|||
Label {
|
||||
color: colors.brightText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
height: contentHeight * 1.2
|
||||
width: contentWidth * 1.2
|
||||
|
||||
background: Rectangle {
|
||||
radius: parent.height / 2
|
||||
color: colors.dark
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import QtQuick 2.6
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Controls 2.1
|
||||
import QtMultimedia 5.6
|
||||
|
||||
import QtQuick 2.6
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.2
|
||||
import im.nheko 1.0
|
||||
|
||||
Rectangle {
|
||||
id: bg
|
||||
|
||||
radius: 10
|
||||
color: colors.dark
|
||||
height: Math.round(content.height + 24)
|
||||
|
@ -14,20 +14,22 @@ Rectangle {
|
|||
|
||||
Column {
|
||||
id: content
|
||||
|
||||
width: parent.width - 24
|
||||
anchors.centerIn: parent
|
||||
|
||||
Rectangle {
|
||||
id: videoContainer
|
||||
visible: model.data.type == MtxEvent.VideoMessage
|
||||
|
||||
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? 400 : model.data.width)
|
||||
property double tempHeight: tempWidth * model.data.proportionalHeight
|
||||
|
||||
property double divisor: model.isReply ? 4 : 2
|
||||
property bool tooHigh: tempHeight > timelineRoot.height / divisor
|
||||
|
||||
visible: model.data.type == MtxEvent.VideoMessage
|
||||
height: tooHigh ? timelineRoot.height / divisor : tempHeight
|
||||
width: tooHigh ? (timelineRoot.height / divisor) / model.data.proportionalHeight : tempWidth
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: model.data.thumbnailUrl.replace("mxc://", "image://MxcImage/")
|
||||
|
@ -39,90 +41,136 @@ Rectangle {
|
|||
fillMode: VideoOutput.PreserveAspectFit
|
||||
source: media
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
width: parent.width
|
||||
|
||||
Text {
|
||||
id: positionText
|
||||
|
||||
text: "--:--:--"
|
||||
color: colors.text
|
||||
}
|
||||
Slider {
|
||||
Layout.fillWidth: true
|
||||
id: progress
|
||||
value: media.position
|
||||
from: 0
|
||||
to: media.duration
|
||||
|
||||
onMoved: media.seek(value)
|
||||
Slider {
|
||||
id: progress
|
||||
|
||||
//indeterminate: true
|
||||
function updatePositionTexts() {
|
||||
function formatTime(date) {
|
||||
var hh = date.getUTCHours();
|
||||
var mm = date.getUTCMinutes();
|
||||
var ss = date.getSeconds();
|
||||
if (hh < 10) {hh = "0"+hh;}
|
||||
if (mm < 10) {mm = "0"+mm;}
|
||||
if (ss < 10) {ss = "0"+ss;}
|
||||
if (hh < 10)
|
||||
hh = "0" + hh;
|
||||
|
||||
if (mm < 10)
|
||||
mm = "0" + mm;
|
||||
|
||||
if (ss < 10)
|
||||
ss = "0" + ss;
|
||||
|
||||
return hh + ":" + mm + ":" + ss;
|
||||
}
|
||||
positionText.text = formatTime(new Date(media.position))
|
||||
durationText.text = formatTime(new Date(media.duration))
|
||||
}
|
||||
onValueChanged: updatePositionTexts()
|
||||
|
||||
positionText.text = formatTime(new Date(media.position));
|
||||
durationText.text = formatTime(new Date(media.duration));
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
value: media.position
|
||||
from: 0
|
||||
to: media.duration
|
||||
onMoved: media.seek(value)
|
||||
onValueChanged: updatePositionTexts()
|
||||
palette: colors
|
||||
}
|
||||
|
||||
Text {
|
||||
id: durationText
|
||||
|
||||
text: "--:--:--"
|
||||
color: colors.text
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
width: parent.width
|
||||
|
||||
spacing: 15
|
||||
|
||||
Rectangle {
|
||||
id: button
|
||||
|
||||
color: colors.window
|
||||
radius: 22
|
||||
height: 44
|
||||
width: 44
|
||||
Image {
|
||||
id: img
|
||||
anchors.centerIn: parent
|
||||
z: 3
|
||||
states: [
|
||||
State {
|
||||
name: "stopped"
|
||||
|
||||
source: "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?"+colors.text
|
||||
fillMode: Image.Pad
|
||||
PropertyChanges {
|
||||
target: img
|
||||
source: "image://colorimage/:/icons/icons/ui/play-sign.png?" + colors.text
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "playing"
|
||||
|
||||
PropertyChanges {
|
||||
target: img
|
||||
source: "image://colorimage/:/icons/icons/ui/pause-symbol.png?" + colors.text
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
Image {
|
||||
id: img
|
||||
|
||||
anchors.centerIn: parent
|
||||
z: 3
|
||||
source: "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?" + colors.text
|
||||
fillMode: Image.Pad
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
switch (button.state) {
|
||||
case "": TimelineManager.timeline.cacheMedia(model.data.id); break;
|
||||
case "":
|
||||
TimelineManager.timeline.cacheMedia(model.data.id);
|
||||
break;
|
||||
case "stopped":
|
||||
media.play(); console.log("play");
|
||||
button.state = "playing"
|
||||
break
|
||||
media.play();
|
||||
console.log("play");
|
||||
button.state = "playing";
|
||||
break;
|
||||
case "playing":
|
||||
media.pause(); console.log("pause");
|
||||
button.state = "stopped"
|
||||
break
|
||||
media.pause();
|
||||
console.log("pause");
|
||||
button.state = "stopped";
|
||||
break;
|
||||
}
|
||||
}
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
MediaPlayer {
|
||||
id: media
|
||||
|
||||
onError: console.log(errorString)
|
||||
onStatusChanged: if(status == MediaPlayer.Loaded) progress.updatePositionTexts()
|
||||
onStatusChanged: {
|
||||
if (status == MediaPlayer.Loaded)
|
||||
progress.updatePositionTexts();
|
||||
|
||||
}
|
||||
onStopped: button.state = "stopped"
|
||||
}
|
||||
|
||||
|
@ -130,25 +178,16 @@ Rectangle {
|
|||
target: TimelineManager.timeline
|
||||
onMediaCached: {
|
||||
if (mxcUrl == model.data.url) {
|
||||
media.source = "file://" + cacheUrl
|
||||
button.state = "stopped"
|
||||
console.log("media loaded: " + mxcUrl + " at " + cacheUrl)
|
||||
media.source = "file://" + cacheUrl;
|
||||
button.state = "stopped";
|
||||
console.log("media loaded: " + mxcUrl + " at " + cacheUrl);
|
||||
}
|
||||
console.log("media cached: " + mxcUrl + " at " + cacheUrl)
|
||||
console.log("media cached: " + mxcUrl + " at " + cacheUrl);
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "stopped"
|
||||
PropertyChanges { target: img; source: "image://colorimage/:/icons/icons/ui/play-sign.png?"+colors.text }
|
||||
},
|
||||
State {
|
||||
name: "playing"
|
||||
PropertyChanges { target: img; source: "image://colorimage/:/icons/icons/ui/pause-symbol.png?"+colors.text }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: col
|
||||
|
||||
|
@ -159,6 +198,7 @@ Rectangle {
|
|||
elide: Text.ElideRight
|
||||
color: colors.text
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: model.data.filesize
|
||||
|
@ -166,8 +206,11 @@ Rectangle {
|
|||
elide: Text.ElideRight
|
||||
color: colors.text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import QtQuick 2.6
|
|||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import im.nheko 1.0
|
||||
|
||||
Item {
|
||||
|
@ -27,18 +26,19 @@ Item {
|
|||
anchors.top: replyContainer.top
|
||||
anchors.bottom: replyContainer.bottom
|
||||
width: 4
|
||||
|
||||
color: TimelineManager.userColor(reply.modelData.userId, colors.window)
|
||||
}
|
||||
|
||||
Column {
|
||||
id: replyContainer
|
||||
|
||||
anchors.left: colorLine.right
|
||||
anchors.leftMargin: 4
|
||||
width: parent.width - 8
|
||||
|
||||
Text {
|
||||
id: userName
|
||||
|
||||
text: TimelineManager.escapeEmoji(reply.modelData.userName)
|
||||
color: replyComponent.userColor
|
||||
textFormat: Text.RichText
|
||||
|
@ -48,20 +48,25 @@ Item {
|
|||
onClicked: chat.model.openUserProfile(reply.modelData.userId)
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MessageDelegate {
|
||||
id: reply
|
||||
|
||||
width: parent.width
|
||||
isReply: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: backgroundItem
|
||||
|
||||
z: -1
|
||||
height: replyContainer.height
|
||||
width: Math.min(Math.max(reply.implicitWidth, userName.implicitWidth) + 8 + 4, parent.width)
|
||||
color: Qt.rgba(userColor.r, userColor.g, userColor.b, 0.2)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import ".."
|
||||
|
||||
import im.nheko 1.0
|
||||
|
||||
MatrixText {
|
||||
property string formatted: model.data.formattedBody
|
||||
|
||||
text: "<style type=\"text/css\">a { color:" + colors.link + ";}</style>" + formatted.replace("<pre>", "<pre style='white-space: pre-wrap'>")
|
||||
width: parent ? parent.width : undefined
|
||||
height: isReply ? Math.round(Math.min(timelineRoot.height / 8, implicitHeight)) : undefined
|
||||
|
|
|
@ -1,39 +1,46 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.10
|
||||
|
||||
import im.nheko 1.0
|
||||
|
||||
Pane {
|
||||
property string title: qsTr("Awaiting Confirmation")
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 16
|
||||
|
||||
Label {
|
||||
id: content
|
||||
|
||||
Layout.maximumWidth: 400
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
id: content
|
||||
text: qsTr("Waiting for other side to complete verification.")
|
||||
color: colors.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: qsTr("Cancel")
|
||||
|
||||
onClicked: {
|
||||
flow.cancel();
|
||||
dialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,26 +1,23 @@
|
|||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Window 2.10
|
||||
|
||||
import im.nheko 1.0
|
||||
|
||||
ApplicationWindow {
|
||||
id: dialog
|
||||
|
||||
property var flow
|
||||
|
||||
onClosing: TimelineManager.removeVerificationFlow(flow)
|
||||
|
||||
title: stack.currentItem.title
|
||||
id: dialog
|
||||
|
||||
flags: Qt.Dialog
|
||||
|
||||
palette: colors
|
||||
|
||||
height: stack.implicitHeight
|
||||
width: stack.implicitWidth
|
||||
|
||||
StackView {
|
||||
id: stack
|
||||
|
||||
initialItem: newVerificationRequest
|
||||
implicitWidth: currentItem.implicitWidth
|
||||
implicitHeight: currentItem.implicitHeight
|
||||
|
@ -28,70 +25,120 @@ ApplicationWindow {
|
|||
|
||||
Component {
|
||||
id: newVerificationRequest
|
||||
NewVerificationRequest {}
|
||||
|
||||
NewVerificationRequest {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: waiting
|
||||
Waiting {}
|
||||
|
||||
Waiting {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: success
|
||||
Success {}
|
||||
|
||||
Success {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: failed
|
||||
Failed {}
|
||||
|
||||
Failed {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: digitVerification
|
||||
DigitVerification {}
|
||||
|
||||
DigitVerification {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: emojiVerification
|
||||
EmojiVerification {}
|
||||
|
||||
EmojiVerification {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
state: flow.state
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "PromptStartVerification"
|
||||
StateChangeScript { script: stack.replace(newVerificationRequest) }
|
||||
|
||||
StateChangeScript {
|
||||
script: stack.replace(newVerificationRequest)
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "CompareEmoji"
|
||||
StateChangeScript { script: stack.replace(emojiVerification) }
|
||||
|
||||
StateChangeScript {
|
||||
script: stack.replace(emojiVerification)
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "CompareNumber"
|
||||
StateChangeScript { script: stack.replace(digitVerification) }
|
||||
|
||||
StateChangeScript {
|
||||
script: stack.replace(digitVerification)
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "WaitingForKeys"
|
||||
StateChangeScript { script: stack.replace(waiting) }
|
||||
|
||||
StateChangeScript {
|
||||
script: stack.replace(waiting)
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "WaitingForOtherToAccept"
|
||||
StateChangeScript { script: stack.replace(waiting) }
|
||||
|
||||
StateChangeScript {
|
||||
script: stack.replace(waiting)
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "WaitingForMac"
|
||||
StateChangeScript { script: stack.replace(waiting) }
|
||||
|
||||
StateChangeScript {
|
||||
script: stack.replace(waiting)
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "Success"
|
||||
StateChangeScript { script: stack.replace(success) }
|
||||
|
||||
StateChangeScript {
|
||||
script: stack.replace(success)
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "Failed"
|
||||
StateChangeScript { script: stack.replace(failed); }
|
||||
|
||||
StateChangeScript {
|
||||
script: stack.replace(failed)
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.10
|
||||
|
||||
import im.nheko 1.0
|
||||
|
||||
Pane {
|
||||
|
@ -9,6 +8,7 @@ Pane {
|
|||
|
||||
ColumnLayout {
|
||||
spacing: 16
|
||||
|
||||
Label {
|
||||
Layout.maximumWidth: 400
|
||||
Layout.fillHeight: true
|
||||
|
@ -18,43 +18,52 @@ Pane {
|
|||
color: colors.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
Label {
|
||||
font.pixelSize: Qt.application.font.pixelSize * 2
|
||||
text: flow.sasList[0]
|
||||
color: colors.text
|
||||
}
|
||||
|
||||
Label {
|
||||
font.pixelSize: Qt.application.font.pixelSize * 2
|
||||
text: flow.sasList[1]
|
||||
color: colors.text
|
||||
}
|
||||
|
||||
Label {
|
||||
font.pixelSize: Qt.application.font.pixelSize * 2
|
||||
text: flow.sasList[2]
|
||||
color: colors.text
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: qsTr("They do not match!")
|
||||
|
||||
onClicked: {
|
||||
flow.cancel();
|
||||
dialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: qsTr("They match!")
|
||||
onClicked: flow.next()
|
||||
}
|
||||
|
||||
onClicked: flow.next();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,19 +8,26 @@ Rectangle {
|
|||
implicitWidth: col.width
|
||||
height: Qt.application.font.pixelSize * 4
|
||||
width: col.width
|
||||
|
||||
ColumnLayout {
|
||||
id: col
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
property var emoji: emojis.mapping[Math.floor(Math.random() * 64)]
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
Label {
|
||||
height: font.pixelSize * 2
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: col.emoji.emoji
|
||||
font.pixelSize: Qt.application.font.pixelSize * 2
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
||||
text: col.emoji.description
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.10
|
||||
|
||||
import im.nheko 1.0
|
||||
|
||||
Pane {
|
||||
|
@ -9,6 +8,7 @@ Pane {
|
|||
|
||||
ColumnLayout {
|
||||
spacing: 16
|
||||
|
||||
Label {
|
||||
Layout.maximumWidth: 400
|
||||
Layout.fillHeight: true
|
||||
|
@ -18,87 +18,352 @@ Pane {
|
|||
color: colors.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
id: emojis
|
||||
property var mapping: [
|
||||
{"number": 0, "emoji": "🐶", "description": "Dog", "unicode": "U+1F436"},
|
||||
{"number": 1, "emoji": "🐱", "description": "Cat", "unicode": "U+1F431"},
|
||||
{"number": 2, "emoji": "🦁", "description": "Lion", "unicode": "U+1F981"},
|
||||
{"number": 3, "emoji": "🐎", "description": "Horse", "unicode": "U+1F40E"},
|
||||
{"number": 4, "emoji": "🦄", "description": "Unicorn", "unicode": "U+1F984"},
|
||||
{"number": 5, "emoji": "🐷", "description": "Pig", "unicode": "U+1F437"},
|
||||
{"number": 6, "emoji": "🐘", "description": "Elephant", "unicode": "U+1F418"},
|
||||
{"number": 7, "emoji": "🐰", "description": "Rabbit", "unicode": "U+1F430"},
|
||||
{"number": 8, "emoji": "🐼", "description": "Panda", "unicode": "U+1F43C"},
|
||||
{"number": 9, "emoji": "🐓", "description": "Rooster", "unicode": "U+1F413"},
|
||||
{"number": 10, "emoji": "🐧", "description": "Penguin", "unicode": "U+1F427"},
|
||||
{"number": 11, "emoji": "🐢", "description": "Turtle", "unicode": "U+1F422"},
|
||||
{"number": 12, "emoji": "🐟", "description": "Fish", "unicode": "U+1F41F"},
|
||||
{"number": 13, "emoji": "🐙", "description": "Octopus", "unicode": "U+1F419"},
|
||||
{"number": 14, "emoji": "🦋", "description": "Butterfly", "unicode": "U+1F98B"},
|
||||
{"number": 15, "emoji": "🌷", "description": "Flower", "unicode": "U+1F337"},
|
||||
{"number": 16, "emoji": "🌳", "description": "Tree", "unicode": "U+1F333"},
|
||||
{"number": 17, "emoji": "🌵", "description": "Cactus", "unicode": "U+1F335"},
|
||||
{"number": 18, "emoji": "🍄", "description": "Mushroom", "unicode": "U+1F344"},
|
||||
{"number": 19, "emoji": "🌏", "description": "Globe", "unicode": "U+1F30F"},
|
||||
{"number": 20, "emoji": "🌙", "description": "Moon", "unicode": "U+1F319"},
|
||||
{"number": 21, "emoji": "☁️", "description": "Cloud", "unicode": "U+2601U+FE0F"},
|
||||
{"number": 22, "emoji": "🔥", "description": "Fire", "unicode": "U+1F525"},
|
||||
{"number": 23, "emoji": "🍌", "description": "Banana", "unicode": "U+1F34C"},
|
||||
{"number": 24, "emoji": "🍎", "description": "Apple", "unicode": "U+1F34E"},
|
||||
{"number": 25, "emoji": "🍓", "description": "Strawberry", "unicode": "U+1F353"},
|
||||
{"number": 26, "emoji": "🌽", "description": "Corn", "unicode": "U+1F33D"},
|
||||
{"number": 27, "emoji": "🍕", "description": "Pizza", "unicode": "U+1F355"},
|
||||
{"number": 28, "emoji": "🎂", "description": "Cake", "unicode": "U+1F382"},
|
||||
{"number": 29, "emoji": "❤️", "description": "Heart", "unicode": "U+2764U+FE0F"},
|
||||
{"number": 30, "emoji": "😀", "description": "Smiley", "unicode": "U+1F600"},
|
||||
{"number": 31, "emoji": "🤖", "description": "Robot", "unicode": "U+1F916"},
|
||||
{"number": 32, "emoji": "🎩", "description": "Hat", "unicode": "U+1F3A9"},
|
||||
{"number": 33, "emoji": "👓", "description": "Glasses", "unicode": "U+1F453"},
|
||||
{"number": 34, "emoji": "🔧", "description": "Spanner", "unicode": "U+1F527"},
|
||||
{"number": 35, "emoji": "🎅", "description": "Santa", "unicode": "U+1F385"},
|
||||
{"number": 36, "emoji": "👍", "description": "Thumbs Up", "unicode": "U+1F44D"},
|
||||
{"number": 37, "emoji": "☂️", "description": "Umbrella", "unicode": "U+2602U+FE0F"},
|
||||
{"number": 38, "emoji": "⌛", "description": "Hourglass", "unicode": "U+231B"},
|
||||
{"number": 39, "emoji": "⏰", "description": "Clock", "unicode": "U+23F0"},
|
||||
{"number": 40, "emoji": "🎁", "description": "Gift", "unicode": "U+1F381"},
|
||||
{"number": 41, "emoji": "💡", "description": "Light Bulb", "unicode": "U+1F4A1"},
|
||||
{"number": 42, "emoji": "📕", "description": "Book", "unicode": "U+1F4D5"},
|
||||
{"number": 43, "emoji": "✏️", "description": "Pencil", "unicode": "U+270FU+FE0F"},
|
||||
{"number": 44, "emoji": "📎", "description": "Paperclip", "unicode": "U+1F4CE"},
|
||||
{"number": 45, "emoji": "✂️", "description": "Scissors", "unicode": "U+2702U+FE0F"},
|
||||
{"number": 46, "emoji": "🔒", "description": "Lock", "unicode": "U+1F512"},
|
||||
{"number": 47, "emoji": "🔑", "description": "Key", "unicode": "U+1F511"},
|
||||
{"number": 48, "emoji": "🔨", "description": "Hammer", "unicode": "U+1F528"},
|
||||
{"number": 49, "emoji": "☎️", "description": "Telephone", "unicode": "U+260EU+FE0F"},
|
||||
{"number": 50, "emoji": "🏁", "description": "Flag", "unicode": "U+1F3C1"},
|
||||
{"number": 51, "emoji": "🚂", "description": "Train", "unicode": "U+1F682"},
|
||||
{"number": 52, "emoji": "🚲", "description": "Bicycle", "unicode": "U+1F6B2"},
|
||||
{"number": 53, "emoji": "✈️", "description": "Aeroplane", "unicode": "U+2708U+FE0F"},
|
||||
{"number": 54, "emoji": "🚀", "description": "Rocket", "unicode": "U+1F680"},
|
||||
{"number": 55, "emoji": "🏆", "description": "Trophy", "unicode": "U+1F3C6"},
|
||||
{"number": 56, "emoji": "⚽", "description": "Ball", "unicode": "U+26BD"},
|
||||
{"number": 57, "emoji": "🎸", "description": "Guitar", "unicode": "U+1F3B8"},
|
||||
{"number": 58, "emoji": "🎺", "description": "Trumpet", "unicode": "U+1F3BA"},
|
||||
{"number": 59, "emoji": "🔔", "description": "Bell", "unicode": "U+1F514"},
|
||||
{"number": 60, "emoji": "⚓", "description": "Anchor", "unicode": "U+2693"},
|
||||
{"number": 61, "emoji": "🎧", "description": "Headphones", "unicode": "U+1F3A7"},
|
||||
{"number": 62, "emoji": "📁", "description": "Folder", "unicode": "U+1F4C1"},
|
||||
{"number": 63, "emoji": "📌", "description": "Pin", "unicode": "U+1F4CC"}
|
||||
]
|
||||
|
||||
property var mapping: [{
|
||||
"number": 0,
|
||||
"emoji": "🐶",
|
||||
"description": "Dog",
|
||||
"unicode": "U+1F436"
|
||||
}, {
|
||||
"number": 1,
|
||||
"emoji": "🐱",
|
||||
"description": "Cat",
|
||||
"unicode": "U+1F431"
|
||||
}, {
|
||||
"number": 2,
|
||||
"emoji": "🦁",
|
||||
"description": "Lion",
|
||||
"unicode": "U+1F981"
|
||||
}, {
|
||||
"number": 3,
|
||||
"emoji": "🐎",
|
||||
"description": "Horse",
|
||||
"unicode": "U+1F40E"
|
||||
}, {
|
||||
"number": 4,
|
||||
"emoji": "🦄",
|
||||
"description": "Unicorn",
|
||||
"unicode": "U+1F984"
|
||||
}, {
|
||||
"number": 5,
|
||||
"emoji": "🐷",
|
||||
"description": "Pig",
|
||||
"unicode": "U+1F437"
|
||||
}, {
|
||||
"number": 6,
|
||||
"emoji": "🐘",
|
||||
"description": "Elephant",
|
||||
"unicode": "U+1F418"
|
||||
}, {
|
||||
"number": 7,
|
||||
"emoji": "🐰",
|
||||
"description": "Rabbit",
|
||||
"unicode": "U+1F430"
|
||||
}, {
|
||||
"number": 8,
|
||||
"emoji": "🐼",
|
||||
"description": "Panda",
|
||||
"unicode": "U+1F43C"
|
||||
}, {
|
||||
"number": 9,
|
||||
"emoji": "🐓",
|
||||
"description": "Rooster",
|
||||
"unicode": "U+1F413"
|
||||
}, {
|
||||
"number": 10,
|
||||
"emoji": "🐧",
|
||||
"description": "Penguin",
|
||||
"unicode": "U+1F427"
|
||||
}, {
|
||||
"number": 11,
|
||||
"emoji": "🐢",
|
||||
"description": "Turtle",
|
||||
"unicode": "U+1F422"
|
||||
}, {
|
||||
"number": 12,
|
||||
"emoji": "🐟",
|
||||
"description": "Fish",
|
||||
"unicode": "U+1F41F"
|
||||
}, {
|
||||
"number": 13,
|
||||
"emoji": "🐙",
|
||||
"description": "Octopus",
|
||||
"unicode": "U+1F419"
|
||||
}, {
|
||||
"number": 14,
|
||||
"emoji": "🦋",
|
||||
"description": "Butterfly",
|
||||
"unicode": "U+1F98B"
|
||||
}, {
|
||||
"number": 15,
|
||||
"emoji": "🌷",
|
||||
"description": "Flower",
|
||||
"unicode": "U+1F337"
|
||||
}, {
|
||||
"number": 16,
|
||||
"emoji": "🌳",
|
||||
"description": "Tree",
|
||||
"unicode": "U+1F333"
|
||||
}, {
|
||||
"number": 17,
|
||||
"emoji": "🌵",
|
||||
"description": "Cactus",
|
||||
"unicode": "U+1F335"
|
||||
}, {
|
||||
"number": 18,
|
||||
"emoji": "🍄",
|
||||
"description": "Mushroom",
|
||||
"unicode": "U+1F344"
|
||||
}, {
|
||||
"number": 19,
|
||||
"emoji": "🌏",
|
||||
"description": "Globe",
|
||||
"unicode": "U+1F30F"
|
||||
}, {
|
||||
"number": 20,
|
||||
"emoji": "🌙",
|
||||
"description": "Moon",
|
||||
"unicode": "U+1F319"
|
||||
}, {
|
||||
"number": 21,
|
||||
"emoji": "☁️",
|
||||
"description": "Cloud",
|
||||
"unicode": "U+2601U+FE0F"
|
||||
}, {
|
||||
"number": 22,
|
||||
"emoji": "🔥",
|
||||
"description": "Fire",
|
||||
"unicode": "U+1F525"
|
||||
}, {
|
||||
"number": 23,
|
||||
"emoji": "🍌",
|
||||
"description": "Banana",
|
||||
"unicode": "U+1F34C"
|
||||
}, {
|
||||
"number": 24,
|
||||
"emoji": "🍎",
|
||||
"description": "Apple",
|
||||
"unicode": "U+1F34E"
|
||||
}, {
|
||||
"number": 25,
|
||||
"emoji": "🍓",
|
||||
"description": "Strawberry",
|
||||
"unicode": "U+1F353"
|
||||
}, {
|
||||
"number": 26,
|
||||
"emoji": "🌽",
|
||||
"description": "Corn",
|
||||
"unicode": "U+1F33D"
|
||||
}, {
|
||||
"number": 27,
|
||||
"emoji": "🍕",
|
||||
"description": "Pizza",
|
||||
"unicode": "U+1F355"
|
||||
}, {
|
||||
"number": 28,
|
||||
"emoji": "🎂",
|
||||
"description": "Cake",
|
||||
"unicode": "U+1F382"
|
||||
}, {
|
||||
"number": 29,
|
||||
"emoji": "❤️",
|
||||
"description": "Heart",
|
||||
"unicode": "U+2764U+FE0F"
|
||||
}, {
|
||||
"number": 30,
|
||||
"emoji": "😀",
|
||||
"description": "Smiley",
|
||||
"unicode": "U+1F600"
|
||||
}, {
|
||||
"number": 31,
|
||||
"emoji": "🤖",
|
||||
"description": "Robot",
|
||||
"unicode": "U+1F916"
|
||||
}, {
|
||||
"number": 32,
|
||||
"emoji": "🎩",
|
||||
"description": "Hat",
|
||||
"unicode": "U+1F3A9"
|
||||
}, {
|
||||
"number": 33,
|
||||
"emoji": "👓",
|
||||
"description": "Glasses",
|
||||
"unicode": "U+1F453"
|
||||
}, {
|
||||
"number": 34,
|
||||
"emoji": "🔧",
|
||||
"description": "Spanner",
|
||||
"unicode": "U+1F527"
|
||||
}, {
|
||||
"number": 35,
|
||||
"emoji": "🎅",
|
||||
"description": "Santa",
|
||||
"unicode": "U+1F385"
|
||||
}, {
|
||||
"number": 36,
|
||||
"emoji": "👍",
|
||||
"description": "Thumbs Up",
|
||||
"unicode": "U+1F44D"
|
||||
}, {
|
||||
"number": 37,
|
||||
"emoji": "☂️",
|
||||
"description": "Umbrella",
|
||||
"unicode": "U+2602U+FE0F"
|
||||
}, {
|
||||
"number": 38,
|
||||
"emoji": "⌛",
|
||||
"description": "Hourglass",
|
||||
"unicode": "U+231B"
|
||||
}, {
|
||||
"number": 39,
|
||||
"emoji": "⏰",
|
||||
"description": "Clock",
|
||||
"unicode": "U+23F0"
|
||||
}, {
|
||||
"number": 40,
|
||||
"emoji": "🎁",
|
||||
"description": "Gift",
|
||||
"unicode": "U+1F381"
|
||||
}, {
|
||||
"number": 41,
|
||||
"emoji": "💡",
|
||||
"description": "Light Bulb",
|
||||
"unicode": "U+1F4A1"
|
||||
}, {
|
||||
"number": 42,
|
||||
"emoji": "📕",
|
||||
"description": "Book",
|
||||
"unicode": "U+1F4D5"
|
||||
}, {
|
||||
"number": 43,
|
||||
"emoji": "✏️",
|
||||
"description": "Pencil",
|
||||
"unicode": "U+270FU+FE0F"
|
||||
}, {
|
||||
"number": 44,
|
||||
"emoji": "📎",
|
||||
"description": "Paperclip",
|
||||
"unicode": "U+1F4CE"
|
||||
}, {
|
||||
"number": 45,
|
||||
"emoji": "✂️",
|
||||
"description": "Scissors",
|
||||
"unicode": "U+2702U+FE0F"
|
||||
}, {
|
||||
"number": 46,
|
||||
"emoji": "🔒",
|
||||
"description": "Lock",
|
||||
"unicode": "U+1F512"
|
||||
}, {
|
||||
"number": 47,
|
||||
"emoji": "🔑",
|
||||
"description": "Key",
|
||||
"unicode": "U+1F511"
|
||||
}, {
|
||||
"number": 48,
|
||||
"emoji": "🔨",
|
||||
"description": "Hammer",
|
||||
"unicode": "U+1F528"
|
||||
}, {
|
||||
"number": 49,
|
||||
"emoji": "☎️",
|
||||
"description": "Telephone",
|
||||
"unicode": "U+260EU+FE0F"
|
||||
}, {
|
||||
"number": 50,
|
||||
"emoji": "🏁",
|
||||
"description": "Flag",
|
||||
"unicode": "U+1F3C1"
|
||||
}, {
|
||||
"number": 51,
|
||||
"emoji": "🚂",
|
||||
"description": "Train",
|
||||
"unicode": "U+1F682"
|
||||
}, {
|
||||
"number": 52,
|
||||
"emoji": "🚲",
|
||||
"description": "Bicycle",
|
||||
"unicode": "U+1F6B2"
|
||||
}, {
|
||||
"number": 53,
|
||||
"emoji": "✈️",
|
||||
"description": "Aeroplane",
|
||||
"unicode": "U+2708U+FE0F"
|
||||
}, {
|
||||
"number": 54,
|
||||
"emoji": "🚀",
|
||||
"description": "Rocket",
|
||||
"unicode": "U+1F680"
|
||||
}, {
|
||||
"number": 55,
|
||||
"emoji": "🏆",
|
||||
"description": "Trophy",
|
||||
"unicode": "U+1F3C6"
|
||||
}, {
|
||||
"number": 56,
|
||||
"emoji": "⚽",
|
||||
"description": "Ball",
|
||||
"unicode": "U+26BD"
|
||||
}, {
|
||||
"number": 57,
|
||||
"emoji": "🎸",
|
||||
"description": "Guitar",
|
||||
"unicode": "U+1F3B8"
|
||||
}, {
|
||||
"number": 58,
|
||||
"emoji": "🎺",
|
||||
"description": "Trumpet",
|
||||
"unicode": "U+1F3BA"
|
||||
}, {
|
||||
"number": 59,
|
||||
"emoji": "🔔",
|
||||
"description": "Bell",
|
||||
"unicode": "U+1F514"
|
||||
}, {
|
||||
"number": 60,
|
||||
"emoji": "⚓",
|
||||
"description": "Anchor",
|
||||
"unicode": "U+2693"
|
||||
}, {
|
||||
"number": 61,
|
||||
"emoji": "🎧",
|
||||
"description": "Headphones",
|
||||
"unicode": "U+1F3A7"
|
||||
}, {
|
||||
"number": 62,
|
||||
"emoji": "📁",
|
||||
"description": "Folder",
|
||||
"unicode": "U+1F4C1"
|
||||
}, {
|
||||
"number": 63,
|
||||
"emoji": "📌",
|
||||
"description": "Pin",
|
||||
"unicode": "U+1F4CC"
|
||||
}]
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
|
||||
model: 7
|
||||
|
||||
delegate: Rectangle {
|
||||
color: "transparent"
|
||||
implicitHeight: Qt.application.font.pixelSize * 8
|
||||
implicitWidth: col.width
|
||||
|
||||
ColumnLayout {
|
||||
id: col
|
||||
|
||||
property var emoji: emojis.mapping[flow.sasList[index]]
|
||||
|
||||
Layout.fillWidth: true
|
||||
anchors.bottom: parent.bottom
|
||||
property var emoji: emojis.mapping[flow.sasList[index]]
|
||||
|
||||
Label {
|
||||
//height: font.pixelSize * 2
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
@ -107,34 +372,43 @@ Pane {
|
|||
font.family: Settings.emojiFont
|
||||
color: colors.text
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
||||
text: col.emoji.description
|
||||
color: colors.text
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: qsTr("They do not match!")
|
||||
|
||||
onClicked: {
|
||||
flow.cancel();
|
||||
dialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: qsTr("They match!")
|
||||
|
||||
onClicked: flow.next()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,44 +1,56 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.10
|
||||
|
||||
import im.nheko 1.0
|
||||
|
||||
Pane {
|
||||
property string title: qsTr("Verification failed")
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 16
|
||||
|
||||
Text {
|
||||
id: content
|
||||
|
||||
Layout.maximumWidth: 400
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
text: switch (flow.error) {
|
||||
case DeviceVerificationFlow.UnknownMethod: return qsTr("Other client does not support our verification protocol.")
|
||||
text: {
|
||||
switch (flow.error) {
|
||||
case DeviceVerificationFlow.UnknownMethod:
|
||||
return qsTr("Other client does not support our verification protocol.");
|
||||
case DeviceVerificationFlow.MismatchedCommitment:
|
||||
case DeviceVerificationFlow.MismatchedSAS:
|
||||
case DeviceVerificationFlow.KeyMismatch: return qsTr("Key mismatch detected!")
|
||||
case DeviceVerificationFlow.Timeout: return qsTr("Device verification timed out.")
|
||||
case DeviceVerificationFlow.User: return qsTr("Other party canceled the verification.")
|
||||
case DeviceVerificationFlow.OutOfOrder: return qsTr("Device verification timed out.")
|
||||
default: return "Unknown verification error.";
|
||||
case DeviceVerificationFlow.KeyMismatch:
|
||||
return qsTr("Key mismatch detected!");
|
||||
case DeviceVerificationFlow.Timeout:
|
||||
return qsTr("Device verification timed out.");
|
||||
case DeviceVerificationFlow.User:
|
||||
return qsTr("Other party canceled the verification.");
|
||||
case DeviceVerificationFlow.OutOfOrder:
|
||||
return qsTr("Device verification timed out.");
|
||||
default:
|
||||
return "Unknown verification error.";
|
||||
}
|
||||
}
|
||||
color: colors.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: qsTr("Close")
|
||||
|
||||
onClicked: dialog.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.10
|
||||
|
||||
import im.nheko 1.0
|
||||
|
||||
Pane {
|
||||
|
@ -9,36 +8,39 @@ Pane {
|
|||
|
||||
ColumnLayout {
|
||||
spacing: 16
|
||||
|
||||
Label {
|
||||
Layout.maximumWidth: 400
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
text: flow.sender ?
|
||||
qsTr("To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device.")
|
||||
: qsTr("The device was requested to be verified")
|
||||
text: flow.sender ? qsTr("To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device.") : qsTr("The device was requested to be verified")
|
||||
color: colors.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: flow.sender ? qsTr("Cancel") : qsTr("Deny")
|
||||
|
||||
onClicked: {
|
||||
flow.cancel();
|
||||
dialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: flow.sender ? qsTr("Start verification") : qsTr("Accept")
|
||||
onClicked: flow.next()
|
||||
}
|
||||
|
||||
onClicked: flow.next();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,28 +4,35 @@ import QtQuick.Layouts 1.10
|
|||
|
||||
Pane {
|
||||
property string title: qsTr("Successful Verification")
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 16
|
||||
|
||||
Label {
|
||||
id: content
|
||||
|
||||
Layout.maximumWidth: 400
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
id: content
|
||||
text: qsTr("Verification successful! Both sides verified their devices!")
|
||||
color: colors.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: qsTr("Close")
|
||||
onClicked: dialog.close()
|
||||
}
|
||||
|
||||
onClicked: dialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,45 +1,56 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.10
|
||||
|
||||
import im.nheko 1.0
|
||||
|
||||
Pane {
|
||||
property string title: qsTr("Waiting for other party")
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 16
|
||||
|
||||
Label {
|
||||
id: content
|
||||
|
||||
Layout.maximumWidth: 400
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
id: content
|
||||
text: switch (flow.state) {
|
||||
case "WaitingForOtherToAccept": return qsTr("Waiting for other side to accept the verification request.")
|
||||
case "WaitingForKeys": return qsTr("Waiting for other side to continue the verification request.")
|
||||
case "WaitingForMac": return qsTr("Waiting for other side to complete the verification request.")
|
||||
text: {
|
||||
switch (flow.state) {
|
||||
case "WaitingForOtherToAccept":
|
||||
return qsTr("Waiting for other side to accept the verification request.");
|
||||
case "WaitingForKeys":
|
||||
return qsTr("Waiting for other side to continue the verification request.");
|
||||
case "WaitingForMac":
|
||||
return qsTr("Waiting for other side to complete the verification request.");
|
||||
}
|
||||
}
|
||||
|
||||
color: colors.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
palette: colors
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: qsTr("Cancel")
|
||||
|
||||
onClicked: {
|
||||
flow.cancel();
|
||||
dialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
import "../"
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.1
|
||||
import im.nheko 1.0
|
||||
import im.nheko.EmojiModel 1.0
|
||||
|
||||
import "../"
|
||||
|
||||
ImageButton {
|
||||
id: emojiButton
|
||||
|
||||
property var colors: currentActivePalette
|
||||
property var emojiPicker
|
||||
property string event_id
|
||||
|
||||
image: ":/icons/icons/ui/smile.png"
|
||||
id: emojiButton
|
||||
onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.show(emojiButton, event_id)
|
||||
|
||||
}
|
||||
|
|
|
@ -1,25 +1,13 @@
|
|||
import "../"
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import im.nheko 1.0
|
||||
import im.nheko.EmojiModel 1.0
|
||||
|
||||
import "../"
|
||||
|
||||
Popup {
|
||||
|
||||
function show(showAt, event_id) {
|
||||
console.debug("Showing emojiPicker for " + event_id)
|
||||
if (showAt){
|
||||
parent = showAt
|
||||
x = Math.round((showAt.width - width) / 2)
|
||||
y = showAt.height
|
||||
}
|
||||
emojiPopup.event_id = event_id
|
||||
open()
|
||||
}
|
||||
id: emojiPopup
|
||||
|
||||
property string event_id
|
||||
property var colors
|
||||
|
@ -30,19 +18,28 @@ Popup {
|
|||
property real highlightSat: colors.highlight.hslSaturation
|
||||
property real highlightLight: colors.highlight.hslLightness
|
||||
|
||||
id: emojiPopup
|
||||
function show(showAt, event_id) {
|
||||
console.debug("Showing emojiPicker for " + event_id);
|
||||
if (showAt) {
|
||||
parent = showAt;
|
||||
x = Math.round((showAt.width - width) / 2);
|
||||
y = showAt.height;
|
||||
}
|
||||
emojiPopup.event_id = event_id;
|
||||
open();
|
||||
}
|
||||
|
||||
margins: 0
|
||||
bottomPadding: 1
|
||||
leftPadding: 1
|
||||
rightPadding: 1
|
||||
|
||||
modal: true
|
||||
focus: true
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
|
||||
ColumnLayout {
|
||||
id: columnView
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
Layout.bottomMargin: 0
|
||||
|
@ -58,23 +55,41 @@ Popup {
|
|||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.leftMargin: 4
|
||||
|
||||
cellWidth: 52
|
||||
cellHeight: 52
|
||||
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
clip: true
|
||||
|
||||
// Individual emoji
|
||||
delegate: AbstractButton {
|
||||
width: 48
|
||||
height: 48
|
||||
hoverEnabled: true
|
||||
ToolTip.text: model.shortName
|
||||
ToolTip.visible: hovered
|
||||
// TODO: maybe add favorites at some point?
|
||||
onClicked: {
|
||||
console.debug("Picked " + model.unicode + "in response to " + emojiPopup.event_id);
|
||||
emojiPopup.close();
|
||||
TimelineManager.queueReactionMessage(emojiPopup.event_id, model.unicode);
|
||||
}
|
||||
|
||||
// give the emoji a little oomf
|
||||
DropShadow {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
horizontalOffset: 3
|
||||
verticalOffset: 3
|
||||
radius: 8
|
||||
samples: 17
|
||||
color: "#80000000"
|
||||
source: parent.contentItem
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.family: Settings.emojiFont
|
||||
|
||||
font.pixelSize: 36
|
||||
text: model.unicode
|
||||
}
|
||||
|
@ -85,76 +100,66 @@ Popup {
|
|||
radius: 5
|
||||
}
|
||||
|
||||
hoverEnabled: true
|
||||
ToolTip.text: model.shortName
|
||||
ToolTip.visible: hovered
|
||||
|
||||
// give the emoji a little oomf
|
||||
DropShadow {
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
horizontalOffset: 3
|
||||
verticalOffset: 3
|
||||
radius: 8.0
|
||||
samples: 17
|
||||
color: "#80000000"
|
||||
source: parent.contentItem
|
||||
}
|
||||
// TODO: maybe add favorites at some point?
|
||||
onClicked: {
|
||||
console.debug("Picked " + model.unicode + "in response to " + emojiPopup.event_id)
|
||||
emojiPopup.close()
|
||||
TimelineManager.queueReactionMessage(emojiPopup.event_id, model.unicode)
|
||||
}
|
||||
}
|
||||
|
||||
// Search field
|
||||
header: TextField {
|
||||
id: emojiSearch
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: emojiScroll.width + 4
|
||||
placeholderText: qsTr("Search")
|
||||
selectByMouse: true
|
||||
rightPadding: clearSearch.width
|
||||
onTextChanged: searchTimer.restart()
|
||||
onVisibleChanged: {
|
||||
if (visible)
|
||||
forceActiveFocus();
|
||||
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: searchTimer
|
||||
|
||||
interval: 350 // tweak as needed?
|
||||
onTriggered: {
|
||||
emojiPopup.model.filter = emojiSearch.text
|
||||
emojiPopup.model.category = EmojiCategory.Search
|
||||
emojiPopup.model.filter = emojiSearch.text;
|
||||
emojiPopup.model.category = EmojiCategory.Search;
|
||||
}
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: clearSearch
|
||||
|
||||
visible: emojiSearch.text !== ''
|
||||
icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.png?" + (clearSearch.hovered ? colors.highlight : colors.buttonText)
|
||||
focusPolicy: Qt.NoFocus
|
||||
onClicked: emojiSearch.clear()
|
||||
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
right: parent.right
|
||||
}
|
||||
// clear the default hover effects.
|
||||
background: Item {}
|
||||
visible: emojiSearch.text !== ''
|
||||
icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.png?" + (clearSearch.hovered ? colors.highlight : colors.buttonText)
|
||||
focusPolicy: Qt.NoFocus
|
||||
onClicked: emojiSearch.clear()
|
||||
|
||||
background: Item {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onTextChanged: searchTimer.restart()
|
||||
onVisibleChanged: if (visible) forceActiveFocus()
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
id: emojiScroll
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Separator
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
|
||||
color: emojiPopup.colors.dark
|
||||
}
|
||||
|
||||
|
@ -164,49 +169,57 @@ Popup {
|
|||
Layout.preferredHeight: 42
|
||||
implicitHeight: 42
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
||||
|
||||
// Display the normal categories
|
||||
Repeater {
|
||||
|
||||
model: ListModel {
|
||||
// TODO: Would like to get 'simple' icons for the categories
|
||||
ListElement { image: ":/icons/icons/emoji-categories/people.png"; category: EmojiCategory.People }
|
||||
ListElement { image: ":/icons/icons/emoji-categories/nature.png"; category: EmojiCategory.Nature }
|
||||
ListElement { image: ":/icons/icons/emoji-categories/foods.png"; category: EmojiCategory.Food }
|
||||
ListElement { image: ":/icons/icons/emoji-categories/activity.png"; category: EmojiCategory.Activity }
|
||||
ListElement { image: ":/icons/icons/emoji-categories/travel.png"; category: EmojiCategory.Travel }
|
||||
ListElement { image: ":/icons/icons/emoji-categories/objects.png"; category: EmojiCategory.Objects }
|
||||
ListElement { image: ":/icons/icons/emoji-categories/symbols.png"; category: EmojiCategory.Symbols }
|
||||
ListElement { image: ":/icons/icons/emoji-categories/flags.png"; category: EmojiCategory.Flags }
|
||||
ListElement {
|
||||
image: ":/icons/icons/emoji-categories/people.png"
|
||||
category: EmojiCategory.People
|
||||
}
|
||||
|
||||
ListElement {
|
||||
image: ":/icons/icons/emoji-categories/nature.png"
|
||||
category: EmojiCategory.Nature
|
||||
}
|
||||
|
||||
ListElement {
|
||||
image: ":/icons/icons/emoji-categories/foods.png"
|
||||
category: EmojiCategory.Food
|
||||
}
|
||||
|
||||
ListElement {
|
||||
image: ":/icons/icons/emoji-categories/activity.png"
|
||||
category: EmojiCategory.Activity
|
||||
}
|
||||
|
||||
ListElement {
|
||||
image: ":/icons/icons/emoji-categories/travel.png"
|
||||
category: EmojiCategory.Travel
|
||||
}
|
||||
|
||||
ListElement {
|
||||
image: ":/icons/icons/emoji-categories/objects.png"
|
||||
category: EmojiCategory.Objects
|
||||
}
|
||||
|
||||
ListElement {
|
||||
image: ":/icons/icons/emoji-categories/symbols.png"
|
||||
category: EmojiCategory.Symbols
|
||||
}
|
||||
|
||||
ListElement {
|
||||
image: ":/icons/icons/emoji-categories/flags.png"
|
||||
category: EmojiCategory.Flags
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
delegate: AbstractButton {
|
||||
Layout.preferredWidth: 36
|
||||
Layout.preferredHeight: 36
|
||||
|
||||
contentItem: Image {
|
||||
horizontalAlignment: Image.AlignHCenter
|
||||
verticalAlignment: Image.AlignVCenter
|
||||
fillMode: Image.Pad
|
||||
sourceSize.width: 32
|
||||
sourceSize.height: 32
|
||||
source: "image://colorimage/" + model.image + "?" + (hovered ? colors.highlight : colors.buttonText)
|
||||
}
|
||||
|
||||
MouseArea
|
||||
{
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
onPressed: mouse.accepted = false
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
|
||||
color: emojiPopup.model.category === model.category ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.20) : 'transparent'
|
||||
radius: 5
|
||||
border.color: emojiPopup.model.category === model.category ? colors.highlight : 'transparent'
|
||||
}
|
||||
|
||||
hoverEnabled: true
|
||||
ToolTip.text: {
|
||||
switch (model.category) {
|
||||
|
@ -229,11 +242,36 @@ Popup {
|
|||
}
|
||||
}
|
||||
ToolTip.visible: hovered
|
||||
|
||||
onClicked: {
|
||||
emojiPopup.model.category = model.category
|
||||
emojiPopup.model.category = model.category;
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
onPressed: mouse.accepted = false
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
contentItem: Image {
|
||||
horizontalAlignment: Image.AlignHCenter
|
||||
verticalAlignment: Image.AlignVCenter
|
||||
fillMode: Image.Pad
|
||||
sourceSize.width: 32
|
||||
sourceSize.height: 32
|
||||
source: "image://colorimage/" + model.image + "?" + (hovered ? colors.highlight : colors.buttonText)
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
color: emojiPopup.model.category === model.category ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.2) : 'transparent'
|
||||
radius: 5
|
||||
border.color: emojiPopup.model.category === model.category ? colors.highlight : 'transparent'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Separator
|
||||
|
@ -242,30 +280,37 @@ Popup {
|
|||
Layout.preferredWidth: 1
|
||||
implicitWidth: 1
|
||||
height: parent.height
|
||||
|
||||
color: emojiPopup.colors.dark
|
||||
}
|
||||
|
||||
// Search Button is special
|
||||
AbstractButton {
|
||||
id: searchBtn
|
||||
|
||||
hoverEnabled: true
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.bottomMargin: 0
|
||||
|
||||
ToolTip.text: qsTr("Search")
|
||||
ToolTip.visible: hovered
|
||||
onClicked: {
|
||||
// clear any filters
|
||||
emojiPopup.model.category = EmojiCategory.Search
|
||||
gridView.positionViewAtBeginning()
|
||||
emojiSearch.forceActiveFocus()
|
||||
emojiPopup.model.category = EmojiCategory.Search;
|
||||
gridView.positionViewAtBeginning();
|
||||
emojiSearch.forceActiveFocus();
|
||||
}
|
||||
Layout.preferredWidth: 36
|
||||
Layout.preferredHeight: 36
|
||||
implicitWidth: 36
|
||||
implicitHeight: 36
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
onPressed: mouse.accepted = false
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
contentItem: Image {
|
||||
anchors.right: parent.right
|
||||
horizontalAlignment: Image.AlignHCenter
|
||||
|
@ -277,14 +322,10 @@ Popup {
|
|||
source: "image://colorimage/:/icons/icons/ui/search.png?" + (parent.hovered ? colors.highlight : colors.buttonText)
|
||||
}
|
||||
|
||||
MouseArea
|
||||
{
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
onPressed: mouse.accepted = false
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue