Lint qml with qml-format

This commit is contained in:
Nicolas Werner 2020-10-08 21:11:21 +02:00
parent 517a126a44
commit 1a029112d9
31 changed files with 2863 additions and 2179 deletions

View file

@ -1,17 +1,18 @@
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 visible: TimelineManager.callState != WebRTCState.DISCONNECTED
color: "#2ECC71" color: "#2ECC71"
implicitHeight: rowLayout.height + 8 implicitHeight: rowLayout.height + 8
RowLayout { RowLayout {
id: rowLayout id: rowLayout
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -20,7 +21,6 @@ Rectangle {
Avatar { Avatar {
width: avatarSize width: avatarSize
height: avatarSize height: avatarSize
url: TimelineManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") url: TimelineManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
displayName: TimelineManager.callPartyName displayName: TimelineManager.callPartyName
} }
@ -38,50 +38,54 @@ Rectangle {
Label { Label {
id: callStateLabel id: callStateLabel
font.pointSize: fontMetrics.font.pointSize * 1.1 font.pointSize: fontMetrics.font.pointSize * 1.1
} }
Connections { Connections {
target: TimelineManager
function onCallStateChanged(state) { function onCallStateChanged(state) {
switch (state) { switch (state) {
case WebRTCState.INITIATING: case WebRTCState.INITIATING:
callStateLabel.text = qsTr("Initiating...") callStateLabel.text = qsTr("Initiating...");
break; break;
case WebRTCState.OFFERSENT: case WebRTCState.OFFERSENT:
callStateLabel.text = qsTr("Calling...") callStateLabel.text = qsTr("Calling...");
break; break;
case WebRTCState.CONNECTING: case WebRTCState.CONNECTING:
callStateLabel.text = qsTr("Connecting...") callStateLabel.text = qsTr("Connecting...");
break; break;
case WebRTCState.CONNECTED: case WebRTCState.CONNECTED:
callStateLabel.text = "00:00" callStateLabel.text = "00:00";
var d = new Date() var d = new Date();
callTimer.startTime = Math.floor(d.getTime() / 1000) callTimer.startTime = Math.floor(d.getTime() / 1000);
break; break;
case WebRTCState.DISCONNECTED: case WebRTCState.DISCONNECTED:
callStateLabel.text = "" callStateLabel.text = "";
} }
} }
target: TimelineManager
} }
Timer { Timer {
id: callTimer id: callTimer
property int startTime property int startTime
function pad(n) {
return (n < 10) ? ("0" + n) : n;
}
interval: 1000 interval: 1000
running: TimelineManager.callState == WebRTCState.CONNECTED running: TimelineManager.callState == WebRTCState.CONNECTED
repeat: true repeat: true
onTriggered: { onTriggered: {
var d = new Date() var d = new Date();
let seconds = Math.floor(d.getTime() / 1000 - startTime) let seconds = Math.floor(d.getTime() / 1000 - startTime);
let s = Math.floor(seconds % 60) let s = Math.floor(seconds % 60);
let m = Math.floor(seconds / 60) % 60 let m = Math.floor(seconds / 60) % 60;
let h = Math.floor(seconds / 3600) let h = Math.floor(seconds / 3600);
callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s) callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s);
}
function pad(n) {
return (n < 10) ? ("0" + n) : n
} }
} }
@ -93,19 +97,17 @@ Rectangle {
width: 24 width: 24
height: 24 height: 24
buttonTextColor: "#000000" buttonTextColor: "#000000"
image: TimelineManager.isMicMuted ? image: TimelineManager.isMicMuted ? ":/icons/icons/ui/microphone-unmute.png" : ":/icons/icons/ui/microphone-mute.png"
":/icons/icons/ui/microphone-unmute.png" :
":/icons/icons/ui/microphone-mute.png"
hoverEnabled: true hoverEnabled: true
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: TimelineManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic") ToolTip.text: TimelineManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic")
onClicked: TimelineManager.toggleMicMute() onClicked: TimelineManager.toggleMicMute()
} }
Item { Item {
implicitWidth: 16 implicitWidth: 16
} }
} }
} }

View file

