Lint qml with qml-format

master
Nicolas Werner 4 years ago
parent 517a126a44
commit 1a029112d9
  1. 210
      resources/qml/ActiveCallBar.qml
  2. 112
      resources/qml/Avatar.qml
  3. 73
      resources/qml/EncryptionIndicator.qml
  4. 46
      resources/qml/ImageButton.qml
  5. 56
      resources/qml/MatrixText.qml
  6. 173
      resources/qml/Reactions.qml
  7. 83
      resources/qml/ScrollHelper.qml
  8. 84
      resources/qml/StatusIndicator.qml
  9. 278
      resources/qml/TimelineRow.qml
  10. 1074
      resources/qml/TimelineView.qml
  11. 331
      resources/qml/UserProfile.qml
  12. 129
      resources/qml/delegates/FileMessage.qml
  13. 73
      resources/qml/delegates/ImageMessage.qml
  14. 541
      resources/qml/delegates/MessageDelegate.qml
  15. 8
      resources/qml/delegates/NoticeMessage.qml
  16. 17
      resources/qml/delegates/Pill.qml
  17. 6
      resources/qml/delegates/Placeholder.qml
  18. 377
      resources/qml/delegates/PlayableMediaMessage.qml
  19. 123
      resources/qml/delegates/Reply.qml
  20. 14
      resources/qml/delegates/TextMessage.qml
  21. 71
      resources/qml/device-verification/AwaitingVerificationConfirmation.qml
  22. 227
      resources/qml/device-verification/DeviceVerification.qml
  23. 115
      resources/qml/device-verification/DigitVerification.qml
  24. 47
      resources/qml/device-verification/EmojiElement.qml
  25. 540
      resources/qml/device-verification/EmojiVerification.qml
  26. 86
      resources/qml/device-verification/Failed.qml
  27. 76
      resources/qml/device-verification/NewVerificationRequest.qml
  28. 57
      resources/qml/device-verification/Success.qml
  29. 87
      resources/qml/device-verification/Waiting.qml
  30. 7
      resources/qml/emoji/EmojiButton.qml
  31. 263
      resources/qml/emoji/EmojiPicker.qml

@ -1,111 +1,113 @@
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import im.nheko 1.0 import im.nheko 1.0
Rectangle { Rectangle {
id: activeCallBar id: activeCallBar
visible: TimelineManager.callState != WebRTCState.DISCONNECTED
color: "#2ECC71" visible: TimelineManager.callState != WebRTCState.DISCONNECTED
implicitHeight: rowLayout.height + 8 color: "#2ECC71"
implicitHeight: rowLayout.height + 8
RowLayout {
id: rowLayout RowLayout {
anchors.left: parent.left id: rowLayout
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left
anchors.leftMargin: 8 anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
Avatar { anchors.leftMargin: 8
width: avatarSize
height: avatarSize Avatar {
width: avatarSize
url: TimelineManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") height: avatarSize
displayName: TimelineManager.callPartyName url: TimelineManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
} displayName: TimelineManager.callPartyName
}
Label {
font.pointSize: fontMetrics.font.pointSize * 1.1 Label {
text: " " + TimelineManager.callPartyName + " " font.pointSize: fontMetrics.font.pointSize * 1.1
} text: " " + TimelineManager.callPartyName + " "
}
Image {
Layout.preferredWidth: 24 Image {
Layout.preferredHeight: 24 Layout.preferredWidth: 24
source: "qrc:/icons/icons/ui/place-call.png" Layout.preferredHeight: 24
} source: "qrc:/icons/icons/ui/place-call.png"
}
Label {
id: callStateLabel Label {
font.pointSize: fontMetrics.font.pointSize * 1.1 id: callStateLabel
}
font.pointSize: fontMetrics.font.pointSize * 1.1
Connections { }
target: TimelineManager
function onCallStateChanged(state) { Connections {
switch (state) { function onCallStateChanged(state) {
case WebRTCState.INITIATING: switch (state) {
callStateLabel.text = qsTr("Initiating...") case WebRTCState.INITIATING:
break; callStateLabel.text = qsTr("Initiating...");
case WebRTCState.OFFERSENT: break;
callStateLabel.text = qsTr("Calling...") case WebRTCState.OFFERSENT:
break; callStateLabel.text = qsTr("Calling...");
case WebRTCState.CONNECTING: break;
callStateLabel.text = qsTr("Connecting...") case WebRTCState.CONNECTING:
break; callStateLabel.text = qsTr("Connecting...");
case WebRTCState.CONNECTED: break;
callStateLabel.text = "00:00" case WebRTCState.CONNECTED:
var d = new Date() callStateLabel.text = "00:00";
callTimer.startTime = Math.floor(d.getTime() / 1000) var d = new Date();
break; callTimer.startTime = Math.floor(d.getTime() / 1000);
case WebRTCState.DISCONNECTED: break;
callStateLabel.text = "" case WebRTCState.DISCONNECTED:
} callStateLabel.text = "";
} }
} }
Timer { target: TimelineManager
id: callTimer }
property int startTime
interval: 1000 Timer {
running: TimelineManager.callState == WebRTCState.CONNECTED id: callTimer
repeat: true
onTriggered: { property int startTime
var d = new Date()
let seconds = Math.floor(d.getTime() / 1000 - startTime) function pad(n) {
let s = Math.floor(seconds % 60) return (n < 10) ? ("0" + n) : n;
let m = Math.floor(seconds / 60) % 60 }
let h = Math.floor(seconds / 3600)
callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s) interval: 1000
} running: TimelineManager.callState == WebRTCState.CONNECTED
repeat: true
function pad(n) { onTriggered: {
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;
Item { let h = Math.floor(seconds / 3600);
Layout.fillWidth: true callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s);
} }
}
ImageButton {
width: 24 Item {
height: 24 Layout.fillWidth: true
buttonTextColor: "#000000" }
image: TimelineManager.isMicMuted ?
":/icons/icons/ui/microphone-unmute.png" : ImageButton {
":/icons/icons/ui/microphone-mute.png" width: 24
height: 24
hoverEnabled: true buttonTextColor: "#000000"
ToolTip.visible: hovered image: TimelineManager.isMicMuted ? ":/icons/icons/ui/microphone-unmute.png" : ":/icons/icons/ui/microphone-mute.png"
ToolTip.text: TimelineManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic") hoverEnabled: true
ToolTip.visible: hovered
onClicked: TimelineManager.toggleMicMute() ToolTip.text: TimelineManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic")
} onClicked: TimelineManager.toggleMicMute()
}
Item {
implicitWidth: 16 Item {
} implicitWidth: 16
} }
}
} }