@ -1,24 +1,25 @@
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
radius: Settings.avatarCircles ? height/2 : 3
property alias url: img.source property alias url: img.source
property string userid property string userid
property string displayName property string displayName
width: 48
height: 48
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
@ -27,23 +28,25 @@ Rectangle {
Image { Image {
id: img id: img
anchors.fill: parent anchors.fill: parent
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
mipmap: true mipmap: true
smooth: false smooth: false
sourceSize.width: avatar.width sourceSize.width: avatar.width
sourceSize.height: avatar.height sourceSize.height: avatar.height
layer.enabled: true layer.enabled: true
layer.effect: OpacityMask { layer.effect: OpacityMask {
maskSource: Rectangle { maskSource: Rectangle {
anchors.fill: parent anchors.fill: parent
width: avatar.width width: avatar.width
height: avatar.height height: avatar.height
radius: Settings.avatarCircles ? height/2 : 3 radius: Settings.avatarCircles ? height / 2 : 3
} }
} }
} }
@ -51,19 +54,22 @@ Rectangle {
Rectangle { Rectangle {
anchors.bottom: avatar.bottom anchors.bottom: avatar.bottom
anchors.right: avatar.right anchors.right: avatar.right
visible: !!userid visible: !!userid
height: avatar.height / 6 height: avatar.height / 6
width: height width: height
radius: Settings.avatarCircles ? height / 2 : height / 4 radius: Settings.avatarCircles ? height / 2 : height / 4
color: switch (TimelineManager.userPresence(userid)) { color: {
case "online": return "#00cc66" switch (TimelineManager.userPresence(userid)) {
case "unavailable": return "#ff9933" case "online":
case "offline": // return "#a82353" don't show anything if offline, since it is confusing, if presence is disabled return "#00cc66";
default: "transparent" 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
} }

View file

@ -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
property bool encrypted: false
function getEncryptionImage() {
if (encrypted)
return "image://colorimage/:/icons/icons/ui/lock.png?" + colors.buttonText;
else
return "image://colorimage/:/icons/icons/ui/unlock.png?#dd3d3d";
}
function getEncryptionTooltip() {
if (encrypted)
return qsTr("Encrypted");
else
return qsTr("This message is not encrypted!");
}
color: "transparent" color: "transparent"
width: 16 width: 16
height: 16 height: 16
ToolTip.visible: ma.containsMouse && indicator.visible ToolTip.visible: ma.containsMouse && indicator.visible
ToolTip.text: getEncryptionTooltip() ToolTip.text: getEncryptionTooltip()
MouseArea{ MouseArea {
id: ma id: ma
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
} }
Image { Image {
id: stateImg id: stateImg
anchors.fill: parent anchors.fill: parent
source: getEncryptionImage() source: getEncryptionImage()
} }
function getEncryptionImage() {
if (encrypted)
return "image://colorimage/:/icons/icons/ui/lock.png?"+colors.buttonText
else
return "image://colorimage/:/icons/icons/ui/unlock.png?#dd3d3d"
}
function getEncryptionTooltip() {
if (encrypted)
return qsTr("Encrypted")
else
return qsTr("This message is not encrypted!")
}
} }

View file

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

View file

@ -1,6 +1,5 @@
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 {
@ -10,26 +9,29 @@ TextEdit {
selectByMouse: true selectByMouse: true
activeFocusOnPress: false activeFocusOnPress: false
color: colors.text color: colors.text
onLinkActivated: { onLinkActivated: {
if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) chat.model.openUserProfile(/^https:\/\/matrix.to\/#\/(@.*)$/.exec(link)[1]) if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) {
else if (/^https:\/\/matrix.to\/#\/(![^\/]*)$/.test(link)) TimelineManager.setHistoryView(/^https:\/\/matrix.to\/#\/(!.*)$/.exec(link)[1]) chat.model.openUserProfile(/^https:\/\/matrix.to\/#\/(@.*)$/.exec(link)[1]);
else if (/^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.test(link)) { } else if (/^https:\/\/matrix.to\/#\/(![^\/]*)$/.test(link)) {
var match = /^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.exec(link) TimelineManager.setHistoryView(/^https:\/\/matrix.to\/#\/(!.*)$/.exec(link)[1]);
TimelineManager.setHistoryView(match[1]) } else if (/^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.test(link)) {
chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain) var match = /^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.exec(link);
TimelineManager.setHistoryView(match[1]);
chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain);
} else {
TimelineManager.openLink(link);
} }
else TimelineManager.openLink(link)
} }
MouseArea ToolTip.visible: hoveredLink
{ ToolTip.text: hoveredLink
MouseArea {
id: ma id: ma
anchors.fill: parent anchors.fill: parent
propagateComposedEvents: true propagateComposedEvents: true
acceptedButtons: Qt.NoButton acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
} }
ToolTip.visible: hoveredLink
ToolTip.text: hoveredLink
} }

View file

@ -1,6 +1,5 @@
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
@ -12,41 +11,38 @@ Flow {
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 { Repeater {
id: repeater id: repeater
delegate: AbstractButton { delegate: AbstractButton {
id: reaction id: reaction
hoverEnabled: true
implicitWidth: contentItem.childrenRect.width + contentItem.leftPadding*2
implicitHeight: contentItem.childrenRect.height
hoverEnabled: true
implicitWidth: contentItem.childrenRect.width + contentItem.leftPadding * 2
implicitHeight: contentItem.childrenRect.height
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: modelData.users ToolTip.text: modelData.users
onClicked: { onClicked: {
console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + ". selfReactedEvent: " + modelData.selfReactedEvent) console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + ". selfReactedEvent: " + modelData.selfReactedEvent);
TimelineManager.queueReactionMessage(reactionFlow.eventId, modelData.key) TimelineManager.queueReactionMessage(reactionFlow.eventId, modelData.key);
} }
contentItem: Row { contentItem: Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: reactionText.implicitHeight/4 spacing: reactionText.implicitHeight / 4
leftPadding: reactionText.implicitHeight / 2 leftPadding: reactionText.implicitHeight / 2
rightPadding: reactionText.implicitHeight / 2 rightPadding: reactionText.implicitHeight / 2
TextMetrics { TextMetrics {
id: textMetrics id: textMetrics
font.family: Settings.emojiFont font.family: Settings.emojiFont
elide: Text.ElideRight elide: Text.ElideRight
elideWidth: 150 elideWidth: 150
@ -54,8 +50,9 @@ Flow {
} }
Text { Text {
anchors.baseline: reactionCounter.baseline
id: reactionText id: reactionText
anchors.baseline: reactionCounter.baseline
text: textMetrics.elidedText + (textMetrics.elidedText == modelData.key ? "" : "…") text: textMetrics.elidedText + (textMetrics.elidedText == modelData.key ? "" : "…")
font.family: Settings.emojiFont font.family: Settings.emojiFont
color: reaction.hovered ? colors.highlight : colors.text color: reaction.hovered ? colors.highlight : colors.text
@ -64,31 +61,35 @@ Flow {
Rectangle { Rectangle {
id: divider 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 {
anchors.verticalCenter: divider.verticalCenter
id: reactionCounter 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 { background: Rectangle {
anchors.centerIn: parent 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
} }
}
}
}
}
}
}

View file

@ -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();
} }
}
} }

View file

@ -4,36 +4,54 @@ import im.nheko 1.0
Rectangle { Rectangle {
id: indicator id: indicator
property int state: 0 property int state: 0
color: "transparent" color: "transparent"
width: 16 width: 16
height: 16 height: 16
ToolTip.visible: ma.containsMouse && state != MtxEvent.Empty ToolTip.visible: ma.containsMouse && state != MtxEvent.Empty
ToolTip.text: switch (state) { ToolTip.text: {
case MtxEvent.Failed: return qsTr("Failed") switch (state) {
case MtxEvent.Sent: return qsTr("Sent") case MtxEvent.Failed:
case MtxEvent.Received: return qsTr("Received") return qsTr("Failed");
case MtxEvent.Read: return qsTr("Read") case MtxEvent.Sent:
default: return "" return qsTr("Sent");
case MtxEvent.Received:
return qsTr("Received");
case MtxEvent.Read:
return qsTr("Read");
default:
return "";
} }
MouseArea{ }
MouseArea {
id: ma id: ma
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
} }
Image { Image {
id: stateImg id: stateImg
// Workaround, can't get icon.source working for now... // Workaround, can't get icon.source working for now...
anchors.fill: parent anchors.fill: parent
source: switch (indicator.state) { source: {
case MtxEvent.Failed: return "image://colorimage/:/icons/icons/ui/remove-symbol.png?" + colors.buttonText switch (indicator.state) {
case MtxEvent.Sent: return "image://colorimage/:/icons/icons/ui/clock.png?" + colors.buttonText case MtxEvent.Failed:
case MtxEvent.Received: return "image://colorimage/:/icons/icons/ui/checkmark.png?" + colors.buttonText return "image://colorimage/:/icons/icons/ui/remove-symbol.png?" + colors.buttonText;
case MtxEvent.Read: return "image://colorimage/:/icons/icons/ui/double-tick-indicator.png?" + colors.buttonText case MtxEvent.Sent:
default: return "" 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 "";
}
} }
} }
}
}

View file

@ -1,13 +1,11 @@
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
@ -18,20 +16,22 @@ Item {
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 { Rectangle {
color: (Settings.messageHoverHighlight && parent.containsMouse) ? colors.base : "transparent" color: (Settings.messageHoverHighlight && parent.containsMouse) ? colors.base : "transparent"
anchors.fill: row anchors.fill: row
} }
RowLayout { RowLayout {
id: row id: row
@ -39,7 +39,6 @@ Item {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
Column { Column {
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
@ -48,7 +47,7 @@ Item {
// fancy reply, if this is a reply // fancy reply, if this is a reply
Reply { Reply {
visible: model.replyTo visible: model.replyTo
modelData: chat.model.getDump(model.replyTo,model.id) modelData: chat.model.getDump(model.replyTo, model.id)
userColor: TimelineManager.userColor(modelData.userId, colors.window) userColor: TimelineManager.userColor(modelData.userId, colors.window)
} }
@ -57,15 +56,16 @@ Item {
id: contentItem id: contentItem
width: parent.width width: parent.width
modelData: model modelData: model
} }
Reactions { Reactions {
id: reactionRow id: reactionRow
reactions: model.reactions reactions: model.reactions
eventId: model.id eventId: model.id
} }
} }
StatusIndicator { StatusIndicator {
@ -82,65 +82,67 @@ Item {
Layout.preferredHeight: 16 Layout.preferredHeight: 16
width: 16 width: 16
} }
EmojiButton { EmojiButton {
id: reactButton
visible: Settings.buttonsInTimeline visible: Settings.buttonsInTimeline
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16 Layout.preferredHeight: 16
width: 16 width: 16
id: reactButton
hoverEnabled: true hoverEnabled: true
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("React") ToolTip.text: qsTr("React")
emojiPicker: emojiPopup emojiPicker: emojiPopup
event_id: model.id event_id: model.id
} }
ImageButton { ImageButton {
id: replyButton
visible: Settings.buttonsInTimeline visible: Settings.buttonsInTimeline
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16 Layout.preferredHeight: 16
width: 16 width: 16
id: replyButton
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/mail-reply.png" image: ":/icons/icons/ui/mail-reply.png"
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Reply") ToolTip.text: qsTr("Reply")
onClicked: chat.model.replyAction(model.id) onClicked: chat.model.replyAction(model.id)
} }
ImageButton { ImageButton {
id: optionsButton
visible: Settings.buttonsInTimeline visible: Settings.buttonsInTimeline
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16 Layout.preferredHeight: 16
width: 16 width: 16
id: optionsButton
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/vertical-ellipsis.png" image: ":/icons/icons/ui/vertical-ellipsis.png"
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Options") ToolTip.text: qsTr("Options")
onClicked: messageContextMenu.show(model.id, model.type, model.isEncrypted, optionsButton) onClicked: messageContextMenu.show(model.id, model.type, model.isEncrypted, optionsButton)
} }
Label { Label {
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
text: model.timestamp.toLocaleTimeString("HH:mm") text: model.timestamp.toLocaleTimeString("HH:mm")
width: Math.max(implicitWidth, text.length*fontMetrics.maximumCharacterWidth) width: Math.max(implicitWidth, text.length * fontMetrics.maximumCharacterWidth)
color: inactiveColors.text color: inactiveColors.text
ToolTip.visible: ma.containsMouse
ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate)
MouseArea{ MouseArea {
id: ma id: ma
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
propagateComposedEvents: true propagateComposedEvents: true
} }
ToolTip.visible: ma.containsMouse
ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate)
} }
} }
} }

View file

@ -1,21 +1,19 @@
import "./delegates"
import "./device-verification"
import "./emoji"
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.2 import QtQuick.Layouts 1.2
import QtGraphicalEffects 1.0
import QtQuick.Window 2.2 import QtQuick.Window 2.2
import im.nheko 1.0 import im.nheko 1.0
import im.nheko.EmojiModel 1.0 import im.nheko.EmojiModel 1.0
import "./delegates"
import "./emoji"
import "./device-verification"
Page { Page {
id: timelineRoot id: timelineRoot
property var colors: currentActivePalette property var colors: currentActivePalette
property var systemInactive: SystemPalette { colorGroup: SystemPalette.Disabled } property var systemInactive
property var inactiveColors: currentInactivePalette ? currentInactivePalette : systemInactive property var inactiveColors: currentInactivePalette ? currentInactivePalette : systemInactive
property int avatarSize: 40 property int avatarSize: 40
property real highlightHue: colors.highlight.hslHue property real highlightHue: colors.highlight.hslHue
@ -30,69 +28,83 @@ Page {
EmojiPicker { EmojiPicker {
id: emojiPopup id: emojiPopup
width: 7 * 52 + 20 width: 7 * 52 + 20
height: 6 * 52 height: 6 * 52
colors: palette colors: palette
model: EmojiProxyModel { model: EmojiProxyModel {
category: EmojiCategory.People category: EmojiCategory.People
sourceModel: EmojiModel {}
sourceModel: EmojiModel {
} }
}
} }
Menu { Menu {
id: messageContextMenu id: messageContextMenu
modal: true
function show(eventId_, eventType_, isEncrypted_, showAt_, position) {
eventId = eventId_
eventType = eventType_
isEncrypted = isEncrypted_
if (position)
popup(position, showAt_)
else
popup(showAt_)
}
property string eventId property string eventId
property int eventType property int eventType
property bool isEncrypted property bool isEncrypted
function show(eventId_, eventType_, isEncrypted_, showAt_, position) {
eventId = eventId_;
eventType = eventType_;
isEncrypted = isEncrypted_;
if (position)
popup(position, showAt_);
else
popup(showAt_);
}
modal: true
MenuItem { MenuItem {
text: qsTr("React") text: qsTr("React")
onClicked: emojiPopup.show(messageContextMenu.parent, messageContextMenu.eventId) onClicked: emojiPopup.show(messageContextMenu.parent, messageContextMenu.eventId)
} }
MenuItem { MenuItem {
text: qsTr("Reply") text: qsTr("Reply")
onClicked: chat.model.replyAction(messageContextMenu.eventId) onClicked: chat.model.replyAction(messageContextMenu.eventId)
} }
MenuItem { MenuItem {
text: qsTr("Read receipts") text: qsTr("Read receipts")
onTriggered: chat.model.readReceiptsAction(messageContextMenu.eventId) onTriggered: chat.model.readReceiptsAction(messageContextMenu.eventId)
} }
MenuItem { MenuItem {
text: qsTr("Mark as read") text: qsTr("Mark as read")
} }
MenuItem { MenuItem {
text: qsTr("View raw message") text: qsTr("View raw message")
onTriggered: chat.model.viewRawMessage(messageContextMenu.eventId) onTriggered: chat.model.viewRawMessage(messageContextMenu.eventId)
} }
MenuItem { MenuItem {
visible: messageContextMenu.isEncrypted visible: messageContextMenu.isEncrypted
height: visible ? implicitHeight : 0 height: visible ? implicitHeight : 0
text: qsTr("View decrypted raw message") text: qsTr("View decrypted raw message")
onTriggered: chat.model.viewDecryptedRawMessage(messageContextMenu.eventId) onTriggered: chat.model.viewDecryptedRawMessage(messageContextMenu.eventId)
} }
MenuItem { MenuItem {
text: qsTr("Redact message") text: qsTr("Redact message")
onTriggered: chat.model.redactEvent(messageContextMenu.eventId) onTriggered: chat.model.redactEvent(messageContextMenu.eventId)
} }
MenuItem { MenuItem {
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
height: visible ? implicitHeight : 0 height: visible ? implicitHeight : 0
text: qsTr("Save as") text: qsTr("Save as")
onTriggered: TimelineManager.timeline.saveMedia(messageContextMenu.eventId) onTriggered: TimelineManager.timeline.saveMedia(messageContextMenu.eventId)
} }
} }
Rectangle { Rectangle {
@ -101,21 +113,32 @@ Page {
Component { Component {
id: deviceVerificationDialog id: deviceVerificationDialog
DeviceVerification {}
DeviceVerification {
} }
}
Connections { Connections {
target: TimelineManager function onNewDeviceVerificationRequest(flow, transactionId, userId, deviceId, isRequest) {
function onNewDeviceVerificationRequest(flow,transactionId,userId,deviceId,isRequest) { var dialog = deviceVerificationDialog.createObject(timelineRoot, {
var dialog = deviceVerificationDialog.createObject(timelineRoot, {flow: flow}); "flow": flow
});
dialog.show(); dialog.show();
} }
target: TimelineManager
} }
Connections { Connections {
target: TimelineManager.timeline
function onOpenProfile(profile) { function onOpenProfile(profile) {
var userProfile = userProfileComponent.createObject(timelineRoot,{profile: profile}); var userProfile = userProfileComponent.createObject(timelineRoot, {
"profile": profile
});
userProfile.show(); userProfile.show();
} }
target: TimelineManager.timeline
} }
Label { Label {
@ -137,21 +160,23 @@ Page {
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
Rectangle { Rectangle {
id: topBar id: topBar
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: topLayout.height + 16 implicitHeight: topLayout.height + 16
z: 3 z: 3
color: colors.base color: colors.base
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: TimelineManager.openRoomSettings(); onClicked: TimelineManager.openRoomSettings()
} }
GridLayout { GridLayout {
//Layout.margins: 8
id: topLayout id: topLayout
anchors.left: parent.left anchors.left: parent.left
@ -159,8 +184,6 @@ Page {
anchors.margins: 8 anchors.margins: 8
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
//Layout.margins: 8
ImageButton { ImageButton {
id: backToRoomsButton id: backToRoomsButton
@ -168,14 +191,10 @@ Page {
Layout.row: 0 Layout.row: 0
Layout.rowSpan: 2 Layout.rowSpan: 2
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: TimelineManager.isNarrowView visible: TimelineManager.isNarrowView
image: ":/icons/icons/ui/angle-pointing-to-left.png" image: ":/icons/icons/ui/angle-pointing-to-left.png"
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Back to room list") ToolTip.text: qsTr("Back to room list")
onClicked: TimelineManager.backToRooms() onClicked: TimelineManager.backToRooms()
} }
@ -184,17 +203,16 @@ Page {
Layout.row: 0 Layout.row: 0
Layout.rowSpan: 2 Layout.rowSpan: 2
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
width: avatarSize width: avatarSize
height: avatarSize height: avatarSize
url: chat.model ? chat.model.roomAvatarUrl.replace("mxc://", "image://MxcImage/") : "" url: chat.model ? chat.model.roomAvatarUrl.replace("mxc://", "image://MxcImage/") : ""
displayName: chat.model ? chat.model.roomName : qsTr("No room selected") displayName: chat.model ? chat.model.roomName : qsTr("No room selected")
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: TimelineManager.openRoomSettings(); onClicked: TimelineManager.openRoomSettings()
} }
} }
Label { Label {
@ -202,23 +220,22 @@ Page {
Layout.column: 2 Layout.column: 2
Layout.row: 0 Layout.row: 0
color: colors.text color: colors.text
font.pointSize: fontMetrics.font.pointSize * 1.1 font.pointSize: fontMetrics.font.pointSize * 1.1
text: chat.model ? chat.model.roomName : qsTr("No room selected") text: chat.model ? chat.model.roomName : qsTr("No room selected")
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: TimelineManager.openRoomSettings(); onClicked: TimelineManager.openRoomSettings()
} }
} }
MatrixText { MatrixText {
Layout.fillWidth: true Layout.fillWidth: true
Layout.column: 2 Layout.column: 2
Layout.row: 1 Layout.row: 1
Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
clip: true clip: true
text: chat.model ? chat.model.roomTopic : "" text: chat.model ? chat.model.roomTopic : ""
} }
@ -229,141 +246,116 @@ Page {
Layout.row: 0 Layout.row: 0
Layout.rowSpan: 2 Layout.rowSpan: 2
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
image: ":/icons/icons/ui/vertical-ellipsis.png" image: ":/icons/icons/ui/vertical-ellipsis.png"
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Room options") ToolTip.text: qsTr("Room options")
onClicked: roomOptionsMenu.popup(roomOptionsButton) onClicked: roomOptionsMenu.popup(roomOptionsButton)
Menu { Menu {
id: roomOptionsMenu id: roomOptionsMenu
MenuItem { MenuItem {
text: qsTr("Invite users") text: qsTr("Invite users")
onTriggered: TimelineManager.openInviteUsersDialog(); onTriggered: TimelineManager.openInviteUsersDialog()
} }
MenuItem { MenuItem {
text: qsTr("Members") text: qsTr("Members")
onTriggered: TimelineManager.openMemberListDialog(); onTriggered: TimelineManager.openMemberListDialog()
} }
MenuItem { MenuItem {
text: qsTr("Leave room") text: qsTr("Leave room")
onTriggered: TimelineManager.openLeaveRoomDialog(); onTriggered: TimelineManager.openLeaveRoomDialog()
} }
MenuItem { MenuItem {
text: qsTr("Settings") text: qsTr("Settings")
onTriggered: TimelineManager.openRoomSettings(); onTriggered: TimelineManager.openRoomSettings()
} }
} }
} }
} }
} }
ListView { ListView {
id: chat id: chat
property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2)
visible: TimelineManager.timeline != null visible: TimelineManager.timeline != null
cacheBuffer: 400 cacheBuffer: 400
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
model: TimelineManager.timeline model: TimelineManager.timeline
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
pixelAligned: true
spacing: 4
verticalLayoutDirection: ListView.BottomToTop
onCountChanged: {
if (atYEnd)
model.currentIndex = 0;
} // Mark last event as read, since we are at the bottom
ScrollHelper { ScrollHelper {
flickable: parent flickable: parent
anchors.fill: parent anchors.fill: parent
} }
pixelAligned: true
Shortcut { Shortcut {
sequence: StandardKey.MoveToPreviousPage sequence: StandardKey.MoveToPreviousPage
onActivated: { chat.contentY = chat.contentY - chat.height / 2; chat.returnToBounds(); } onActivated: {
chat.contentY = chat.contentY - chat.height / 2;
chat.returnToBounds();
} }
}
Shortcut { Shortcut {
sequence: StandardKey.MoveToNextPage sequence: StandardKey.MoveToNextPage
onActivated: { chat.contentY = chat.contentY + chat.height / 2; chat.returnToBounds(); } onActivated: {
chat.contentY = chat.contentY + chat.height / 2;
chat.returnToBounds();
} }
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: chat.model.reply = undefined onActivated: chat.model.reply = undefined
} }
Shortcut { Shortcut {
sequence: "Alt+Up" sequence: "Alt+Up"
onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply? chat.model.idToIndex(chat.model.reply) + 1 : 0) onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0)
} }
Shortcut { Shortcut {
sequence: "Alt+Down" sequence: "Alt+Down"
onActivated: { onActivated: {
var idx = chat.model.reply? chat.model.idToIndex(chat.model.reply) - 1 : -1 var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1;
chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : undefined chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : undefined;
} }
} }
ScrollBar.vertical: ScrollBar { Component {
id: scrollbar
}
spacing: 4
verticalLayoutDirection: ListView.BottomToTop
onCountChanged: if (atYEnd) model.currentIndex = 0 // Mark last event as read, since we are at the bottom
property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width*2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width*2)
delegate: Item {
// This would normally be previousSection, but our model's order is inverted.
property bool sectionBoundary: (ListView.nextSection != "" && ListView.nextSection !== ListView.section) || model.index === chat.count - 1
id: wrapper
property Item section
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
width: chat.delegateMaxWidth
height: section ? section.height + timelinerow.height : timelinerow.height
TimelineRow {
id: timelinerow
y: section ? section.y + section.height : 0
}
onSectionBoundaryChanged: {
if (sectionBoundary) {
var properties = {
'modelData': model.dump,
'section': ListView.section,
'nextSection': ListView.nextSection
}
section = sectionHeader.createObject(wrapper, properties)
} else {
section.destroy()
section = null
}
}
Connections {
target: chat
function onMovementEnded() {
if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
chat.model.currentIndex = index;
}
}
}
Component{
id: userProfileComponent id: userProfileComponent
UserProfile{}
UserProfile {
}
} }
section { section {
property: "section" property: "section"
} }
Component { Component {
id: sectionHeader id: sectionHeader
Column { Column {
property var modelData property var modelData
property string section property string section
@ -372,28 +364,27 @@ Page {
topPadding: 4 topPadding: 4
bottomPadding: 4 bottomPadding: 4
spacing: 8 spacing: 8
visible: !!modelData visible: !!modelData
width: parent.width width: parent.width
height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8 height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
Label { Label {
id: dateBubble id: dateBubble
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
visible: section.includes(" ") visible: section.includes(" ")
text: chat.model.formatDateSeparator(modelData.timestamp) text: chat.model.formatDateSeparator(modelData.timestamp)
color: colors.text color: colors.text
height: fontMetrics.height * 1.4 height: fontMetrics.height * 1.4
width: contentWidth * 1.2 width: contentWidth * 1.2
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
background: Rectangle { background: Rectangle {
radius: parent.height / 2 radius: parent.height / 2
color: colors.base color: colors.base
} }
} }
Row { Row {
@ -413,10 +404,12 @@ Page {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
propagateComposedEvents: true propagateComposedEvents: true
} }
} }
Label { Label {
id: userName id: userName
text: TimelineManager.escapeEmoji(modelData.userName) text: TimelineManager.escapeEmoji(modelData.userName)
color: TimelineManager.userColor(modelData.userId, colors.window) color: TimelineManager.userColor(modelData.userId, colors.window)
textFormat: Text.RichText textFormat: Text.RichText
@ -428,9 +421,59 @@ Page {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
propagateComposedEvents: true propagateComposedEvents: true
} }
}
}
}
}
ScrollBar.vertical: ScrollBar {
id: scrollbar
}
delegate: Item {
id: wrapper
// This would normally be previousSection, but our model's order is inverted.
property bool sectionBoundary: (ListView.nextSection != "" && ListView.nextSection !== ListView.section) || model.index === chat.count - 1
property Item section
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
width: chat.delegateMaxWidth
height: section ? section.height + timelinerow.height : timelinerow.height
onSectionBoundaryChanged: {
if (sectionBoundary) {
var properties = {
"modelData": model.dump,
"section": ListView.section,
"nextSection": ListView.nextSection
};
section = sectionHeader.createObject(wrapper, properties);
} else {
section.destroy();
section = null;
} }
} }
TimelineRow {
id: timelinerow
y: section ? section.y + section.height : 0
} }
Connections {
function onMovementEnded() {
if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
chat.model.currentIndex = index;
}
target: chat
}
} }
footer: BusyIndicator { footer: BusyIndicator {
@ -440,6 +483,7 @@ Page {
width: 50 width: 50
z: 3 z: 3
} }
} }
Item { Item {
@ -451,17 +495,22 @@ Page {
Column { Column {
id: footerContent id: footerContent
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
Rectangle { Rectangle {
id: typingRect id: typingRect
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
color: (chat.model && chat.model.typingUsers.length > 0) ? colors.window : "transparent" color: (chat.model && chat.model.typingUsers.length > 0) ? colors.window : "transparent"
height: typingDisplay.height height: typingDisplay.height
Label { Label {
id: typingDisplay id: typingDisplay
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 10 anchors.leftMargin: 10
anchors.right: parent.right anchors.right: parent.right
@ -470,20 +519,19 @@ Page {
text: chat.model ? chat.model.formatTypingUsers(chat.model.typingUsers, colors.window) : "" text: chat.model ? chat.model.formatTypingUsers(chat.model.typingUsers, colors.window) : ""
textFormat: Text.RichText textFormat: Text.RichText
} }
} }
Rectangle { Rectangle {
anchors.left: parent.left
anchors.right: parent.right
id: replyPopup id: replyPopup
anchors.left: parent.left
anchors.right: parent.right
visible: chat.model && chat.model.reply visible: chat.model && chat.model.reply
// Height of child, plus margins, plus border // Height of child, plus margins, plus border
height: replyPreview.height + 10 height: replyPreview.height + 10
color: colors.base color: colors.base
Reply { Reply {
id: replyPreview id: replyPreview
@ -492,8 +540,8 @@ Page {
anchors.right: closeReplyButton.left anchors.right: closeReplyButton.left
anchors.rightMargin: 20 anchors.rightMargin: 20
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
modelData: chat.model ? chat.model.getDump(chat.model.reply, chat.model.id) : {
modelData: chat.model ? chat.model.getDump(chat.model.reply, chat.model.id) : {} }
userColor: TimelineManager.userColor(modelData.userId, colors.window) userColor: TimelineManager.userColor(modelData.userId, colors.window)
} }
@ -506,21 +554,29 @@ Page {
hoverEnabled: true hoverEnabled: true
width: 16 width: 16
height: 16 height: 16
image: ":/icons/icons/ui/remove-symbol.png" image: ":/icons/icons/ui/remove-symbol.png"
ToolTip.visible: closeReplyButton.hovered ToolTip.visible: closeReplyButton.hovered
ToolTip.text: qsTr("Close") ToolTip.text: qsTr("Close")
onClicked: chat.model.reply = undefined onClicked: chat.model.reply = undefined
} }
} }
} }
} }
ActiveCallBar { ActiveCallBar {
Layout.fillWidth: true Layout.fillWidth: true
z: 3 z: 3
} }
} }
} }
systemInactive: SystemPalette {
colorGroup: SystemPalette.Disabled
}
} }