@ -1,69 +1,75 @@
import QtGraphicalEffects 1.0
import QtQuick 2.6 import QtQuick 2.6
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtGraphicalEffects 1.0
import im.nheko 1.0 import im.nheko 1.0
Rectangle { Rectangle {
id: avatar id: avatar
width: 48
height: 48 property alias url: img.source
radius: Settings.avatarCircles ? height/2 : 3 property string userid
property string displayName
property alias url: img.source width: 48
property string userid height: 48
property string displayName radius: Settings.avatarCircles ? height / 2 : 3
color: colors.base
Label { Label {
anchors.fill: parent anchors.fill: parent
text: TimelineManager.escapeEmoji(displayName ? String.fromCodePoint(displayName.codePointAt(0)) : "") text: TimelineManager.escapeEmoji(displayName ? String.fromCodePoint(displayName.codePointAt(0)) : "")
textFormat: Text.RichText textFormat: Text.RichText
font.pixelSize: avatar.height/2 font.pixelSize: avatar.height / 2
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
visible: img.status != Image.Ready visible: img.status != Image.Ready
color: colors.text color: colors.text
} }
Image { Image {
id: img id: img
anchors.fill: parent
asynchronous: true
fillMode: Image.PreserveAspectCrop
mipmap: true
smooth: false
sourceSize.width: avatar.width anchors.fill: parent
sourceSize.height: avatar.height asynchronous: true
fillMode: Image.PreserveAspectCrop
mipmap: true
smooth: false
sourceSize.width: avatar.width
sourceSize.height: avatar.height
layer.enabled: true
layer.enabled: true layer.effect: OpacityMask {
layer.effect: OpacityMask {
maskSource: Rectangle {
anchors.fill: parent
width: avatar.width
height: avatar.height
radius: Settings.avatarCircles ? height/2 : 3
}
}
} maskSource: Rectangle {
anchors.fill: parent
width: avatar.width
height: avatar.height
radius: Settings.avatarCircles ? height / 2 : 3
}
Rectangle { }
anchors.bottom: avatar.bottom
anchors.right: avatar.right
visible: !!userid }
height: avatar.height / 6 Rectangle {
width: height anchors.bottom: avatar.bottom
radius: Settings.avatarCircles ? height / 2 : height / 4 anchors.right: avatar.right
color: switch (TimelineManager.userPresence(userid)) { visible: !!userid
case "online": return "#00cc66" height: avatar.height / 6
case "unavailable": return "#ff9933" width: height
case "offline": // return "#a82353" don't show anything if offline, since it is confusing, if presence is disabled radius: Settings.avatarCircles ? height / 2 : height / 4
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 import im.nheko 1.0
Rectangle { Rectangle {
property bool encrypted: false id: indicator
id: indicator
color: "transparent" property bool encrypted: false
width: 16
height: 16 function getEncryptionImage() {
if (encrypted)
ToolTip.visible: ma.containsMouse && indicator.visible return "image://colorimage/:/icons/icons/ui/lock.png?" + colors.buttonText;
ToolTip.text: getEncryptionTooltip() else
return "image://colorimage/:/icons/icons/ui/unlock.png?#dd3d3d";
MouseArea{ }
id: ma
anchors.fill: parent function getEncryptionTooltip() {
hoverEnabled: true if (encrypted)
} return qsTr("Encrypted");
else
Image { return qsTr("This message is not encrypted!");
id: stateImg }
anchors.fill: parent
source: getEncryptionImage() color: "transparent"
} width: 16
height: 16
function getEncryptionImage() { ToolTip.visible: ma.containsMouse && indicator.visible
if (encrypted) ToolTip.text: getEncryptionTooltip()
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!")
}
}
MouseArea {
id: ma
anchors.fill: parent
hoverEnabled: true
}
Image {
id: stateImg
anchors.fill: parent
source: getEncryptionImage()
}
}

@ -2,25 +2,29 @@ import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
AbstractButton { AbstractButton {
property string image: undefined id: button
property color highlightColor: colors.highlight
property color buttonTextColor: colors.buttonText property string image: undefined
width: 16 property color highlightColor: colors.highlight
height: 16 property color buttonTextColor: colors.buttonText
id: button
width: 16
Image { height: 16
id: buttonImg
// Workaround, can't get icon.source working for now... Image {
anchors.fill: parent id: buttonImg
source: "image://colorimage/" + image + "?" + (button.hovered ? highlightColor : buttonTextColor)
} // Workaround, can't get icon.source working for now...
anchors.fill: parent
MouseArea source: "image://colorimage/" + image + "?" + (button.hovered ? highlightColor : buttonTextColor)
{ }
id: mouseArea
anchors.fill: parent MouseArea {
onPressed: mouse.accepted = false id: mouseArea
cursorShape: Qt.PointingHandCursor
} anchors.fill: parent
onPressed: mouse.accepted = false
cursorShape: Qt.PointingHandCursor
}
} }

@ -1,35 +1,37 @@
import QtQuick 2.5 import QtQuick 2.5
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import im.nheko 1.0 import im.nheko 1.0
TextEdit { TextEdit {
textFormat: TextEdit.RichText textFormat: TextEdit.RichText
readOnly: true readOnly: true
wrapMode: Text.Wrap wrapMode: Text.Wrap
selectByMouse: true selectByMouse: true
activeFocusOnPress: false activeFocusOnPress: false
color: colors.text 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);
} else {
TimelineManager.openLink(link);
}
}
ToolTip.visible: hoveredLink
ToolTip.text: hoveredLink
MouseArea {
id: ma
onLinkActivated: { anchors.fill: parent
if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) chat.model.openUserProfile(/^https:\/\/matrix.to\/#\/(@.*)$/.exec(link)[1]) propagateComposedEvents: true
else if (/^https:\/\/matrix.to\/#\/(![^\/]*)$/.test(link)) TimelineManager.setHistoryView(/^https:\/\/matrix.to\/#\/(!.*)$/.exec(link)[1]) acceptedButtons: Qt.NoButton
else if (/^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.test(link)) { cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
var match = /^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.exec(link) }
TimelineManager.setHistoryView(match[1])
chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain)
}
else TimelineManager.openLink(link)
}
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,94 +1,95 @@
import QtQuick 2.6 import QtQuick 2.6
import QtQuick.Controls 2.2 import QtQuick.Controls 2.2
import im.nheko 1.0 import im.nheko 1.0
// This class is for showing Reactions in the timeline row, not for // This class is for showing Reactions in the timeline row, not for
// adding new reactions via the emoji picker // adding new reactions via the emoji picker
Flow { Flow {
id: reactionFlow id: reactionFlow
// highlight colors for selfReactedEvent background // highlight colors for selfReactedEvent background
property real highlightHue: colors.highlight.hslHue property real highlightHue: colors.highlight.hslHue
property real highlightSat: colors.highlight.hslSaturation property real highlightSat: colors.highlight.hslSaturation
property real highlightLight: colors.highlight.hslLightness property real highlightLight: colors.highlight.hslLightness
property string eventId
property string eventId property alias reactions: repeater.model
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
spacing: 4 spacing: 4
property alias reactions: repeater.model Repeater {
id: repeater
Repeater {
id: repeater delegate: AbstractButton {
id: reaction
delegate: AbstractButton {
id: reaction hoverEnabled: true
hoverEnabled: true implicitWidth: contentItem.childrenRect.width + contentItem.leftPadding * 2
implicitWidth: contentItem.childrenRect.width + contentItem.leftPadding*2 implicitHeight: contentItem.childrenRect.height
implicitHeight: contentItem.childrenRect.height ToolTip.visible: hovered
ToolTip.text: modelData.users
ToolTip.visible: hovered onClicked: {
ToolTip.text: modelData.users console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + ". selfReactedEvent: " + modelData.selfReactedEvent);
TimelineManager.queueReactionMessage(reactionFlow.eventId, modelData.key);
onClicked: { }
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
leftPadding: reactionText.implicitHeight / 2
contentItem: Row { rightPadding: reactionText.implicitHeight / 2
anchors.centerIn: parent
spacing: reactionText.implicitHeight/4 TextMetrics {
leftPadding: reactionText.implicitHeight / 2 id: textMetrics
rightPadding: reactionText.implicitHeight / 2
font.family: Settings.emojiFont
TextMetrics { elide: Text.ElideRight
id: textMetrics elideWidth: 150
font.family: Settings.emojiFont text: modelData.key
elide: Text.ElideRight }
elideWidth: 150
text: modelData.key Text {
} id: reactionText
Text { anchors.baseline: reactionCounter.baseline
anchors.baseline: reactionCounter.baseline text: textMetrics.elidedText + (textMetrics.elidedText == modelData.key ? "" : "…")
id: reactionText font.family: Settings.emojiFont
text: textMetrics.elidedText + (textMetrics.elidedText == modelData.key ? "" : "…") color: reaction.hovered ? colors.highlight : colors.text
font.family: Settings.emojiFont maximumLineCount: 1
color: reaction.hovered ? colors.highlight : colors.text }
maximumLineCount: 1
} Rectangle {
id: divider
Rectangle {
id: divider height: Math.floor(reactionCounter.implicitHeight * 1.4)
height: Math.floor(reactionCounter.implicitHeight * 1.4) width: 1
width: 1 color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text
color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text }
}
Text {
Text { id: reactionCounter
anchors.verticalCenter: divider.verticalCenter
id: reactionCounter anchors.verticalCenter: divider.verticalCenter
text: modelData.count text: modelData.count
font: reaction.font font: reaction.font
color: reaction.hovered ? colors.highlight : colors.text color: reaction.hovered ? colors.highlight : colors.text
} }
}
}
background: Rectangle {
anchors.centerIn: parent background: Rectangle {
anchors.centerIn: parent
implicitWidth: reaction.implicitWidth implicitWidth: reaction.implicitWidth
implicitHeight: reaction.implicitHeight implicitHeight: reaction.implicitHeight
border.color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text 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 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., * with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import QtQuick 2.9
import QtQuick.Controls 2.3
/* /*
* Shamelessly stolen from: * Shamelessly stolen from:
* https://cgit.kde.org/kube.git/tree/framework/qml/ScrollHelper.qml * https://cgit.kde.org/kube.git/tree/framework/qml/ScrollHelper.qml
@ -31,81 +27,82 @@ import QtQuick.Controls 2.3
* ScrollView.qml in qtquickcontrols * ScrollView.qml in qtquickcontrols
* qquickwheelarea.cpp in qtquickcontrols * qquickwheelarea.cpp in qtquickcontrols
*/ */
import QtQuick 2.9
import QtQuick.Controls 2.3
MouseArea { MouseArea {
// console.warn("Delta: ", wheel.pixelDelta.y);
// console.warn("Old position: ", flickable.contentY);
// console.warn("New position: ", newPos);
id: root id: root
propagateComposedEvents: true
property Flickable flickable property Flickable flickable
property alias enabled: root.enabled 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) { function calculateNewPosition(flickableItem, wheel) {
//Nothing to scroll //Nothing to scroll
if (flickableItem.contentHeight < flickableItem.height) { if (flickableItem.contentHeight < flickableItem.height)
return flickableItem.contentY; return flickableItem.contentY;
}
//Ignore 0 events (happens at least with Christians trackpad) //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; return flickableItem.contentY;
}
//pixelDelta seems to be the same as angleDelta/8 //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 //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) { if (wheel.angleDelta.y) {
var wheelScrollLines = 3 //Default value of QApplication wheelScrollLines property var wheelScrollLines = 3; //Default value of QApplication wheelScrollLines property
var pixelPerLine = 20 //Default value in Qt, originally comes from QTextEdit 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. var ticks = (wheel.angleDelta.y / 8) / 15; //Divide by 8 gives us pixels typically come in 15pixel steps.
pixelDelta = ticks * pixelPerLine * wheelScrollLines pixelDelta = ticks * pixelPerLine * wheelScrollLines;
} else { } else {
pixelDelta = wheel.pixelDelta.y pixelDelta = wheel.pixelDelta.y;
} }
pixelDelta = Math.round(pixelDelta);
pixelDelta = Math.round(pixelDelta) if (!pixelDelta)
if (!pixelDelta) {
return flickableItem.contentY; return flickableItem.contentY;
}
var minYExtent = flickableItem.originY + flickableItem.topMargin; var minYExtent = flickableItem.originY + flickableItem.topMargin;
var maxYExtent = (flickableItem.contentHeight + flickableItem.bottomMargin + flickableItem.originY) - flickableItem.height; var maxYExtent = (flickableItem.contentHeight + flickableItem.bottomMargin + flickableItem.originY) - flickableItem.height;
if (typeof (flickableItem.headerItem) !== "undefined" && flickableItem.headerItem)
if (typeof(flickableItem.headerItem) !== "undefined" && flickableItem.headerItem) { minYExtent += flickableItem.headerItem.height;
minYExtent += flickableItem.headerItem.height
}
//Avoid overscrolling //Avoid overscrolling
return Math.max(minYExtent, Math.min(maxYExtent, flickableItem.contentY - pixelDelta)); 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: { onWheel: {
var newPos = calculateNewPosition(flickable, wheel); 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 // Show the scrollbars
flickable.flick(0, 0); flickable.flick(0, 0);
flickable.contentY = newPos; flickable.contentY = newPos;
cancelFlickStateTimer.start() cancelFlickStateTimer.start();
} }
Timer { Timer {
id: cancelFlickStateTimer id: cancelFlickStateTimer
//How long the scrollbar will remain visible //How long the scrollbar will remain visible
interval: 500 interval: 500
// Hide the scrollbars // Hide the scrollbars
onTriggered: { flickable.cancelFlick(); flickable.movementEnded(); } onTriggered: {
flickable.cancelFlick();
flickable.movementEnded();
}
} }
} }

@ -3,37 +3,55 @@ import QtQuick.Controls 2.1
import im.nheko 1.0 import im.nheko 1.0
Rectangle { Rectangle {
id: indicator id: indicator
property int state: 0
color: "transparent" property int state: 0
width: 16
height: 16 color: "transparent"
width: 16
ToolTip.visible: ma.containsMouse && state != MtxEvent.Empty height: 16
ToolTip.text: switch (state) { ToolTip.visible: ma.containsMouse && state != MtxEvent.Empty
case MtxEvent.Failed: return qsTr("Failed") ToolTip.text: {
case MtxEvent.Sent: return qsTr("Sent") switch (state) {
case MtxEvent.Received: return qsTr("Received") case MtxEvent.Failed:
case MtxEvent.Read: return qsTr("Read") return qsTr("Failed");
default: return "" case MtxEvent.Sent:
} return qsTr("Sent");
MouseArea{ case MtxEvent.Received:
id: ma return qsTr("Received");
anchors.fill: parent case MtxEvent.Read:
hoverEnabled: true return qsTr("Read");
} default:
return "";
Image { }
id: stateImg }
// Workaround, can't get icon.source working for now...
anchors.fill: parent MouseArea {
source: switch (indicator.state) { id: ma
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 ""
}
}
}
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 "";
}
}
}
}

@ -1,146 +1,148 @@
import "./delegates"
import "./emoji"
import QtQuick 2.6 import QtQuick 2.6
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import QtQuick.Window 2.2 import QtQuick.Window 2.2
import im.nheko 1.0 import im.nheko 1.0
import "./delegates"
import "./emoji"
Item { Item {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
height: row.height height: row.height
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
propagateComposedEvents: true propagateComposedEvents: true
preventStealing: true preventStealing: true
hoverEnabled: true hoverEnabled: true
acceptedButtons: Qt.AllButtons
acceptedButtons: Qt.AllButtons onClicked: {
onClicked: { if (mouse.button === Qt.RightButton)
if (mouse.button === Qt.RightButton) messageContextMenu.show(model.id, model.type, model.isEncrypted, row);
messageContextMenu.show(model.id, model.type, model.isEncrypted, row)
} }
onPressAndHold: { 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" Rectangle {
anchors.fill: row color: (Settings.messageHoverHighlight && parent.containsMouse) ? colors.base : "transparent"
} anchors.fill: row
RowLayout { }
id: row
RowLayout {
anchors.leftMargin: avatarSize + 16 id: row
anchors.left: parent.left
anchors.right: parent.right anchors.leftMargin: avatarSize + 16
anchors.left: parent.left
anchors.right: parent.right
Column {
Layout.fillWidth: true Column {
Layout.alignment: Qt.AlignTop Layout.fillWidth: true
spacing: 4 Layout.alignment: Qt.AlignTop
spacing: 4
// fancy reply, if this is a reply
Reply { // fancy reply, if this is a reply
visible: model.replyTo Reply {
modelData: chat.model.getDump(model.replyTo,model.id) visible: model.replyTo
userColor: TimelineManager.userColor(modelData.userId, colors.window) modelData: chat.model.getDump(model.replyTo, model.id)
} userColor: TimelineManager.userColor(modelData.userId, colors.window)
}
// actual message content
MessageDelegate { // actual message content
id: contentItem MessageDelegate {
id: contentItem
width: parent.width
width: parent.width
modelData: model modelData: model
} }
Reactions { Reactions {
id: reactionRow id: reactionRow
reactions: model.reactions
eventId: model.id reactions: model.reactions
} eventId: model.id
} }
StatusIndicator { }
state: model.state
Layout.alignment: Qt.AlignRight | Qt.AlignTop StatusIndicator {
Layout.preferredHeight: 16 state: model.state
width: 16 Layout.alignment: Qt.AlignRight | Qt.AlignTop
} Layout.preferredHeight: 16
width: 16
EncryptionIndicator { }
visible: model.isRoomEncrypted
encrypted: model.isEncrypted EncryptionIndicator {
Layout.alignment: Qt.AlignRight | Qt.AlignTop visible: model.isRoomEncrypted
Layout.preferredHeight: 16 encrypted: model.isEncrypted
width: 16 Layout.alignment: Qt.AlignRight | Qt.AlignTop
} Layout.preferredHeight: 16
EmojiButton { width: 16
visible: Settings.buttonsInTimeline }
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16 EmojiButton {
width: 16 id: reactButton
id: reactButton
hoverEnabled: true visible: Settings.buttonsInTimeline
ToolTip.visible: hovered Layout.alignment: Qt.AlignRight | Qt.AlignTop
ToolTip.text: qsTr("React") Layout.preferredHeight: 16
emojiPicker: emojiPopup width: 16
event_id: model.id hoverEnabled: true
} ToolTip.visible: hovered
ImageButton { ToolTip.text: qsTr("React")
visible: Settings.buttonsInTimeline emojiPicker: emojiPopup
Layout.alignment: Qt.AlignRight | Qt.AlignTop event_id: model.id
Layout.preferredHeight: 16 }
width: 16
id: replyButton ImageButton {
hoverEnabled: true id: replyButton
visible: Settings.buttonsInTimeline
image: ":/icons/icons/ui/mail-reply.png" Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
ToolTip.visible: hovered width: 16
ToolTip.text: qsTr("Reply") hoverEnabled: true
image: ":/icons/icons/ui/mail-reply.png"
onClicked: chat.model.replyAction(model.id) ToolTip.visible: hovered
} ToolTip.text: qsTr("Reply")
ImageButton { onClicked: chat.model.replyAction(model.id)
visible: Settings.buttonsInTimeline }
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16 ImageButton {
width: 16 id: optionsButton
id: optionsButton
hoverEnabled: true visible: Settings.buttonsInTimeline
Layout.alignment: Qt.AlignRight | Qt.AlignTop
image: ":/icons/icons/ui/vertical-ellipsis.png" Layout.preferredHeight: 16
width: 16
ToolTip.visible: hovered hoverEnabled: true
ToolTip.text: qsTr("Options") image: ":/icons/icons/ui/vertical-ellipsis.png"
ToolTip.visible: hovered
onClicked: messageContextMenu.show(model.id, model.type, model.isEncrypted, optionsButton) ToolTip.text: qsTr("Options")
} onClicked: messageContextMenu.show(model.id, model.type, model.isEncrypted, optionsButton)
}
Label {
Layout.alignment: Qt.AlignRight | Qt.AlignTop Label {
text: model.timestamp.toLocaleTimeString("HH:mm") Layout.alignment: Qt.AlignRight | Qt.AlignTop
width: Math.max(implicitWidth, text.length*fontMetrics.maximumCharacterWidth) text: model.timestamp.toLocaleTimeString("HH:mm")
color: inactiveColors.text width: Math.max(implicitWidth, text.length * fontMetrics.maximumCharacterWidth)
color: inactiveColors.text
MouseArea{ ToolTip.visible: ma.containsMouse
id: ma ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate)
anchors.fill: parent
hoverEnabled: true MouseArea {
propagateComposedEvents: true id: ma
}
anchors.fill: parent
ToolTip.visible: ma.containsMouse hoverEnabled: true
ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate) propagateComposedEvents: true
} }
}
}
}
} }

File diff suppressed because it is too large Load Diff

@ -1,172 +1,175 @@
import "./device-verification"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import QtQuick.Window 2.3 import QtQuick.Window 2.3
import im.nheko 1.0 import im.nheko 1.0
import "./device-verification" ApplicationWindow {
id: userProfileDialog
property var profile
height: 650
width: 420
minimumHeight: 420
palette: colors
Component {
id: deviceVerificationDialog
DeviceVerification {
}
}
ColumnLayout {
id: contentL
anchors.fill: parent
anchors.margins: 10
spacing: 10
Avatar {
url: profile.avatarUrl.replace("mxc://", "image://MxcImage/")
height: 130
width: 130
displayName: profile.displayName
userid: profile.userid
Layout.alignment: Qt.AlignHCenter
}
Label {
text: profile.displayName
fontSizeMode: Text.HorizontalFit
font.pixelSize: 20
color: TimelineManager.userColor(profile.userid, colors.window)
font.bold: true
Layout.alignment: Qt.AlignHCenter
}
MatrixText {
text: profile.userid
font.pixelSize: 15
Layout.alignment: Qt.AlignHCenter
}
Button {
id: verifyUserButton
text: qsTr("Verify")
Layout.alignment: Qt.AlignHCenter
enabled: !profile.isUserVerified
visible: !profile.isUserVerified
onClicked: profile.verify()
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 8
ImageButton {
image: ":/icons/icons/ui/do-not-disturb-rounded-sign.png"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Ban the user")
onClicked: profile.banUser()
}
// ImageButton{
// image:":/icons/icons/ui/volume-off-indicator.png"
// Layout.margins: {
// left: 5
// right: 5
// }
// ToolTip.visible: hovered
// ToolTip.text: qsTr("Ignore messages from this user")
// onClicked : {
// profile.ignoreUser()
// }
// }
ImageButton {
image: ":/icons/icons/ui/black-bubble-speech.png"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Start a private chat")
onClicked: profile.startChat()
}
ImageButton {
image: ":/icons/icons/ui/round-remove-button.png"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Kick the user")
onClicked: profile.kickUser()
}
}
ListView {
id: devicelist
Layout.fillHeight: true
Layout.minimumHeight: 200
Layout.fillWidth: true
clip: true
spacing: 8
boundsBehavior: Flickable.StopAtBounds
model: profile.deviceList
delegate: RowLayout {
width: devicelist.width
spacing: 4
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"))
}
Button {
id: verifyButton
text: (model.verificationStatus != VerificationStatus.VERIFIED) ? "Verify" : "Unverify"
onClicked: {
if (model.verificationStatus == VerificationStatus.VERIFIED)
profile.unverify(model.deviceId);
else
profile.verify(model.deviceId);
}
}
}
}
}
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: userProfileDialog.close()
}
ApplicationWindow{
property var profile
id: userProfileDialog
height: 650
width: 420
minimumHeight: 420
palette: colors
Component {
id: deviceVerificationDialog
DeviceVerification {}
}
ColumnLayout{
id: contentL
anchors.fill: parent
anchors.margins: 10
spacing: 10
Avatar {
url: profile.avatarUrl.replace("mxc://", "image://MxcImage/")
height: 130
width: 130
displayName: profile.displayName
userid: profile.userid
Layout.alignment: Qt.AlignHCenter
}
Label {
text: profile.displayName
fontSizeMode: Text.HorizontalFit
font.pixelSize: 20
color: TimelineManager.userColor(profile.userid, colors.window)
font.bold: true
Layout.alignment: Qt.AlignHCenter
}
MatrixText {
text: profile.userid
font.pixelSize: 15
Layout.alignment: Qt.AlignHCenter
}
Button {
id: verifyUserButton
text: qsTr("Verify")
Layout.alignment: Qt.AlignHCenter
enabled: !profile.isUserVerified
visible: !profile.isUserVerified
onClicked: profile.verify()
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 8
ImageButton {
image:":/icons/icons/ui/do-not-disturb-rounded-sign.png"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Ban the user")
onClicked: profile.banUser()
}
// ImageButton{
// image:":/icons/icons/ui/volume-off-indicator.png"
// Layout.margins: {
// left: 5
// right: 5
// }
// ToolTip.visible: hovered
// ToolTip.text: qsTr("Ignore messages from this user")
// onClicked : {
// profile.ignoreUser()
// }
// }
ImageButton{
image:":/icons/icons/ui/black-bubble-speech.png"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Start a private chat")
onClicked: profile.startChat()
}
ImageButton{
image:":/icons/icons/ui/round-remove-button.png"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Kick the user")
onClicked: profile.kickUser()
}
}
ListView{
id: devicelist
Layout.fillHeight: true
Layout.minimumHeight: 200
Layout.fillWidth: true
clip: true
spacing: 8
boundsBehavior: Flickable.StopAtBounds
model: profile.deviceList
delegate: RowLayout{
width: devicelist.width
spacing: 4
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"))
}
Button{
id: verifyButton
text: (model.verificationStatus != VerificationStatus.VERIFIED)?"Verify":"Unverify"
onClicked: {
if(model.verificationStatus == VerificationStatus.VERIFIED){
profile.unverify(model.deviceId)
}else{
profile.verify(model.deviceId);
}
}
}
}
}
}
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: userProfileDialog.close()
}
} }