View file

@ -1,33 +1,33 @@
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
ApplicationWindow{
property var profile property var profile
id: userProfileDialog
height: 650 height: 650
width: 420 width: 420
minimumHeight: 420 minimumHeight: 420
palette: colors palette: colors
Component { Component {
id: deviceVerificationDialog id: deviceVerificationDialog
DeviceVerification {}
DeviceVerification {
} }
ColumnLayout{ }
ColumnLayout {
id: contentL id: contentL
anchors.fill: parent anchors.fill: parent
anchors.margins: 10 anchors.margins: 10
spacing: 10 spacing: 10
Avatar { Avatar {
@ -56,11 +56,11 @@ ApplicationWindow{
Button { Button {
id: verifyUserButton id: verifyUserButton
text: qsTr("Verify") text: qsTr("Verify")
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
enabled: !profile.isUserVerified enabled: !profile.isUserVerified
visible: !profile.isUserVerified visible: !profile.isUserVerified
onClicked: profile.verify() onClicked: profile.verify()
} }
@ -69,13 +69,14 @@ ApplicationWindow{
spacing: 8 spacing: 8
ImageButton { ImageButton {
image:":/icons/icons/ui/do-not-disturb-rounded-sign.png" image: ":/icons/icons/ui/do-not-disturb-rounded-sign.png"
hoverEnabled: true hoverEnabled: true
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Ban the user") ToolTip.text: qsTr("Ban the user")
onClicked: profile.banUser() onClicked: profile.banUser()
} }
// ImageButton{ // ImageButton{
// image:":/icons/icons/ui/volume-off-indicator.png" // image:":/icons/icons/ui/volume-off-indicator.png"
// Layout.margins: { // Layout.margins: {
// left: 5 // left: 5
@ -87,86 +88,88 @@ ApplicationWindow{
// profile.ignoreUser() // profile.ignoreUser()
// } // }
// } // }
ImageButton{ ImageButton {
image:":/icons/icons/ui/black-bubble-speech.png" image: ":/icons/icons/ui/black-bubble-speech.png"
hoverEnabled: true hoverEnabled: true
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Start a private chat") ToolTip.text: qsTr("Start a private chat")
onClicked: profile.startChat() onClicked: profile.startChat()
} }
ImageButton{
image:":/icons/icons/ui/round-remove-button.png" ImageButton {
image: ":/icons/icons/ui/round-remove-button.png"
hoverEnabled: true hoverEnabled: true
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Kick the user") ToolTip.text: qsTr("Kick the user")
onClicked: profile.kickUser() onClicked: profile.kickUser()
} }
} }
ListView{ ListView {
id: devicelist id: devicelist
Layout.fillHeight: true Layout.fillHeight: true
Layout.minimumHeight: 200 Layout.minimumHeight: 200
Layout.fillWidth: true Layout.fillWidth: true
clip: true clip: true
spacing: 8 spacing: 8
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
model: profile.deviceList model: profile.deviceList
delegate: RowLayout{ delegate: RowLayout {
width: devicelist.width width: devicelist.width
spacing: 4 spacing: 4
ColumnLayout{ ColumnLayout {
spacing: 0 spacing: 0
Text{
Text {
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
elide: Text.ElideRight elide: Text.ElideRight
font.bold: true font.bold: true
color: colors.text color: colors.text
text: model.deviceId text: model.deviceId
} }
Text{
Text {
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
elide: Text.ElideRight elide: Text.ElideRight
color: colors.text color: colors.text
text: model.deviceName text: model.deviceName
} }
} }
Image{ Image {
Layout.preferredHeight: 16 Layout.preferredHeight: 16
Layout.preferredWidth: 16 Layout.preferredWidth: 16
source: ((model.verificationStatus == VerificationStatus.VERIFIED) ? "image://colorimage/:/icons/icons/ui/lock.png?green" : ((model.verificationStatus == VerificationStatus.UNVERIFIED) ? "image://colorimage/:/icons/icons/ui/unlock.png?yellow" : "image://colorimage/:/icons/icons/ui/unlock.png?red"))
source: ((model.verificationStatus == VerificationStatus.VERIFIED)?"image://colorimage/:/icons/icons/ui/lock.png?green":
((model.verificationStatus == VerificationStatus.UNVERIFIED)?"image://colorimage/:/icons/icons/ui/unlock.png?yellow":
"image://colorimage/:/icons/icons/ui/unlock.png?red"))
} }
Button{
Button {
id: verifyButton id: verifyButton
text: (model.verificationStatus != VerificationStatus.VERIFIED)?"Verify":"Unverify"
text: (model.verificationStatus != VerificationStatus.VERIFIED) ? "Verify" : "Unverify"
onClicked: { onClicked: {
if(model.verificationStatus == VerificationStatus.VERIFIED){ if (model.verificationStatus == VerificationStatus.VERIFIED)
profile.unverify(model.deviceId) profile.unverify(model.deviceId);
}else{ else
profile.verify(model.deviceId); profile.verify(model.deviceId);
} }
} }
} }
} }
}
} }
footer: DialogButtonBox { footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok standardButtons: DialogButtonBox.Ok
onAccepted: userProfileDialog.close() onAccepted: userProfileDialog.close()
} }
} }

View file

@ -1,6 +1,5 @@
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 {
@ -12,49 +11,57 @@ Item {
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 { Image {
id: img id: img
anchors.centerIn: parent
anchors.centerIn: parent
source: "qrc:/icons/icons/ui/arrow-pointing-down.png" source: "qrc:/icons/icons/ui/arrow-pointing-down.png"
fillMode: Image.Pad fillMode: Image.Pad
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: TimelineManager.timeline.saveMedia(model.data.id) onClicked: TimelineManager.timeline.saveMedia(model.data.id)
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
} }
ColumnLayout { ColumnLayout {
id: col id: col
Text { Text {
id: filename id: filename
Layout.fillWidth: true Layout.fillWidth: true
text: model.data.filename text: model.data.filename
textFormat: Text.PlainText textFormat: Text.PlainText
elide: Text.ElideRight elide: Text.ElideRight
color: colors.text color: colors.text
} }
Text { Text {
id: filesize id: filesize
Layout.fillWidth: true Layout.fillWidth: true
text: model.data.filesize text: model.data.filesize
textFormat: Text.PlainText textFormat: Text.PlainText
elide: Text.ElideRight elide: Text.ElideRight
color: colors.text color: colors.text
} }
} }
} }
Rectangle { Rectangle {

View file

@ -1,11 +1,9 @@
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
@ -14,21 +12,20 @@ Item {
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.width: parent.width
sourceSize.height: parent.height sourceSize.height: parent.height
} }
Image { Image {
id: img id: img
anchors.fill: parent
anchors.fill: parent
source: model.data.url.replace("mxc://", "image://MxcImage/") source: model.data.url.replace("mxc://", "image://MxcImage/")
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
@ -38,5 +35,7 @@ Item {
anchors.fill: parent anchors.fill: parent
onClicked: TimelineManager.openImageOverlay(model.data.url, model.data.id) onClicked: TimelineManager.openImageOverlay(model.data.url, model.data.id)
} }
} }
} }