@ -1,68 +1,75 @@
import QtQuick 2.6 import QtQuick 2.6
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import im.nheko 1.0 import im.nheko 1.0
Item { Item {
height: row.height + 24 height: row.height + 24
width: parent ? parent.width : undefined width: parent ? parent.width : undefined
RowLayout { RowLayout {
id: row id: row
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width - 24 width: parent.width - 24
spacing: 15
spacing: 15
Rectangle {
Rectangle { id: button
id: button
color: colors.light color: colors.light
radius: 22 radius: 22
height: 44 height: 44
width: 44 width: 44
Image {
id: img Image {
anchors.centerIn: parent id: img
source: "qrc:/icons/icons/ui/arrow-pointing-down.png" anchors.centerIn: parent
fillMode: Image.Pad source: "qrc:/icons/icons/ui/arrow-pointing-down.png"
fillMode: Image.Pad
} }
MouseArea {
anchors.fill: parent MouseArea {
onClicked: TimelineManager.timeline.saveMedia(model.data.id) anchors.fill: parent
cursorShape: Qt.PointingHandCursor onClicked: TimelineManager.timeline.saveMedia(model.data.id)
} cursorShape: Qt.PointingHandCursor
} }
ColumnLayout {
id: col }
Text { ColumnLayout {
id: filename id: col
Layout.fillWidth: true
text: model.data.filename Text {
textFormat: Text.PlainText id: filename
elide: Text.ElideRight
color: colors.text Layout.fillWidth: true
} text: model.data.filename
Text { textFormat: Text.PlainText
id: filesize elide: Text.ElideRight
Layout.fillWidth: true color: colors.text
text: model.data.filesize }
textFormat: Text.PlainText
elide: Text.ElideRight Text {
color: colors.text id: filesize
}
} Layout.fillWidth: true
} text: model.data.filesize
textFormat: Text.PlainText
Rectangle { elide: Text.ElideRight
color: colors.dark color: colors.text
z: -1 }
radius: 10
height: row.height + 24 }
width: 44 + 24 + 24 + Math.max(Math.min(filesize.width, filesize.implicitWidth), Math.min(filename.width, filename.implicitWidth))
} }
Rectangle {
color: colors.dark
z: -1
radius: 10
height: row.height + 24
width: 44 + 24 + 24 + Math.max(Math.min(filesize.width, filesize.implicitWidth), Math.min(filename.width, filename.implicitWidth))
}
} }

@ -1,42 +1,41 @@
import QtQuick 2.6 import QtQuick 2.6
import im.nheko 1.0 import im.nheko 1.0
Item { Item {
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? parent.width : model.data.width) 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 tempHeight: tempWidth * model.data.proportionalHeight
property double divisor: model.isReply ? 4 : 2
property double divisor: model.isReply ? 4 : 2 property bool tooHigh: tempHeight > timelineRoot.height / divisor
property bool tooHigh: tempHeight > timelineRoot.height / divisor
height: Math.round(tooHigh ? timelineRoot.height / divisor : tempHeight)
height: Math.round(tooHigh ? timelineRoot.height / divisor : tempHeight) width: Math.round(tooHigh ? (timelineRoot.height / divisor) / model.data.proportionalHeight : tempWidth)
width: Math.round(tooHigh ? (timelineRoot.height / divisor) / model.data.proportionalHeight : tempWidth)
Image {
Image { id: blurhash
id: blurhash
anchors.fill: parent anchors.fill: parent
visible: img.status != Image.Ready 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)
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
asynchronous: true fillMode: Image.PreserveAspectFit
fillMode: Image.PreserveAspectFit sourceSize.width: parent.width
sourceSize.height: parent.height
sourceSize.width: parent.width }
sourceSize.height: parent.height
} Image {
id: img
Image {
id: img anchors.fill: parent
anchors.fill: parent source: model.data.url.replace("mxc://", "image://MxcImage/")
asynchronous: true
source: model.data.url.replace("mxc://", "image://MxcImage/") fillMode: Image.PreserveAspectFit
asynchronous: true
fillMode: Image.PreserveAspectFit MouseArea {
enabled: model.data.type == MtxEvent.ImageMessage && img.status == Image.Ready
MouseArea { anchors.fill: parent
enabled: model.data.type == MtxEvent.ImageMessage && img.status == Image.Ready onClicked: TimelineManager.openImageOverlay(model.data.url, model.data.id)
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 import im.nheko 1.0
Item { Item {
// Workaround to have an assignable global property property alias modelData: model.data
Item { property alias isReply: model.isReply
id: model property real implicitWidth: (chooser.child && chooser.child.implicitWidth) ? chooser.child.implicitWidth : width
property var data;
property bool isReply: false height: chooser.childrenRect.height
}
// Workaround to have an assignable global property
property alias modelData: model.data Item {
property alias isReply: model.isReply id: model
height: chooser.childrenRect.height property var data
property real implicitWidth: (chooser.child && chooser.child.implicitWidth) ? chooser.child.implicitWidth : width property bool isReply: false
}
DelegateChooser {
id: chooser DelegateChooser {
//role: "type" //< not supported in our custom implementation, have to use roleValue id: chooser
roleValue: model.data.type
anchors.fill: parent //role: "type" //< not supported in our custom implementation, have to use roleValue
roleValue: model.data.type
DelegateChoice { anchors.fill: parent
roleValue: MtxEvent.UnknownMessage
Placeholder { text: "Unretrieved event" } DelegateChoice {
} roleValue: MtxEvent.UnknownMessage
DelegateChoice {
roleValue: MtxEvent.TextMessage Placeholder {
TextMessage {} text: "Unretrieved event"
} }
DelegateChoice {
roleValue: MtxEvent.NoticeMessage }
NoticeMessage {}
} DelegateChoice {
DelegateChoice { roleValue: MtxEvent.TextMessage
roleValue: MtxEvent.EmoteMessage
NoticeMessage { TextMessage {
formatted: TimelineManager.escapeEmoji(modelData.userName) + " " + model.data.formattedBody }
color: TimelineManager.userColor(modelData.userId, colors.window)
} }
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.ImageMessage roleValue: MtxEvent.NoticeMessage
ImageMessage {}
} NoticeMessage {
DelegateChoice { }
roleValue: MtxEvent.Sticker
ImageMessage {} }
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.FileMessage roleValue: MtxEvent.EmoteMessage
FileMessage {}
} NoticeMessage {
DelegateChoice { formatted: TimelineManager.escapeEmoji(modelData.userName) + " " + model.data.formattedBody
roleValue: MtxEvent.VideoMessage color: TimelineManager.userColor(modelData.userId, colors.window)
PlayableMediaMessage {} }
}
DelegateChoice { }
roleValue: MtxEvent.AudioMessage
PlayableMediaMessage {} DelegateChoice {
} roleValue: MtxEvent.ImageMessage
DelegateChoice {
roleValue: MtxEvent.Redacted ImageMessage {
Pill { }
text: qsTr("redacted")
} }
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.Redaction roleValue: MtxEvent.Sticker
Pill {
text: qsTr("redacted") ImageMessage {
} }
}
DelegateChoice { }
roleValue: MtxEvent.Encryption
Pill { DelegateChoice {
text: qsTr("Encryption enabled") roleValue: MtxEvent.FileMessage
}
} FileMessage {
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.VideoMessage
DelegateChoice {
roleValue: MtxEvent.Topic PlayableMediaMessage {
NoticeMessage { }
text: model.data.roomTopic ? qsTr("topic changed to: %1").arg(model.data.roomTopic) : qsTr("removed topic")
} }
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.RoomCreate roleValue: MtxEvent.AudioMessage
NoticeMessage {
text: qsTr("%1 created and configured room: %2").arg(model.data.userName).arg(model.data.roomId) PlayableMediaMessage {
} }
}
DelegateChoice { }
roleValue: MtxEvent.CallInvite
NoticeMessage { DelegateChoice {
text: switch(model.data.callType) { roleValue: MtxEvent.Redacted
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) Pill {
default: return qsTr("%1 placed a call.").arg(model.data.userName) text: qsTr("redacted")
} }
}
} }
DelegateChoice {
roleValue: MtxEvent.CallAnswer DelegateChoice {
NoticeMessage { roleValue: MtxEvent.Redaction
text: qsTr("%1 answered the call.").arg(model.data.userName)
} Pill {
} text: qsTr("redacted")
DelegateChoice { }
roleValue: MtxEvent.CallHangUp
NoticeMessage { }
text: qsTr("%1 ended the call.").arg(model.data.userName)
} DelegateChoice {
} roleValue: MtxEvent.Encryption
DelegateChoice {
roleValue: MtxEvent.CallCandidates Pill {
NoticeMessage { text: qsTr("Encryption enabled")
text: qsTr("Negotiating call...") }
}
} }
DelegateChoice {
// TODO: make a more complex formatter for the power levels. DelegateChoice {
roleValue: MtxEvent.PowerLevels roleValue: MtxEvent.Name
NoticeMessage {
text: TimelineManager.timeline.formatPowerLevelEvent(model.data.id) NoticeMessage {
} text: model.data.roomName ? qsTr("room name changed to: %1").arg(model.data.roomName) : qsTr("removed room name")
} }
DelegateChoice {
roleValue: MtxEvent.RoomJoinRules }
NoticeMessage {
text: TimelineManager.timeline.formatJoinRuleEvent(model.data.id) DelegateChoice {
} roleValue: MtxEvent.Topic
}
DelegateChoice { NoticeMessage {
roleValue: MtxEvent.RoomHistoryVisibility text: model.data.roomTopic ? qsTr("topic changed to: %1").arg(model.data.roomTopic) : qsTr("removed topic")
NoticeMessage { }
text: TimelineManager.timeline.formatHistoryVisibilityEvent(model.data.id)
} }
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.RoomGuestAccess roleValue: MtxEvent.RoomCreate
NoticeMessage {
text: TimelineManager.timeline.formatGuestAccessEvent(model.data.id) NoticeMessage {
} text: qsTr("%1 created and configured room: %2").arg(model.data.userName).arg(model.data.roomId)
} }
DelegateChoice {
roleValue: MtxEvent.Member }
NoticeMessage {
text: TimelineManager.timeline.formatMemberEvent(model.data.id); DelegateChoice {
} roleValue: MtxEvent.CallInvite
}
DelegateChoice { NoticeMessage {
roleValue: MtxEvent.KeyVerificationRequest text: {
NoticeMessage { switch (model.data.callType) {
text: "KeyVerificationRequest"; case "voice":
} return qsTr("%1 placed a voice call.").arg(model.data.userName);
} case "video":
DelegateChoice { return qsTr("%1 placed a video call.").arg(model.data.userName);
roleValue: MtxEvent.KeyVerificationStart default:
NoticeMessage { return qsTr("%1 placed a call.").arg(model.data.userName);
text: "KeyVerificationStart"; }
} }
} }
DelegateChoice {
roleValue: MtxEvent.KeyVerificationReady }
NoticeMessage {
text: "KeyVerificationReady"; DelegateChoice {
} roleValue: MtxEvent.CallAnswer
}
DelegateChoice { NoticeMessage {
roleValue: MtxEvent.KeyVerificationCancel text: qsTr("%1 answered the call.").arg(model.data.userName)
NoticeMessage { }
text: "KeyVerificationCancel";
} }
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationKey roleValue: MtxEvent.CallHangUp
NoticeMessage {
text: "KeyVerificationKey"; NoticeMessage {
} text: qsTr("%1 ended the call.").arg(model.data.userName)
} }
DelegateChoice {
roleValue: MtxEvent.KeyVerificationMac }
NoticeMessage {
text: "KeyVerificationMac"; DelegateChoice {
} roleValue: MtxEvent.CallCandidates
}
DelegateChoice { NoticeMessage {
roleValue: MtxEvent.KeyVerificationDone text: qsTr("Negotiating call...")
NoticeMessage { }
text: "KeyVerificationDone";
} }
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationDone // TODO: make a more complex formatter for the power levels.
NoticeMessage { roleValue: MtxEvent.PowerLevels
text: "KeyVerificationDone";
} NoticeMessage {
} text: TimelineManager.timeline.formatPowerLevelEvent(model.data.id)
DelegateChoice { }
roleValue: MtxEvent.KeyVerificationAccept
NoticeMessage { }
text: "KeyVerificationAccept";
} DelegateChoice {
} roleValue: MtxEvent.RoomJoinRules
DelegateChoice {
Placeholder {} 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)
}
}
DelegateChoice {
roleValue: MtxEvent.KeyVerificationRequest
NoticeMessage {
text: "KeyVerificationRequest"
}
}
DelegateChoice {
roleValue: MtxEvent.KeyVerificationStart
NoticeMessage {
text: "KeyVerificationStart"
}
}
DelegateChoice {
roleValue: MtxEvent.KeyVerificationReady
NoticeMessage {
text: "KeyVerificationReady"
}
}
DelegateChoice {
roleValue: MtxEvent.KeyVerificationCancel
NoticeMessage {
text: "KeyVerificationCancel"
}
}
DelegateChoice {
roleValue: MtxEvent.KeyVerificationKey
NoticeMessage {
text: "KeyVerificationKey"
}
}
DelegateChoice {
roleValue: MtxEvent.KeyVerificationMac
NoticeMessage {
text: "KeyVerificationMac"
}
}
DelegateChoice {
roleValue: MtxEvent.KeyVerificationDone
NoticeMessage {
text: "KeyVerificationDone"
}
}
DelegateChoice {
roleValue: MtxEvent.KeyVerificationDone
NoticeMessage {
text: "KeyVerificationDone"
}
}
DelegateChoice {
roleValue: MtxEvent.KeyVerificationAccept
NoticeMessage {
text: "KeyVerificationAccept"
}
}
DelegateChoice {
Placeholder {
}
}
}
} }