View file

@ -2,215 +2,334 @@ import QtQuick 2.6
import im.nheko 1.0 import im.nheko 1.0
Item { Item {
property alias modelData: model.data
property alias isReply: model.isReply
property real implicitWidth: (chooser.child && chooser.child.implicitWidth) ? chooser.child.implicitWidth : width
height: chooser.childrenRect.height
// Workaround to have an assignable global property // Workaround to have an assignable global property
Item { Item {
id: model id: model
property var data;
property var data
property bool isReply: false property bool isReply: false
} }
property alias modelData: model.data
property alias isReply: model.isReply
height: chooser.childrenRect.height
property real implicitWidth: (chooser.child && chooser.child.implicitWidth) ? chooser.child.implicitWidth : width
DelegateChooser { DelegateChooser {
id: chooser id: chooser
//role: "type" //< not supported in our custom implementation, have to use roleValue //role: "type" //< not supported in our custom implementation, have to use roleValue
roleValue: model.data.type roleValue: model.data.type
anchors.fill: parent anchors.fill: parent
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.UnknownMessage roleValue: MtxEvent.UnknownMessage
Placeholder { text: "Unretrieved event" }
Placeholder {
text: "Unretrieved event"
} }
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.TextMessage roleValue: MtxEvent.TextMessage
TextMessage {}
TextMessage {
} }
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.NoticeMessage roleValue: MtxEvent.NoticeMessage
NoticeMessage {}
NoticeMessage {
} }
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.EmoteMessage roleValue: MtxEvent.EmoteMessage
NoticeMessage { NoticeMessage {
formatted: TimelineManager.escapeEmoji(modelData.userName) + " " + model.data.formattedBody formatted: TimelineManager.escapeEmoji(modelData.userName) + " " + model.data.formattedBody
color: TimelineManager.userColor(modelData.userId, colors.window) color: TimelineManager.userColor(modelData.userId, colors.window)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.ImageMessage roleValue: MtxEvent.ImageMessage
ImageMessage {}
ImageMessage {
} }
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.Sticker roleValue: MtxEvent.Sticker
ImageMessage {}
ImageMessage {
} }
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.FileMessage roleValue: MtxEvent.FileMessage
FileMessage {}
FileMessage {
} }
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.VideoMessage roleValue: MtxEvent.VideoMessage
PlayableMediaMessage {}
PlayableMediaMessage {
} }
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.AudioMessage roleValue: MtxEvent.AudioMessage
PlayableMediaMessage {}
PlayableMediaMessage {
} }
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.Redacted roleValue: MtxEvent.Redacted
Pill { Pill {
text: qsTr("redacted") text: qsTr("redacted")
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.Redaction roleValue: MtxEvent.Redaction
Pill { Pill {
text: qsTr("redacted") text: qsTr("redacted")
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.Encryption roleValue: MtxEvent.Encryption
Pill { Pill {
text: qsTr("Encryption enabled") text: qsTr("Encryption enabled")
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.Name roleValue: MtxEvent.Name
NoticeMessage { NoticeMessage {
text: model.data.roomName ? qsTr("room name changed to: %1").arg(model.data.roomName) : qsTr("removed room name") text: model.data.roomName ? qsTr("room name changed to: %1").arg(model.data.roomName) : qsTr("removed room name")
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.Topic roleValue: MtxEvent.Topic
NoticeMessage { NoticeMessage {
text: model.data.roomTopic ? qsTr("topic changed to: %1").arg(model.data.roomTopic) : qsTr("removed topic") text: model.data.roomTopic ? qsTr("topic changed to: %1").arg(model.data.roomTopic) : qsTr("removed topic")
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.RoomCreate roleValue: MtxEvent.RoomCreate
NoticeMessage { NoticeMessage {
text: qsTr("%1 created and configured room: %2").arg(model.data.userName).arg(model.data.roomId) text: qsTr("%1 created and configured room: %2").arg(model.data.userName).arg(model.data.roomId)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.CallInvite roleValue: MtxEvent.CallInvite
NoticeMessage { NoticeMessage {
text: switch(model.data.callType) { text: {
case "voice": return qsTr("%1 placed a voice call.").arg(model.data.userName) switch (model.data.callType) {
case "video": return qsTr("%1 placed a video call.").arg(model.data.userName) case "voice":
default: return qsTr("%1 placed a call.").arg(model.data.userName) return qsTr("%1 placed a voice call.").arg(model.data.userName);
case "video":
return qsTr("%1 placed a video call.").arg(model.data.userName);
default:
return qsTr("%1 placed a call.").arg(model.data.userName);
} }
} }
} }
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.CallAnswer roleValue: MtxEvent.CallAnswer
NoticeMessage { NoticeMessage {
text: qsTr("%1 answered the call.").arg(model.data.userName) text: qsTr("%1 answered the call.").arg(model.data.userName)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.CallHangUp roleValue: MtxEvent.CallHangUp
NoticeMessage { NoticeMessage {
text: qsTr("%1 ended the call.").arg(model.data.userName) text: qsTr("%1 ended the call.").arg(model.data.userName)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.CallCandidates roleValue: MtxEvent.CallCandidates
NoticeMessage { NoticeMessage {
text: qsTr("Negotiating call...") text: qsTr("Negotiating call...")
} }
} }
DelegateChoice { DelegateChoice {
// TODO: make a more complex formatter for the power levels. // TODO: make a more complex formatter for the power levels.
roleValue: MtxEvent.PowerLevels roleValue: MtxEvent.PowerLevels
NoticeMessage { NoticeMessage {
text: TimelineManager.timeline.formatPowerLevelEvent(model.data.id) text: TimelineManager.timeline.formatPowerLevelEvent(model.data.id)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.RoomJoinRules roleValue: MtxEvent.RoomJoinRules
NoticeMessage { NoticeMessage {
text: TimelineManager.timeline.formatJoinRuleEvent(model.data.id) text: TimelineManager.timeline.formatJoinRuleEvent(model.data.id)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.RoomHistoryVisibility roleValue: MtxEvent.RoomHistoryVisibility
NoticeMessage { NoticeMessage {
text: TimelineManager.timeline.formatHistoryVisibilityEvent(model.data.id) text: TimelineManager.timeline.formatHistoryVisibilityEvent(model.data.id)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.RoomGuestAccess roleValue: MtxEvent.RoomGuestAccess
NoticeMessage { NoticeMessage {
text: TimelineManager.timeline.formatGuestAccessEvent(model.data.id) text: TimelineManager.timeline.formatGuestAccessEvent(model.data.id)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.Member roleValue: MtxEvent.Member
NoticeMessage { NoticeMessage {
text: TimelineManager.timeline.formatMemberEvent(model.data.id); text: TimelineManager.timeline.formatMemberEvent(model.data.id)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationRequest roleValue: MtxEvent.KeyVerificationRequest
NoticeMessage { NoticeMessage {
text: "KeyVerificationRequest"; text: "KeyVerificationRequest"
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationStart roleValue: MtxEvent.KeyVerificationStart
NoticeMessage { NoticeMessage {
text: "KeyVerificationStart"; text: "KeyVerificationStart"
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationReady roleValue: MtxEvent.KeyVerificationReady
NoticeMessage { NoticeMessage {
text: "KeyVerificationReady"; text: "KeyVerificationReady"
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationCancel roleValue: MtxEvent.KeyVerificationCancel
NoticeMessage { NoticeMessage {
text: "KeyVerificationCancel"; text: "KeyVerificationCancel"
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationKey roleValue: MtxEvent.KeyVerificationKey
NoticeMessage { NoticeMessage {
text: "KeyVerificationKey"; text: "KeyVerificationKey"
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationMac roleValue: MtxEvent.KeyVerificationMac
NoticeMessage { NoticeMessage {
text: "KeyVerificationMac"; text: "KeyVerificationMac"
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationDone roleValue: MtxEvent.KeyVerificationDone
NoticeMessage { NoticeMessage {
text: "KeyVerificationDone"; text: "KeyVerificationDone"
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationDone roleValue: MtxEvent.KeyVerificationDone
NoticeMessage { NoticeMessage {
text: "KeyVerificationDone"; text: "KeyVerificationDone"
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationAccept roleValue: MtxEvent.KeyVerificationAccept
NoticeMessage { NoticeMessage {
text: "KeyVerificationAccept"; text: "KeyVerificationAccept"
} }
} }
DelegateChoice { DelegateChoice {
Placeholder {} Placeholder {
} }
} }
}
} }

View file

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

View file

@ -1,12 +1,12 @@
import QtQuick 2.6
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.1
import QtMultimedia 5.6 import QtMultimedia 5.6
import QtQuick 2.6
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.2
import im.nheko 1.0 import im.nheko 1.0
Rectangle { Rectangle {
id: bg id: bg
radius: 10 radius: 10
color: colors.dark color: colors.dark
height: Math.round(content.height + 24) height: Math.round(content.height + 24)
@ -14,20 +14,22 @@ Rectangle {
Column { Column {
id: content id: content
width: parent.width - 24 width: parent.width - 24
anchors.centerIn: parent anchors.centerIn: parent
Rectangle { Rectangle {
id: videoContainer 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 tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? 400 : 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
visible: model.data.type == MtxEvent.VideoMessage
height: tooHigh ? timelineRoot.height / divisor : tempHeight height: tooHigh ? timelineRoot.height / divisor : tempHeight
width: tooHigh ? (timelineRoot.height / divisor) / model.data.proportionalHeight : tempWidth width: tooHigh ? (timelineRoot.height / divisor) / model.data.proportionalHeight : tempWidth
Image { Image {
anchors.fill: parent anchors.fill: parent
source: model.data.thumbnailUrl.replace("mxc://", "image://MxcImage/") source: model.data.thumbnailUrl.replace("mxc://", "image://MxcImage/")
@ -39,90 +41,136 @@ Rectangle {
fillMode: VideoOutput.PreserveAspectFit fillMode: VideoOutput.PreserveAspectFit
source: media source: media
} }
} }
} }
RowLayout { RowLayout {
width: parent.width width: parent.width
Text { Text {
id: positionText id: positionText
text: "--:--:--" text: "--:--:--"
color: colors.text color: colors.text
} }
Slider {
Layout.fillWidth: true
id: progress
value: media.position
from: 0
to: media.duration
onMoved: media.seek(value) Slider {
id: progress
//indeterminate: true //indeterminate: true
function updatePositionTexts() { function updatePositionTexts() {
function formatTime(date) { function formatTime(date) {
var hh = date.getUTCHours(); var hh = date.getUTCHours();
var mm = date.getUTCMinutes(); var mm = date.getUTCMinutes();
var ss = date.getSeconds(); var ss = date.getSeconds();
if (hh < 10) {hh = "0"+hh;} if (hh < 10)
if (mm < 10) {mm = "0"+mm;} hh = "0" + hh;
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()
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 palette: colors
} }
Text { Text {
id: durationText id: durationText
text: "--:--:--" text: "--:--:--"
color: colors.text color: colors.text
} }
} }
RowLayout { RowLayout {
width: parent.width width: parent.width
spacing: 15 spacing: 15
Rectangle { Rectangle {
id: button id: button
color: colors.window color: colors.window
radius: 22 radius: 22
height: 44 height: 44
width: 44 width: 44
Image { states: [
id: img State {
anchors.centerIn: parent name: "stopped"
z: 3
source: "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?"+colors.text PropertyChanges {
fillMode: Image.Pad 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 { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
switch (button.state) { switch (button.state) {
case "": TimelineManager.timeline.cacheMedia(model.data.id); break; case "":
TimelineManager.timeline.cacheMedia(model.data.id);
break;
case "stopped": case "stopped":
media.play(); console.log("play"); media.play();
button.state = "playing" console.log("play");
break button.state = "playing";
break;
case "playing": case "playing":
media.pause(); console.log("pause"); media.pause();
button.state = "stopped" console.log("pause");
break button.state = "stopped";
break;
} }
} }
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
MediaPlayer { MediaPlayer {
id: media id: media
onError: console.log(errorString) onError: console.log(errorString)
onStatusChanged: if(status == MediaPlayer.Loaded) progress.updatePositionTexts() onStatusChanged: {
if (status == MediaPlayer.Loaded)
progress.updatePositionTexts();
}
onStopped: button.state = "stopped" onStopped: button.state = "stopped"
} }
@ -130,25 +178,16 @@ Rectangle {
target: TimelineManager.timeline target: TimelineManager.timeline
onMediaCached: { onMediaCached: {
if (mxcUrl == model.data.url) { if (mxcUrl == model.data.url) {
media.source = "file://" + cacheUrl media.source = "file://" + cacheUrl;
button.state = "stopped" button.state = "stopped";
console.log("media loaded: " + mxcUrl + " at " + cacheUrl) console.log("media loaded: " + mxcUrl + " at " + cacheUrl);
} }
console.log("media cached: " + mxcUrl + " at " + cacheUrl) console.log("media cached: " + mxcUrl + " at " + cacheUrl);
} }
} }
states: [
State {
name: "stopped"
PropertyChanges { target: img; source: "image://colorimage/:/icons/icons/ui/play-sign.png?"+colors.text }
},
State {
name: "playing"
PropertyChanges { target: img; source: "image://colorimage/:/icons/icons/ui/pause-symbol.png?"+colors.text }
}
]
} }
ColumnLayout { ColumnLayout {
id: col id: col
@ -159,6 +198,7 @@ Rectangle {
elide: Text.ElideRight elide: Text.ElideRight
color: colors.text color: colors.text
} }
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
text: model.data.filesize text: model.data.filesize
@ -166,8 +206,11 @@ Rectangle {
elide: Text.ElideRight elide: Text.ElideRight
color: colors.text color: colors.text
} }
}
}
}
}
}
}
}
}

View file

@ -2,7 +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
Item { Item {
@ -27,18 +26,19 @@ Item {
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) text: TimelineManager.escapeEmoji(reply.modelData.userName)
color: replyComponent.userColor color: replyComponent.userColor
textFormat: Text.RichText textFormat: Text.RichText
@ -48,20 +48,25 @@ Item {
onClicked: chat.model.openUserProfile(reply.modelData.userId) onClicked: chat.model.openUserProfile(reply.modelData.userId)
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
} }
MessageDelegate { MessageDelegate {
id: reply id: reply
width: parent.width width: parent.width
isReply: true isReply: true
} }
} }
Rectangle { Rectangle {
id: backgroundItem id: backgroundItem
z: -1 z: -1
height: replyContainer.height height: replyContainer.height
width: Math.min(Math.max(reply.implicitWidth, userName.implicitWidth) + 8 + 4, parent.width) width: Math.min(Math.max(reply.implicitWidth, userName.implicitWidth) + 8 + 4, parent.width)
color: Qt.rgba(userColor.r, userColor.g, userColor.b, 0.2) color: Qt.rgba(userColor.r, userColor.g, userColor.b, 0.2)
} }
} }

View file

@ -1,10 +1,10 @@
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'>")
text: "<style type=\"text/css\">a { color:" + colors.link + ";}</style>" + formatted.replace("<pre>", "<pre style='white-space: pre-wrap'>")
width: parent ? parent.width : undefined width: parent ? parent.width : undefined
height: isReply ? Math.round(Math.min(timelineRoot.height / 8, implicitHeight)) : undefined height: isReply ? Math.round(Math.min(timelineRoot.height / 8, implicitHeight)) : undefined
clip: true clip: true

View file

@ -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 { ColumnLayout {
spacing: 16 spacing: 16
Label { Label {
id: content
Layout.maximumWidth: 400 Layout.maximumWidth: 400
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap wrapMode: Text.Wrap
id: content
text: qsTr("Waiting for other side to complete verification.") text: qsTr("Waiting for other side to complete verification.")
color:colors.text color: colors.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
BusyIndicator { BusyIndicator {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
} }
RowLayout { RowLayout {
Button { Button {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("Cancel") text: qsTr("Cancel")
onClicked: { onClicked: {
flow.cancel(); flow.cancel();
dialog.close(); dialog.close();
} }
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
} }
} }

View file

@ -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 {
id: dialog
property var flow property var flow
onClosing: TimelineManager.removeVerificationFlow(flow) onClosing: TimelineManager.removeVerificationFlow(flow)
title: stack.currentItem.title title: stack.currentItem.title
id: dialog
flags: Qt.Dialog flags: Qt.Dialog
palette: colors palette: colors
height: stack.implicitHeight height: stack.implicitHeight
width: stack.implicitWidth width: stack.implicitWidth
StackView { StackView {
id: stack id: stack
initialItem: newVerificationRequest initialItem: newVerificationRequest
implicitWidth: currentItem.implicitWidth implicitWidth: currentItem.implicitWidth
implicitHeight: currentItem.implicitHeight implicitHeight: currentItem.implicitHeight
} }
Component{ Component {
id: newVerificationRequest id: newVerificationRequest
NewVerificationRequest {}
NewVerificationRequest {
}
} }
Component { Component {
id: waiting id: waiting
Waiting {}
Waiting {
}
} }
Component { Component {
id: success id: success
Success {}
Success {
}
} }
Component { Component {
id: failed id: failed
Failed {}
Failed {
}
} }
Component { Component {
id: digitVerification id: digitVerification
DigitVerification {}
DigitVerification {
}
} }
Component { Component {
id: emojiVerification id: emojiVerification
EmojiVerification {}
EmojiVerification {
}
} }
Item { Item {
state: flow.state state: flow.state
states: [ states: [
State { State {
name: "PromptStartVerification" name: "PromptStartVerification"
StateChangeScript { script: stack.replace(newVerificationRequest) }
StateChangeScript {
script: stack.replace(newVerificationRequest)
}
}, },
State { State {
name: "CompareEmoji" name: "CompareEmoji"
StateChangeScript { script: stack.replace(emojiVerification) }
StateChangeScript {
script: stack.replace(emojiVerification)
}
}, },
State { State {
name: "CompareNumber" name: "CompareNumber"
StateChangeScript { script: stack.replace(digitVerification) }
StateChangeScript {
script: stack.replace(digitVerification)
}
}, },
State { State {
name: "WaitingForKeys" name: "WaitingForKeys"
StateChangeScript { script: stack.replace(waiting) }
StateChangeScript {
script: stack.replace(waiting)
}
}, },
State { State {
name: "WaitingForOtherToAccept" name: "WaitingForOtherToAccept"
StateChangeScript { script: stack.replace(waiting) }
StateChangeScript {
script: stack.replace(waiting)
}
}, },
State { State {
name: "WaitingForMac" name: "WaitingForMac"
StateChangeScript { script: stack.replace(waiting) }
StateChangeScript {
script: stack.replace(waiting)
}
}, },
State { State {
name: "Success" name: "Success"
StateChangeScript { script: stack.replace(success) }
StateChangeScript {
script: stack.replace(success)
}
}, },
State { State {
name: "Failed" name: "Failed"
StateChangeScript { script: stack.replace(failed); }
StateChangeScript {
script: stack.replace(failed)
}
} }
] ]
} }
} }

View file

@ -1,7 +1,6 @@
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 {
@ -9,52 +8,62 @@ Pane {
ColumnLayout { ColumnLayout {
spacing: 16 spacing: 16
Label { Label {
Layout.maximumWidth: 400 Layout.maximumWidth: 400
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap wrapMode: Text.Wrap
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!") 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!")
color:colors.text color: colors.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
RowLayout { RowLayout {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Label { Label {
font.pixelSize: Qt.application.font.pixelSize * 2 font.pixelSize: Qt.application.font.pixelSize * 2
text: flow.sasList[0] text: flow.sasList[0]
color:colors.text 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[1]
color:colors.text color: colors.text
} }
Label { Label {
font.pixelSize: Qt.application.font.pixelSize * 2 font.pixelSize: Qt.application.font.pixelSize * 2
text: flow.sasList[2] text: flow.sasList[2]
color:colors.text color: colors.text
} }
} }
RowLayout { RowLayout {
Button { Button {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("They do not match!") text: qsTr("They do not match!")
onClicked: { onClicked: {
flow.cancel(); flow.cancel();
dialog.close(); dialog.close();
} }
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
Button { Button {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
text: qsTr("They match!") text: qsTr("They match!")
onClicked: flow.next()
}
onClicked: flow.next();
}
} }
} }
} }

View file

@ -8,19 +8,26 @@ Rectangle {
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 { ColumnLayout {
id: col id: col
property var emoji: emojis.mapping[Math.floor(Math.random() * 64)]
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
property var emoji: emojis.mapping[Math.floor(Math.random()*64)]
Label { Label {
height: font.pixelSize * 2 height: font.pixelSize * 2
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: col.emoji.emoji text: col.emoji.emoji
font.pixelSize: Qt.application.font.pixelSize * 2 font.pixelSize: Qt.application.font.pixelSize * 2
} }
Label { Label {
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
text: col.emoji.description text: col.emoji.description
} }
} }
} }

View file

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

View file

@ -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 { ColumnLayout {
spacing: 16 spacing: 16
Text { Text {
id: content id: content
Layout.maximumWidth: 400 Layout.maximumWidth: 400
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap wrapMode: Text.Wrap
text: switch (flow.error) { text: {
case DeviceVerificationFlow.UnknownMethod: return qsTr("Other client does not support our verification protocol.") switch (flow.error) {
case DeviceVerificationFlow.UnknownMethod:
return qsTr("Other client does not support our verification protocol.");
case DeviceVerificationFlow.MismatchedCommitment: case DeviceVerificationFlow.MismatchedCommitment:
case DeviceVerificationFlow.MismatchedSAS: case DeviceVerificationFlow.MismatchedSAS:
case DeviceVerificationFlow.KeyMismatch: return qsTr("Key mismatch detected!") case DeviceVerificationFlow.KeyMismatch:
case DeviceVerificationFlow.Timeout: return qsTr("Device verification timed out.") return qsTr("Key mismatch detected!");
case DeviceVerificationFlow.User: return qsTr("Other party canceled the verification.") case DeviceVerificationFlow.Timeout:
case DeviceVerificationFlow.OutOfOrder: return qsTr("Device verification timed out.") return qsTr("Device verification timed out.");
default: return "Unknown verification error."; case DeviceVerificationFlow.User:
return qsTr("Other party canceled the verification.");
case DeviceVerificationFlow.OutOfOrder:
return qsTr("Device verification timed out.");
default:
return "Unknown verification error.";
} }
color:colors.text }
color: colors.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
RowLayout { RowLayout {
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
Button { Button {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
text: qsTr("Close") text: qsTr("Close")
onClicked: dialog.close() onClicked: dialog.close()
} }
} }
} }
} }

View file

@ -1,7 +1,6 @@
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 {
@ -9,36 +8,39 @@ Pane {
ColumnLayout { ColumnLayout {
spacing: 16 spacing: 16
Label { Label {
Layout.maximumWidth: 400 Layout.maximumWidth: 400
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap wrapMode: Text.Wrap
text: flow.sender ? 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("To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device.") color: colors.text
: qsTr("The device was requested to be verified")
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 { Button {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
text: flow.sender ? qsTr("Start verification") : qsTr("Accept") text: flow.sender ? qsTr("Start verification") : qsTr("Accept")
onClicked: flow.next()
}
onClicked: flow.next();
}
} }
} }
} }

View file

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

View file

@ -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 { ColumnLayout {
spacing: 16 spacing: 16
Label { Label {
id: content
Layout.maximumWidth: 400 Layout.maximumWidth: 400
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap wrapMode: Text.Wrap
id: content text: {
text: switch (flow.state) { switch (flow.state) {
case "WaitingForOtherToAccept": return qsTr("Waiting for other side to accept the verification request.") case "WaitingForOtherToAccept":
case "WaitingForKeys": return qsTr("Waiting for other side to continue the verification request.") return qsTr("Waiting for other side to accept the verification request.");
case "WaitingForMac": return qsTr("Waiting for other side to complete the verification request.") case "WaitingForKeys":
return qsTr("Waiting for other side to continue the verification request.");
case "WaitingForMac":
return qsTr("Waiting for other side to complete the verification request.");
}
} }
color: colors.text color: colors.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
BusyIndicator { BusyIndicator {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
palette: colors palette: colors
} }
RowLayout { RowLayout {
Button { Button {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("Cancel") text: qsTr("Cancel")
onClicked: { onClicked: {
flow.cancel(); flow.cancel();
dialog.close(); dialog.close();
} }
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
} }
} }

View file

@ -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)
} }

View file

@ -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,49 +169,57 @@ 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
contentItem: Image {
horizontalAlignment: Image.AlignHCenter
verticalAlignment: Image.AlignVCenter
fillMode: Image.Pad
sourceSize.width: 32
sourceSize.height: 32
source: "image://colorimage/" + model.image + "?" + (hovered ? colors.highlight : colors.buttonText)
}
MouseArea
{
id: mouseArea
anchors.fill: parent
onPressed: mouse.accepted = false
cursorShape: Qt.PointingHandCursor
}
background: Rectangle {
anchors.fill: parent
color: emojiPopup.model.category === model.category ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.20) : 'transparent'
radius: 5
border.color: emojiPopup.model.category === model.category ? colors.highlight : 'transparent'
}
hoverEnabled: true hoverEnabled: true
ToolTip.text: { ToolTip.text: {
switch (model.category) { switch (model.category) {
@ -229,11 +242,36 @@ Popup {
} }
} }
ToolTip.visible: hovered ToolTip.visible: hovered
onClicked: { onClicked: {
emojiPopup.model.category = model.category emojiPopup.model.category = model.category;
} }
MouseArea {
id: mouseArea
anchors.fill: parent
onPressed: mouse.accepted = false
cursorShape: Qt.PointingHandCursor
} }
contentItem: Image {
horizontalAlignment: Image.AlignHCenter
verticalAlignment: Image.AlignVCenter
fillMode: Image.Pad
sourceSize.width: 32
sourceSize.height: 32
source: "image://colorimage/" + model.image + "?" + (hovered ? colors.highlight : colors.buttonText)
}
background: Rectangle {
anchors.fill: parent
color: emojiPopup.model.category === model.category ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.2) : 'transparent'
radius: 5
border.color: emojiPopup.model.category === model.category ? colors.highlight : 'transparent'
}
}
} }
// Separator // 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
}
} }
} }
} }
} }