@ -1,6 +1,6 @@
TextMessage { TextMessage {
font.italic: true font.italic: true
color: colors.buttonText color: colors.buttonText
height: isReply ? Math.min(chat.height / 8, implicitHeight) : undefined height: isReply ? Math.min(chat.height / 8, implicitHeight) : undefined
clip: true clip: true
} }

@ -2,13 +2,14 @@ import QtQuick 2.5
import QtQuick.Controls 2.1 import QtQuick.Controls 2.1
Label { Label {
color: colors.brightText color: colors.brightText
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
height: contentHeight * 1.2
width: contentWidth * 1.2
background: Rectangle {
radius: parent.height / 2
color: colors.dark
}
height: contentHeight * 1.2
width: contentWidth * 1.2
background: Rectangle {
radius: parent.height / 2
color: colors.dark
}
} }

@ -1,7 +1,7 @@
import ".." import ".."
MatrixText { MatrixText {
text: qsTr("unimplemented event: ") + model.data.typeString text: qsTr("unimplemented event: ") + model.data.typeString
width: parent ? parent.width : undefined width: parent ? parent.width : undefined
color: inactiveColors.text color: inactiveColors.text
} }

@ -1,173 +1,216 @@
import QtMultimedia 5.6
import QtQuick 2.6 import QtQuick 2.6
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.1 import QtQuick.Controls 2.1
import QtMultimedia 5.6 import QtQuick.Layouts 1.2
import im.nheko 1.0 import im.nheko 1.0
Rectangle { Rectangle {
id: bg id: bg
radius: 10
color: colors.dark
height: Math.round(content.height + 24)
width: parent ? parent.width : undefined
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
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/")
asynchronous: true
fillMode: Image.PreserveAspectFit
VideoOutput {
anchors.fill: parent
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)
//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;}
return hh+":"+mm+":"+ss;
}
positionText.text = formatTime(new Date(media.position))
durationText.text = formatTime(new Date(media.duration))
}
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
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 "stopped":
media.play(); console.log("play");
button.state = "playing"
break
case "playing":
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()
onStopped: button.state = "stopped"
}
Connections {
target: TimelineManager.timeline
onMediaCached: {
if (mxcUrl == model.data.url) {
media.source = "file://" + cacheUrl
button.state = "stopped"
console.log("media loaded: " + 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
Text {
Layout.fillWidth: true
text: model.data.body
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text
}
Text {
Layout.fillWidth: true
text: model.data.filesize
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text
}
}
}
}
}
radius: 10
color: colors.dark
height: Math.round(content.height + 24)
width: parent ? parent.width : undefined
Column {
id: content
width: parent.width - 24
anchors.centerIn: parent
Rectangle {
id: videoContainer
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/")
asynchronous: true
fillMode: Image.PreserveAspectFit
VideoOutput {
anchors.fill: parent
fillMode: VideoOutput.PreserveAspectFit
source: media
}
}
}
RowLayout {
width: parent.width
Text {
id: positionText
text: "--:--:--"
color: colors.text
}
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;
return hh + ":" + mm + ":" + ss;
}
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
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
}
}
]
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 "stopped":
media.play();
console.log("play");
button.state = "playing";
break;
case "playing":
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();
}
onStopped: button.state = "stopped"
}
Connections {
target: TimelineManager.timeline
onMediaCached: {
if (mxcUrl == model.data.url) {
media.source = "file://" + cacheUrl;
button.state = "stopped";
console.log("media loaded: " + mxcUrl + " at " + cacheUrl);
}
console.log("media cached: " + mxcUrl + " at " + cacheUrl);
}
}
}
ColumnLayout {
id: col
Text {
Layout.fillWidth: true
text: model.data.body
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text
}
Text {
Layout.fillWidth: true
text: model.data.filesize
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text
}
}
}
}
}

@ -2,66 +2,71 @@ import QtQuick 2.6
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import QtQuick.Window 2.2 import QtQuick.Window 2.2
import im.nheko 1.0 import im.nheko 1.0
Item { Item {
id: replyComponent id: replyComponent
property alias modelData: reply.modelData property alias modelData: reply.modelData
property color userColor: "red" property color userColor: "red"
width: parent.width width: parent.width
height: replyContainer.height height: replyContainer.height
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
preventStealing: true preventStealing: true
onClicked: chat.positionViewAtIndex(chat.model.idToIndex(modelData.id), ListView.Contain) onClicked: chat.positionViewAtIndex(chat.model.idToIndex(modelData.id), ListView.Contain)
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
Rectangle { Rectangle {
id: colorLine id: colorLine
anchors.top: replyContainer.top anchors.top: replyContainer.top
anchors.bottom: replyContainer.bottom anchors.bottom: replyContainer.bottom
width: 4 width: 4
color: TimelineManager.userColor(reply.modelData.userId, colors.window)
color: TimelineManager.userColor(reply.modelData.userId, colors.window) }
}
Column {
Column { id: replyContainer
id: replyContainer
anchors.left: colorLine.right anchors.left: colorLine.right
anchors.leftMargin: 4 anchors.leftMargin: 4
width: parent.width - 8 width: parent.width - 8
Text { Text {
id: userName id: userName
text: TimelineManager.escapeEmoji(reply.modelData.userName)
color: replyComponent.userColor text: TimelineManager.escapeEmoji(reply.modelData.userName)
textFormat: Text.RichText color: replyComponent.userColor
textFormat: Text.RichText
MouseArea {
anchors.fill: parent MouseArea {
onClicked: chat.model.openUserProfile(reply.modelData.userId) anchors.fill: parent
cursorShape: Qt.PointingHandCursor onClicked: chat.model.openUserProfile(reply.modelData.userId)
} cursorShape: Qt.PointingHandCursor
} }
MessageDelegate { }
id: reply
width: parent.width MessageDelegate {
isReply: true 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) Rectangle {
color: Qt.rgba(userColor.r, userColor.g, userColor.b, 0.2) 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,12 +1,12 @@
import ".." import ".."
import im.nheko 1.0 import im.nheko 1.0
MatrixText { MatrixText {
property string formatted: model.data.formattedBody 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 text: "<style type=\"text/css\">a { color:" + colors.link + ";}</style>" + formatted.replace("<pre>", "<pre style='white-space: pre-wrap'>")
height: isReply ? Math.round(Math.min(timelineRoot.height / 8, implicitHeight)) : undefined width: parent ? parent.width : undefined
clip: true height: isReply ? Math.round(Math.min(timelineRoot.height / 8, implicitHeight)) : undefined
font.pointSize: (Settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize clip: true
font.pointSize: (Settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize
} }

@ -1,39 +1,46 @@
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.10 import QtQuick.Controls 2.10
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10
import im.nheko 1.0 import im.nheko 1.0
Pane { Pane {
property string title: qsTr("Awaiting Confirmation") property string title: qsTr("Awaiting Confirmation")
ColumnLayout {
spacing: 16 ColumnLayout {
Label { spacing: 16
Layout.maximumWidth: 400
Layout.fillHeight: true Label {
Layout.fillWidth: true id: content
wrapMode: Text.Wrap
id: content Layout.maximumWidth: 400
text: qsTr("Waiting for other side to complete verification.") Layout.fillHeight: true
color:colors.text Layout.fillWidth: true
verticalAlignment: Text.AlignVCenter wrapMode: Text.Wrap
} text: qsTr("Waiting for other side to complete verification.")
BusyIndicator { color: colors.text
Layout.alignment: Qt.AlignHCenter verticalAlignment: Text.AlignVCenter
} }
RowLayout {
Button { BusyIndicator {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignHCenter
text: qsTr("Cancel") }
onClicked: { RowLayout {
flow.cancel(); Button {
dialog.close(); Layout.alignment: Qt.AlignLeft
} text: qsTr("Cancel")
} onClicked: {
Item { flow.cancel();
Layout.fillWidth: true dialog.close();
} }
} }
}
Item {
Layout.fillWidth: true
}
}
}
} }

@ -1,97 +1,144 @@
import QtQuick 2.10 import QtQuick 2.10
import QtQuick.Controls 2.10 import QtQuick.Controls 2.10
import QtQuick.Window 2.10 import QtQuick.Window 2.10
import im.nheko 1.0 import im.nheko 1.0
ApplicationWindow { ApplicationWindow {
property var flow id: dialog
onClosing: TimelineManager.removeVerificationFlow(flow) property var flow
title: stack.currentItem.title onClosing: TimelineManager.removeVerificationFlow(flow)
id: dialog title: stack.currentItem.title
flags: Qt.Dialog
flags: Qt.Dialog palette: colors
height: stack.implicitHeight
palette: colors width: stack.implicitWidth
height: stack.implicitHeight StackView {
width: stack.implicitWidth id: stack
StackView { initialItem: newVerificationRequest
id: stack implicitWidth: currentItem.implicitWidth
initialItem: newVerificationRequest implicitHeight: currentItem.implicitHeight
implicitWidth: currentItem.implicitWidth }
implicitHeight: currentItem.implicitHeight
} Component {
id: newVerificationRequest
Component{
id: newVerificationRequest NewVerificationRequest {
NewVerificationRequest {} }
}
}
Component {
id: waiting Component {
Waiting {} id: waiting
}
Waiting {
Component { }
id: success
Success {} }
}
Component {
Component { id: success
id: failed
Failed {} Success {
} }
Component { }
id: digitVerification
DigitVerification {} Component {
} id: failed
Component { Failed {
id: emojiVerification }
EmojiVerification {}
} }
Item { Component {
state: flow.state id: digitVerification
states: [ DigitVerification {
State { }
name: "PromptStartVerification"
StateChangeScript { script: stack.replace(newVerificationRequest) } }
},
State { Component {
name: "CompareEmoji" id: emojiVerification
StateChangeScript { script: stack.replace(emojiVerification) }
}, EmojiVerification {
State { }
name: "CompareNumber"
StateChangeScript { script: stack.replace(digitVerification) } }
},
State { Item {
name: "WaitingForKeys" state: flow.state
StateChangeScript { script: stack.replace(waiting) } states: [
}, State {
State { name: "PromptStartVerification"
name: "WaitingForOtherToAccept"
StateChangeScript { script: stack.replace(waiting) } StateChangeScript {
}, script: stack.replace(newVerificationRequest)
State { }
name: "WaitingForMac"
StateChangeScript { script: stack.replace(waiting) } },
}, State {
State { name: "CompareEmoji"
name: "Success"
StateChangeScript { script: stack.replace(success) } StateChangeScript {
}, script: stack.replace(emojiVerification)
State { }
name: "Failed"
StateChangeScript { script: stack.replace(failed); } },
} State {
] name: "CompareNumber"
}
StateChangeScript {
script: stack.replace(digitVerification)
}
},
State {
name: "WaitingForKeys"
StateChangeScript {
script: stack.replace(waiting)
}
},
State {
name: "WaitingForOtherToAccept"
StateChangeScript {
script: stack.replace(waiting)
}
},
State {
name: "WaitingForMac"
StateChangeScript {
script: stack.replace(waiting)
}
},
State {
name: "Success"
StateChangeScript {
script: stack.replace(success)
}
},
State {
name: "Failed"
StateChangeScript {
script: stack.replace(failed)
}
}
]
}
} }

@ -1,60 +1,69 @@
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.10 import QtQuick.Controls 2.10
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10
import im.nheko 1.0 import im.nheko 1.0
Pane { Pane {
property string title: qsTr("Verification Code") property string title: qsTr("Verification Code")
ColumnLayout { ColumnLayout {
spacing: 16 spacing: 16
Label {
Layout.maximumWidth: 400 Label {
Layout.fillHeight: true Layout.maximumWidth: 400
Layout.fillWidth: true Layout.fillHeight: true
wrapMode: Text.Wrap Layout.fillWidth: true
text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!") wrapMode: Text.Wrap
color:colors.text text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!")
verticalAlignment: Text.AlignVCenter color: colors.text
} verticalAlignment: Text.AlignVCenter
RowLayout { }
Layout.alignment: Qt.AlignHCenter
Label { RowLayout {
font.pixelSize: Qt.application.font.pixelSize * 2 Layout.alignment: Qt.AlignHCenter
text: flow.sasList[0]
color:colors.text Label {
} font.pixelSize: Qt.application.font.pixelSize * 2
Label { text: flow.sasList[0]
font.pixelSize: Qt.application.font.pixelSize * 2 color: colors.text
text: flow.sasList[1] }
color:colors.text
} Label {
Label { font.pixelSize: Qt.application.font.pixelSize * 2
font.pixelSize: Qt.application.font.pixelSize * 2 text: flow.sasList[1]
text: flow.sasList[2] color: colors.text
color:colors.text }
}
} Label {
RowLayout { font.pixelSize: Qt.application.font.pixelSize * 2
Button { text: flow.sasList[2]
Layout.alignment: Qt.AlignLeft color: colors.text
text: qsTr("They do not match!") }
onClicked: { }
flow.cancel();
dialog.close(); RowLayout {
} Button {
} Layout.alignment: Qt.AlignLeft
Item { text: qsTr("They do not match!")
Layout.fillWidth: true onClicked: {
} flow.cancel();
Button { dialog.close();
Layout.alignment: Qt.AlignRight }
text: qsTr("They match!") }
onClicked: flow.next(); Item {
} Layout.fillWidth: true
} }
}
Button {
Layout.alignment: Qt.AlignRight
text: qsTr("They match!")
onClicked: flow.next()
}
}
}
} }

@ -3,24 +3,31 @@ import QtQuick.Controls 2.10
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10
Rectangle { Rectangle {
color: "red" color: "red"
implicitHeight: Qt.application.font.pixelSize * 4 implicitHeight: Qt.application.font.pixelSize * 4
implicitWidth: col.width implicitWidth: col.width
height: Qt.application.font.pixelSize * 4 height: Qt.application.font.pixelSize * 4
width: col.width width: col.width
ColumnLayout {
id: col ColumnLayout {
anchors.bottom: parent.bottom id: col
property var emoji: emojis.mapping[Math.floor(Math.random()*64)]
Label { property var emoji: emojis.mapping[Math.floor(Math.random() * 64)]
height: font.pixelSize * 2
Layout.alignment: Qt.AlignHCenter anchors.bottom: parent.bottom
text: col.emoji.emoji
font.pixelSize: Qt.application.font.pixelSize * 2 Label {
} height: font.pixelSize * 2
Label { Layout.alignment: Qt.AlignHCenter
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom text: col.emoji.emoji
text: col.emoji.description font.pixelSize: Qt.application.font.pixelSize * 2
} }
}
Label {
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
text: col.emoji.description
}
}
} }

@ -1,140 +1,414 @@
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.10 import QtQuick.Controls 2.10
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10
import im.nheko 1.0 import im.nheko 1.0
Pane { Pane {
property string title: qsTr("Verification Code") property string title: qsTr("Verification Code")
ColumnLayout { ColumnLayout {
spacing: 16 spacing: 16
Label {
Layout.maximumWidth: 400 Label {
Layout.fillHeight: true Layout.maximumWidth: 400
Layout.fillWidth: true Layout.fillHeight: true
wrapMode: Text.Wrap Layout.fillWidth: true
text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!") wrapMode: Text.Wrap
color:colors.text text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!")
verticalAlignment: Text.AlignVCenter color: colors.text
} verticalAlignment: Text.AlignVCenter
RowLayout { }
Layout.alignment: Qt.AlignHCenter
id: emojis RowLayout {
property var mapping: [ id: emojis
{"number": 0, "emoji": "🐶", "description": "Dog", "unicode": "U+1F436"},
{"number": 1, "emoji": "🐱", "description": "Cat", "unicode": "U+1F431"}, property var mapping: [{
{"number": 2, "emoji": "🦁", "description": "Lion", "unicode": "U+1F981"}, "number": 0,
{"number": 3, "emoji": "🐎", "description": "Horse", "unicode": "U+1F40E"}, "emoji": "🐶",
{"number": 4, "emoji": "🦄", "description": "Unicorn", "unicode": "U+1F984"}, "description": "Dog",
{"number": 5, "emoji": "🐷", "description": "Pig", "unicode": "U+1F437"}, "unicode": "U+1F436"
{"number": 6, "emoji": "🐘", "description": "Elephant", "unicode": "U+1F418"}, }, {
{"number": 7, "emoji": "🐰", "description": "Rabbit", "unicode": "U+1F430"}, "number": 1,
{"number": 8, "emoji": "🐼", "description": "Panda", "unicode": "U+1F43C"}, "emoji": "🐱",
{"number": 9, "emoji": "🐓", "description": "Rooster", "unicode": "U+1F413"}, "description": "Cat",
{"number": 10, "emoji": "🐧", "description": "Penguin", "unicode": "U+1F427"}, "unicode": "U+1F431"
{"number": 11, "emoji": "🐢", "description": "Turtle", "unicode": "U+1F422"}, }, {
{"number": 12, "emoji": "🐟", "description": "Fish", "unicode": "U+1F41F"}, "number": 2,
{"number": 13, "emoji": "🐙", "description": "Octopus", "unicode": "U+1F419"}, "emoji": "🦁",
{"number": 14, "emoji": "🦋", "description": "Butterfly", "unicode": "U+1F98B"}, "description": "Lion",
{"number": 15, "emoji": "🌷", "description": "Flower", "unicode": "U+1F337"}, "unicode": "U+1F981"
{"number": 16, "emoji": "🌳", "description": "Tree", "unicode": "U+1F333"}, }, {
{"number": 17, "emoji": "🌵", "description": "Cactus", "unicode": "U+1F335"}, "number": 3,
{"number": 18, "emoji": "🍄", "description": "Mushroom", "unicode": "U+1F344"}, "emoji": "🐎",
{"number": 19, "emoji": "🌏", "description": "Globe", "unicode": "U+1F30F"}, "description": "Horse",
{"number": 20, "emoji": "🌙", "description": "Moon", "unicode": "U+1F319"}, "unicode": "U+1F40E"
{"number": 21, "emoji": "☁", "description": "Cloud", "unicode": "U+2601U+FE0F"}, }, {
{"number": 22, "emoji": "🔥", "description": "Fire", "unicode": "U+1F525"}, "number": 4,
{"number": 23, "emoji": "🍌", "description": "Banana", "unicode": "U+1F34C"}, "emoji": "🦄",
{"number": 24, "emoji": "🍎", "description": "Apple", "unicode": "U+1F34E"}, "description": "Unicorn",
{"number": 25, "emoji": "🍓", "description": "Strawberry", "unicode": "U+1F353"}, "unicode": "U+1F984"
{"number": 26, "emoji": "🌽", "description": "Corn", "unicode": "U+1F33D"}, }, {
{"number": 27, "emoji": "🍕", "description": "Pizza", "unicode": "U+1F355"}, "number": 5,
{"number": 28, "emoji": "🎂", "description": "Cake", "unicode": "U+1F382"}, "emoji": "🐷",
{"number": 29, "emoji": "❤", "description": "Heart", "unicode": "U+2764U+FE0F"}, "description": "Pig",
{"number": 30, "emoji": "😀", "description": "Smiley", "unicode": "U+1F600"}, "unicode": "U+1F437"
{"number": 31, "emoji": "🤖", "description": "Robot", "unicode": "U+1F916"}, }, {
{"number": 32, "emoji": "🎩", "description": "Hat", "unicode": "U+1F3A9"}, "number": 6,
{"number": 33, "emoji": "👓", "description": "Glasses", "unicode": "U+1F453"}, "emoji": "🐘",
{"number": 34, "emoji": "🔧", "description": "Spanner", "unicode": "U+1F527"}, "description": "Elephant",
{"number": 35, "emoji": "🎅", "description": "Santa", "unicode": "U+1F385"}, "unicode": "U+1F418"
{"number": 36, "emoji": "👍", "description": "Thumbs Up", "unicode": "U+1F44D"}, }, {
{"number": 37, "emoji": "☂", "description": "Umbrella", "unicode": "U+2602U+FE0F"}, "number": 7,
{"number": 38, "emoji": "⌛", "description": "Hourglass", "unicode": "U+231B"}, "emoji": "🐰",
{"number": 39, "emoji": "⏰", "description": "Clock", "unicode": "U+23F0"}, "description": "Rabbit",
{"number": 40, "emoji": "🎁", "description": "Gift", "unicode": "U+1F381"}, "unicode": "U+1F430"
{"number": 41, "emoji": "💡", "description": "Light Bulb", "unicode": "U+1F4A1"}, }, {
{"number": 42, "emoji": "📕", "description": "Book", "unicode": "U+1F4D5"}, "number": 8,
{"number": 43, "emoji": "✏", "description": "Pencil", "unicode": "U+270FU+FE0F"}, "emoji": "🐼",
{"number": 44, "emoji": "📎", "description": "Paperclip", "unicode": "U+1F4CE"}, "description": "Panda",
{"number": 45, "emoji": "✂", "description": "Scissors", "unicode": "U+2702U+FE0F"}, "unicode": "U+1F43C"
{"number": 46, "emoji": "🔒", "description": "Lock", "unicode": "U+1F512"}, }, {
{"number": 47, "emoji": "🔑", "description": "Key", "unicode": "U+1F511"}, "number": 9,
{"number": 48, "emoji": "🔨", "description": "Hammer", "unicode": "U+1F528"}, "emoji": "🐓",
{"number": 49, "emoji": "☎", "description": "Telephone", "unicode": "U+260EU+FE0F"}, "description": "Rooster",
{"number": 50, "emoji": "🏁", "description": "Flag", "unicode": "U+1F3C1"}, "unicode": "U+1F413"
{"number": 51, "emoji": "🚂", "description": "Train", "unicode": "U+1F682"}, }, {
{"number": 52, "emoji": "🚲", "description": "Bicycle", "unicode": "U+1F6B2"}, "number": 10,
{"number": 53, "emoji": "✈", "description": "Aeroplane", "unicode": "U+2708U+FE0F"}, "emoji": "🐧",
{"number": 54, "emoji": "🚀", "description": "Rocket", "unicode": "U+1F680"}, "description": "Penguin",
{"number": 55, "emoji": "🏆", "description": "Trophy", "unicode": "U+1F3C6"}, "unicode": "U+1F427"
{"number": 56, "emoji": "⚽", "description": "Ball", "unicode": "U+26BD"}, }, {
{"number": 57, "emoji": "🎸", "description": "Guitar", "unicode": "U+1F3B8"}, "number": 11,
{"number": 58, "emoji": "🎺", "description": "Trumpet", "unicode": "U+1F3BA"}, "emoji": "🐢",
{"number": 59, "emoji": "🔔", "description": "Bell", "unicode": "U+1F514"}, "description": "Turtle",
{"number": 60, "emoji": "⚓", "description": "Anchor", "unicode": "U+2693"}, "unicode": "U+1F422"
{"number": 61, "emoji": "🎧", "description": "Headphones", "unicode": "U+1F3A7"}, }, {
{"number": 62, "emoji": "📁", "description": "Folder", "unicode": "U+1F4C1"}, "number": 12,
{"number": 63, "emoji": "📌", "description": "Pin", "unicode": "U+1F4CC"} "emoji": "🐟",
] "description": "Fish",
Repeater { "unicode": "U+1F41F"
id: repeater }, {
model: 7 "number": 13,
delegate: Rectangle { "emoji": "🐙",
color: "transparent" "description": "Octopus",
implicitHeight: Qt.application.font.pixelSize * 8 "unicode": "U+1F419"
implicitWidth: col.width }, {
ColumnLayout { "number": 14,
id: col "emoji": "🦋",
Layout.fillWidth: true "description": "Butterfly",
anchors.bottom: parent.bottom "unicode": "U+1F98B"
property var emoji: emojis.mapping[flow.sasList[index]] }, {
Label { "number": 15,
//height: font.pixelSize * 2 "emoji": "🌷",
Layout.alignment: Qt.AlignHCenter "description": "Flower",
text: col.emoji.emoji "unicode": "U+1F337"
font.pixelSize: Qt.application.font.pixelSize * 2 }, {
font.family: Settings.emojiFont "number": 16,
color:colors.text "emoji": "🌳",
} "description": "Tree",
Label { "unicode": "U+1F333"
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom }, {
text: col.emoji.description "number": 17,
color:colors.text "emoji": "🌵",
} "description": "Cactus",
} "unicode": "U+1F335"
} }, {
} "number": 18,
} "emoji": "🍄",
RowLayout { "description": "Mushroom",
Button { "unicode": "U+1F344"
Layout.alignment: Qt.AlignLeft }, {
text: qsTr("They do not match!") "number": 19,
"emoji": "🌏",
onClicked: { "description": "Globe",
flow.cancel(); "unicode": "U+1F30F"
dialog.close(); }, {
} "number": 20,
} "emoji": "🌙",
Item { "description": "Moon",
Layout.fillWidth: true "unicode": "U+1F319"
} }, {
Button { "number": 21,
Layout.alignment: Qt.AlignRight "emoji": "☁",
text: qsTr("They match!") "description": "Cloud",
"unicode": "U+2601U+FE0F"
onClicked: flow.next() }, {
} "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
Label {
//height: font.pixelSize * 2
Layout.alignment: Qt.AlignHCenter
text: col.emoji.emoji
font.pixelSize: Qt.application.font.pixelSize * 2
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 2.3
import QtQuick.Controls 2.10 import QtQuick.Controls 2.10
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10
import im.nheko 1.0 import im.nheko 1.0
Pane { Pane {
property string title: qsTr("Verification failed") property string title: qsTr("Verification failed")
ColumnLayout {
spacing: 16 ColumnLayout {
Text { spacing: 16
id: content
Text {
Layout.maximumWidth: 400 id: content
Layout.fillHeight: true
Layout.fillWidth: true Layout.maximumWidth: 400
Layout.fillHeight: true
wrapMode: Text.Wrap Layout.fillWidth: true
text: switch (flow.error) { wrapMode: Text.Wrap
case DeviceVerificationFlow.UnknownMethod: return qsTr("Other client does not support our verification protocol.") text: {
case DeviceVerificationFlow.MismatchedCommitment: switch (flow.error) {
case DeviceVerificationFlow.MismatchedSAS: case DeviceVerificationFlow.UnknownMethod:
case DeviceVerificationFlow.KeyMismatch: return qsTr("Key mismatch detected!") return qsTr("Other client does not support our verification protocol.");
case DeviceVerificationFlow.Timeout: return qsTr("Device verification timed out.") case DeviceVerificationFlow.MismatchedCommitment:
case DeviceVerificationFlow.User: return qsTr("Other party canceled the verification.") case DeviceVerificationFlow.MismatchedSAS:
case DeviceVerificationFlow.OutOfOrder: return qsTr("Device verification timed out.") case DeviceVerificationFlow.KeyMismatch:
default: return "Unknown verification error."; return qsTr("Key mismatch detected!");
} case DeviceVerificationFlow.Timeout:
color:colors.text return qsTr("Device verification timed out.");
verticalAlignment: Text.AlignVCenter case DeviceVerificationFlow.User:
} return qsTr("Other party canceled the verification.");
RowLayout { case DeviceVerificationFlow.OutOfOrder:
Item { return qsTr("Device verification timed out.");
Layout.fillWidth: true default:
} return "Unknown verification error.";
Button { }
Layout.alignment: Qt.AlignRight }
text: qsTr("Close") color: colors.text
verticalAlignment: Text.AlignVCenter
onClicked: dialog.close() }
}
} RowLayout {
} Item {
Layout.fillWidth: true
}
Button {
Layout.alignment: Qt.AlignRight
text: qsTr("Close")
onClicked: dialog.close()
}
}
}
} }

@ -1,44 +1,46 @@
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.10 import QtQuick.Controls 2.10
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10
import im.nheko 1.0 import im.nheko 1.0
Pane { Pane {
property string title: flow.sender ? qsTr("Send Device Verification Request") : qsTr("Recieved Device Verification Request") property string title: flow.sender ? qsTr("Send Device Verification Request") : qsTr("Recieved Device Verification Request")
ColumnLayout { ColumnLayout {
spacing: 16 spacing: 16
Label {
Layout.maximumWidth: 400 Label {
Layout.fillHeight: true Layout.maximumWidth: 400
Layout.fillWidth: true Layout.fillHeight: true
wrapMode: Text.Wrap Layout.fillWidth: true
text: flow.sender ? wrapMode: Text.Wrap
qsTr("To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device.") 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")
: qsTr("The device was requested to be verified") color: colors.text
color:colors.text verticalAlignment: Text.AlignVCenter
verticalAlignment: Text.AlignVCenter }
}
RowLayout { RowLayout {
Button { Button {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: flow.sender ? qsTr("Cancel") : qsTr("Deny") text: flow.sender ? qsTr("Cancel") : qsTr("Deny")
onClicked: {
onClicked: { flow.cancel();
flow.cancel(); dialog.close();
dialog.close(); }
} }
}
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
Button {
Layout.alignment: Qt.AlignRight Button {
text: flow.sender ? qsTr("Start verification") : qsTr("Accept") Layout.alignment: Qt.AlignRight
text: flow.sender ? qsTr("Start verification") : qsTr("Accept")
onClicked: flow.next(); onClicked: flow.next()
} }
}
} }
}
} }

@ -3,29 +3,36 @@ import QtQuick.Controls 2.10
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10
Pane { Pane {
property string title: qsTr("Successful Verification") property string title: qsTr("Successful Verification")
ColumnLayout {
spacing: 16 ColumnLayout {
Label { spacing: 16
Layout.maximumWidth: 400
Layout.fillHeight: true Label {
Layout.fillWidth: true id: content
wrapMode: Text.Wrap
id: content Layout.maximumWidth: 400
text: qsTr("Verification successful! Both sides verified their devices!") Layout.fillHeight: true
color:colors.text Layout.fillWidth: true
verticalAlignment: Text.AlignVCenter wrapMode: Text.Wrap
} text: qsTr("Verification successful! Both sides verified their devices!")
RowLayout { color: colors.text
Item { verticalAlignment: Text.AlignVCenter
Layout.fillWidth: true }
}
Button { RowLayout {
Layout.alignment: Qt.AlignRight Item {
text: qsTr("Close") Layout.fillWidth: true
}
onClicked: dialog.close();
} Button {
} Layout.alignment: Qt.AlignRight
} text: qsTr("Close")
onClicked: dialog.close()
}
}
}
} }

@ -1,45 +1,56 @@
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.10 import QtQuick.Controls 2.10
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10
import im.nheko 1.0 import im.nheko 1.0
Pane { Pane {
property string title: qsTr("Waiting for other party") property string title: qsTr("Waiting for other party")
ColumnLayout {
spacing: 16 ColumnLayout {
Label { spacing: 16
Layout.maximumWidth: 400
Layout.fillHeight: true Label {
Layout.fillWidth: true id: content
wrapMode: Text.Wrap
id: content Layout.maximumWidth: 400
text: switch (flow.state) { Layout.fillHeight: true
case "WaitingForOtherToAccept": return qsTr("Waiting for other side to accept the verification request.") Layout.fillWidth: true
case "WaitingForKeys": return qsTr("Waiting for other side to continue the verification request.") wrapMode: Text.Wrap
case "WaitingForMac": return qsTr("Waiting for other side to complete the verification request.") text: {
} switch (flow.state) {
case "WaitingForOtherToAccept":
color: colors.text return qsTr("Waiting for other side to accept the verification request.");
verticalAlignment: Text.AlignVCenter case "WaitingForKeys":
} return qsTr("Waiting for other side to continue the verification request.");
BusyIndicator { case "WaitingForMac":
Layout.alignment: Qt.AlignHCenter return qsTr("Waiting for other side to complete the verification request.");
palette: colors }
} }
RowLayout { color: colors.text
Button { verticalAlignment: Text.AlignVCenter
Layout.alignment: Qt.AlignLeft }
text: qsTr("Cancel")
BusyIndicator {
onClicked: { Layout.alignment: Qt.AlignHCenter
flow.cancel(); palette: colors
dialog.close(); }
}
} RowLayout {
Item { Button {
Layout.fillWidth: true 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 2.10
import QtQuick.Controls 2.1 import QtQuick.Controls 2.1
import im.nheko 1.0 import im.nheko 1.0
import im.nheko.EmojiModel 1.0 import im.nheko.EmojiModel 1.0
import "../"
ImageButton { ImageButton {
id: emojiButton
property var colors: currentActivePalette property var colors: currentActivePalette
property var emojiPicker property var emojiPicker
property string event_id property string event_id
image: ":/icons/icons/ui/smile.png" image: ":/icons/icons/ui/smile.png"
id: emojiButton
onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.show(emojiButton, event_id) 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 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import im.nheko 1.0 import im.nheko 1.0
import im.nheko.EmojiModel 1.0 import im.nheko.EmojiModel 1.0
import "../"
Popup { Popup {
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()
}
property string event_id property string event_id
property var colors property var colors
@ -30,19 +18,28 @@ Popup {
property real highlightSat: colors.highlight.hslSaturation property real highlightSat: colors.highlight.hslSaturation
property real highlightLight: colors.highlight.hslLightness 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 margins: 0
bottomPadding: 1 bottomPadding: 1
leftPadding: 1 leftPadding: 1
rightPadding: 1 rightPadding: 1
modal: true modal: true
focus: true focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
ColumnLayout { ColumnLayout {
id: columnView id: columnView
anchors.fill: parent anchors.fill: parent
spacing: 0 spacing: 0
Layout.bottomMargin: 0 Layout.bottomMargin: 0
@ -58,23 +55,41 @@ Popup {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.leftMargin: 4 Layout.leftMargin: 4
cellWidth: 52 cellWidth: 52
cellHeight: 52 cellHeight: 52
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
clip: true clip: true
// Individual emoji // Individual emoji
delegate: AbstractButton { delegate: AbstractButton {
width: 48 width: 48
height: 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 { contentItem: Text {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
font.family: Settings.emojiFont font.family: Settings.emojiFont
font.pixelSize: 36 font.pixelSize: 36
text: model.unicode text: model.unicode
} }
@ -85,76 +100,66 @@ Popup {
radius: 5 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 // Search field
header: TextField { header: TextField {
id: emojiSearch id: emojiSearch
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: emojiScroll.width + 4 anchors.rightMargin: emojiScroll.width + 4
placeholderText: qsTr("Search") placeholderText: qsTr("Search")
selectByMouse: true selectByMouse: true
rightPadding: clearSearch.width rightPadding: clearSearch.width
onTextChanged: searchTimer.restart()
onVisibleChanged: {
if (visible)
forceActiveFocus();
}
Timer { Timer {
id: searchTimer id: searchTimer
interval: 350 // tweak as needed? interval: 350 // tweak as needed?
onTriggered: { onTriggered: {
emojiPopup.model.filter = emojiSearch.text emojiPopup.model.filter = emojiSearch.text;
emojiPopup.model.category = EmojiCategory.Search emojiPopup.model.category = EmojiCategory.Search;
} }
} }
ToolButton { ToolButton {
id: clearSearch 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 { anchors {
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
right: parent.right right: parent.right
} }
// clear the default hover effects. // clear the default hover effects.
background: Item {}
visible: emojiSearch.text !== '' background: Item {
icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.png?" + (clearSearch.hovered ? colors.highlight : colors.buttonText) }
focusPolicy: Qt.NoFocus
onClicked: emojiSearch.clear()
} }
onTextChanged: searchTimer.restart()
onVisibleChanged: if (visible) forceActiveFocus()
} }
ScrollBar.vertical: ScrollBar { ScrollBar.vertical: ScrollBar {
id: emojiScroll id: emojiScroll
} }
} }
// Separator // Separator
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 1 Layout.preferredHeight: 1
color: emojiPopup.colors.dark color: emojiPopup.colors.dark
} }
@ -164,23 +169,90 @@ Popup {
Layout.preferredHeight: 42 Layout.preferredHeight: 42
implicitHeight: 42 implicitHeight: 42
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
// Display the normal categories // Display the normal categories
Repeater { Repeater {
model: ListModel { model: ListModel {
// TODO: Would like to get 'simple' icons for the categories // TODO: Would like to get 'simple' icons for the categories
ListElement { image: ":/icons/icons/emoji-categories/people.png"; category: EmojiCategory.People } ListElement {
ListElement { image: ":/icons/icons/emoji-categories/nature.png"; category: EmojiCategory.Nature } image: ":/icons/icons/emoji-categories/people.png"
ListElement { image: ":/icons/icons/emoji-categories/foods.png"; category: EmojiCategory.Food } category: EmojiCategory.People
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 {
ListElement { image: ":/icons/icons/emoji-categories/symbols.png"; category: EmojiCategory.Symbols } image: ":/icons/icons/emoji-categories/nature.png"
ListElement { image: ":/icons/icons/emoji-categories/flags.png"; category: EmojiCategory.Flags } 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 { delegate: AbstractButton {
Layout.preferredWidth: 36 Layout.preferredWidth: 36
Layout.preferredHeight: 36 Layout.preferredHeight: 36
hoverEnabled: true
ToolTip.text: {
switch (model.category) {
case EmojiCategory.People:
return qsTr('People');
case EmojiCategory.Nature:
return qsTr('Nature');
case EmojiCategory.Food:
return qsTr('Food');
case EmojiCategory.Activity:
return qsTr('Activity');
case EmojiCategory.Travel:
return qsTr('Travel');
case EmojiCategory.Objects:
return qsTr('Objects');
case EmojiCategory.Symbols:
return qsTr('Symbols');
case EmojiCategory.Flags:
return qsTr('Flags');
}
}
ToolTip.visible: hovered
onClicked: {
emojiPopup.model.category = model.category;
}
MouseArea {
id: mouseArea
anchors.fill: parent
onPressed: mouse.accepted = false
cursorShape: Qt.PointingHandCursor
}
contentItem: Image { contentItem: Image {
horizontalAlignment: Image.AlignHCenter horizontalAlignment: Image.AlignHCenter
@ -191,49 +263,15 @@ Popup {
source: "image://colorimage/" + model.image + "?" + (hovered ? colors.highlight : colors.buttonText) 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 { background: Rectangle {
anchors.fill: parent anchors.fill: parent
color: emojiPopup.model.category === model.category ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.2) : 'transparent'
color: emojiPopup.model.category === model.category ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.20) : 'transparent'
radius: 5 radius: 5
border.color: emojiPopup.model.category === model.category ? colors.highlight : 'transparent' border.color: emojiPopup.model.category === model.category ? colors.highlight : 'transparent'
} }
hoverEnabled: true
ToolTip.text: {
switch (model.category) {
case EmojiCategory.People:
return qsTr('People');
case EmojiCategory.Nature:
return qsTr('Nature');
case EmojiCategory.Food:
return qsTr('Food');
case EmojiCategory.Activity:
return qsTr('Activity');
case EmojiCategory.Travel:
return qsTr('Travel');
case EmojiCategory.Objects:
return qsTr('Objects');
case EmojiCategory.Symbols:
return qsTr('Symbols');
case EmojiCategory.Flags:
return qsTr('Flags');
}
}
ToolTip.visible: hovered
onClicked: {
emojiPopup.model.category = model.category
}
} }
} }
// Separator // Separator
@ -242,30 +280,37 @@ Popup {
Layout.preferredWidth: 1 Layout.preferredWidth: 1
implicitWidth: 1 implicitWidth: 1
height: parent.height height: parent.height
color: emojiPopup.colors.dark color: emojiPopup.colors.dark
} }
// Search Button is special // Search Button is special
AbstractButton { AbstractButton {
id: searchBtn id: searchBtn
hoverEnabled: true hoverEnabled: true
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.bottomMargin: 0 Layout.bottomMargin: 0
ToolTip.text: qsTr("Search") ToolTip.text: qsTr("Search")
ToolTip.visible: hovered ToolTip.visible: hovered
onClicked: { onClicked: {
// clear any filters // clear any filters
emojiPopup.model.category = EmojiCategory.Search emojiPopup.model.category = EmojiCategory.Search;
gridView.positionViewAtBeginning() gridView.positionViewAtBeginning();
emojiSearch.forceActiveFocus() emojiSearch.forceActiveFocus();
} }
Layout.preferredWidth: 36 Layout.preferredWidth: 36
Layout.preferredHeight: 36 Layout.preferredHeight: 36
implicitWidth: 36 implicitWidth: 36
implicitHeight: 36 implicitHeight: 36
MouseArea {
id: mouseArea
anchors.fill: parent
onPressed: mouse.accepted = false
cursorShape: Qt.PointingHandCursor
}
contentItem: Image { contentItem: Image {
anchors.right: parent.right anchors.right: parent.right
horizontalAlignment: Image.AlignHCenter horizontalAlignment: Image.AlignHCenter
@ -277,14 +322,10 @@ Popup {
source: "image://colorimage/:/icons/icons/ui/search.png?" + (parent.hovered ? colors.highlight : colors.buttonText) 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…
Cancel
Save