mirror of https://github.com/Nheko-Reborn/nheko
parent
517a126a44
commit
1a029112d9
@ -1,111 +1,113 @@ |
||||
import QtQuick 2.9 |
||||
import QtQuick.Controls 2.3 |
||||
import QtQuick.Layouts 1.2 |
||||
|
||||
import im.nheko 1.0 |
||||
|
||||
Rectangle { |
||||
id: activeCallBar |
||||
visible: TimelineManager.callState != WebRTCState.DISCONNECTED |
||||
color: "#2ECC71" |
||||
implicitHeight: rowLayout.height + 8 |
||||
|
||||
RowLayout { |
||||
id: rowLayout |
||||
anchors.left: parent.left |
||||
anchors.right: parent.right |
||||
anchors.verticalCenter: parent.verticalCenter |
||||
anchors.leftMargin: 8 |
||||
|
||||
Avatar { |
||||
width: avatarSize |
||||
height: avatarSize |
||||
|
||||
url: TimelineManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") |
||||
displayName: TimelineManager.callPartyName |
||||
} |
||||
|
||||
Label { |
||||
font.pointSize: fontMetrics.font.pointSize * 1.1 |
||||
text: " " + TimelineManager.callPartyName + " " |
||||
} |
||||
|
||||
Image { |
||||
Layout.preferredWidth: 24 |
||||
Layout.preferredHeight: 24 |
||||
source: "qrc:/icons/icons/ui/place-call.png" |
||||
} |
||||
|
||||
Label { |
||||
id: callStateLabel |
||||
font.pointSize: fontMetrics.font.pointSize * 1.1 |
||||
} |
||||
|
||||
Connections { |
||||
target: TimelineManager |
||||
function onCallStateChanged(state) { |
||||
switch (state) { |
||||
case WebRTCState.INITIATING: |
||||
callStateLabel.text = qsTr("Initiating...") |
||||
break; |
||||
case WebRTCState.OFFERSENT: |
||||
callStateLabel.text = qsTr("Calling...") |
||||
break; |
||||
case WebRTCState.CONNECTING: |
||||
callStateLabel.text = qsTr("Connecting...") |
||||
break; |
||||
case WebRTCState.CONNECTED: |
||||
callStateLabel.text = "00:00" |
||||
var d = new Date() |
||||
callTimer.startTime = Math.floor(d.getTime() / 1000) |
||||
break; |
||||
case WebRTCState.DISCONNECTED: |
||||
callStateLabel.text = "" |
||||
} |
||||
} |
||||
} |
||||
|
||||
Timer { |
||||
id: callTimer |
||||
property int startTime |
||||
interval: 1000 |
||||
running: TimelineManager.callState == WebRTCState.CONNECTED |
||||
repeat: true |
||||
onTriggered: { |
||||
var d = new Date() |
||||
let seconds = Math.floor(d.getTime() / 1000 - startTime) |
||||
let s = Math.floor(seconds % 60) |
||||
let m = Math.floor(seconds / 60) % 60 |
||||
let h = Math.floor(seconds / 3600) |
||||
callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s) |
||||
} |
||||
|
||||
function pad(n) { |
||||
return (n < 10) ? ("0" + n) : n |
||||
} |
||||
} |
||||
|
||||
Item { |
||||
Layout.fillWidth: true |
||||
} |
||||
|
||||
ImageButton { |
||||
width: 24 |
||||
height: 24 |
||||
buttonTextColor: "#000000" |
||||
image: TimelineManager.isMicMuted ? |
||||
":/icons/icons/ui/microphone-unmute.png" : |
||||
":/icons/icons/ui/microphone-mute.png" |
||||
|
||||
hoverEnabled: true |
||||
ToolTip.visible: hovered |
||||
ToolTip.text: TimelineManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic") |
||||
|
||||
onClicked: TimelineManager.toggleMicMute() |
||||
} |
||||
|
||||
Item { |
||||
implicitWidth: 16 |
||||
} |
||||
} |
||||
id: activeCallBar |
||||
|
||||
visible: TimelineManager.callState != WebRTCState.DISCONNECTED |
||||
color: "#2ECC71" |
||||
implicitHeight: rowLayout.height + 8 |
||||
|
||||
RowLayout { |
||||
id: rowLayout |
||||
|
||||
anchors.left: parent.left |
||||
anchors.right: parent.right |
||||
anchors.verticalCenter: parent.verticalCenter |
||||
anchors.leftMargin: 8 |
||||
|
||||
Avatar { |
||||
width: avatarSize |
||||
height: avatarSize |
||||
url: TimelineManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") |
||||
displayName: TimelineManager.callPartyName |
||||
} |
||||
|
||||
Label { |
||||
font.pointSize: fontMetrics.font.pointSize * 1.1 |
||||
text: " " + TimelineManager.callPartyName + " " |
||||
} |
||||
|
||||
Image { |
||||
Layout.preferredWidth: 24 |
||||
Layout.preferredHeight: 24 |
||||
source: "qrc:/icons/icons/ui/place-call.png" |
||||
} |
||||
|
||||
Label { |
||||
id: callStateLabel |
||||
|
||||
font.pointSize: fontMetrics.font.pointSize * 1.1 |
||||
} |
||||
|
||||
Connections { |
||||
function onCallStateChanged(state) { |
||||
switch (state) { |
||||
case WebRTCState.INITIATING: |
||||
callStateLabel.text = qsTr("Initiating..."); |
||||
break; |
||||
case WebRTCState.OFFERSENT: |
||||
callStateLabel.text = qsTr("Calling..."); |
||||
break; |
||||
case WebRTCState.CONNECTING: |
||||
callStateLabel.text = qsTr("Connecting..."); |
||||
break; |
||||
case WebRTCState.CONNECTED: |
||||
callStateLabel.text = "00:00"; |
||||
var d = new Date(); |
||||
callTimer.startTime = Math.floor(d.getTime() / 1000); |
||||
break; |
||||
case WebRTCState.DISCONNECTED: |
||||
callStateLabel.text = ""; |
||||
} |
||||
} |
||||
|
||||
target: TimelineManager |
||||
} |
||||
|
||||
Timer { |
||||
id: callTimer |
||||
|
||||
property int startTime |
||||
|
||||
function pad(n) { |
||||
return (n < 10) ? ("0" + n) : n; |
||||
} |
||||
|
||||
interval: 1000 |
||||
running: TimelineManager.callState == WebRTCState.CONNECTED |
||||
repeat: true |
||||
onTriggered: { |
||||
var d = new Date(); |
||||
let seconds = Math.floor(d.getTime() / 1000 - startTime); |
||||
let s = Math.floor(seconds % 60); |
||||
let m = Math.floor(seconds / 60) % 60; |
||||
let h = Math.floor(seconds / 3600); |
||||
callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s); |
||||
} |
||||
} |
||||
|
||||
Item { |
||||
Layout.fillWidth: true |
||||
} |
||||
|
||||
ImageButton { |
||||
width: 24 |
||||
height: 24 |
||||
buttonTextColor: "#000000" |
||||
image: TimelineManager.isMicMuted ? ":/icons/icons/ui/microphone-unmute.png" : ":/icons/icons/ui/microphone-mute.png" |
||||
hoverEnabled: true |
||||
ToolTip.visible: hovered |
||||
ToolTip.text: TimelineManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic") |
||||
onClicked: TimelineManager.toggleMicMute() |
||||
} |
||||
|
||||
Item { |
||||
implicitWidth: 16 |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
@ -1,69 +1,75 @@ |
||||
import QtGraphicalEffects 1.0 |
||||
import QtQuick 2.6 |
||||
import QtQuick.Controls 2.3 |
||||
import QtGraphicalEffects 1.0 |
||||
|
||||
import im.nheko 1.0 |
||||
|
||||
Rectangle { |
||||
id: avatar |
||||
width: 48 |
||||
height: 48 |
||||
radius: Settings.avatarCircles ? height/2 : 3 |
||||
id: avatar |
||||
|
||||
property alias url: img.source |
||||
property string userid |
||||
property string displayName |
||||
|
||||
property alias url: img.source |
||||
property string userid |
||||
property string displayName |
||||
width: 48 |
||||
height: 48 |
||||
radius: Settings.avatarCircles ? height / 2 : 3 |
||||
color: colors.base |
||||
|
||||
Label { |
||||
anchors.fill: parent |
||||
text: TimelineManager.escapeEmoji(displayName ? String.fromCodePoint(displayName.codePointAt(0)) : "") |
||||
textFormat: Text.RichText |
||||
font.pixelSize: avatar.height/2 |
||||
verticalAlignment: Text.AlignVCenter |
||||
horizontalAlignment: Text.AlignHCenter |
||||
visible: img.status != Image.Ready |
||||
color: colors.text |
||||
} |
||||
Label { |
||||
anchors.fill: parent |
||||
text: TimelineManager.escapeEmoji(displayName ? String.fromCodePoint(displayName.codePointAt(0)) : "") |
||||
textFormat: Text.RichText |
||||
font.pixelSize: avatar.height / 2 |
||||
verticalAlignment: Text.AlignVCenter |
||||
horizontalAlignment: Text.AlignHCenter |
||||
visible: img.status != Image.Ready |
||||
color: colors.text |
||||
} |
||||
|
||||
Image { |
||||
id: img |
||||
anchors.fill: parent |
||||
asynchronous: true |
||||
fillMode: Image.PreserveAspectCrop |
||||
mipmap: true |
||||
smooth: false |
||||
Image { |
||||
id: img |
||||
|
||||
sourceSize.width: avatar.width |
||||
sourceSize.height: avatar.height |
||||
anchors.fill: parent |
||||
asynchronous: true |
||||
fillMode: Image.PreserveAspectCrop |
||||
mipmap: true |
||||
smooth: false |
||||
sourceSize.width: avatar.width |
||||
sourceSize.height: avatar.height |
||||
layer.enabled: true |
||||
|
||||
layer.enabled: true |
||||
layer.effect: OpacityMask { |
||||
maskSource: Rectangle { |
||||
anchors.fill: parent |
||||
width: avatar.width |
||||
height: avatar.height |
||||
radius: Settings.avatarCircles ? height/2 : 3 |
||||
} |
||||
} |
||||
layer.effect: OpacityMask { |
||||
|
||||
} |
||||
maskSource: Rectangle { |
||||
anchors.fill: parent |
||||
width: avatar.width |
||||
height: avatar.height |
||||
radius: Settings.avatarCircles ? height / 2 : 3 |
||||
} |
||||
|
||||
Rectangle { |
||||
anchors.bottom: avatar.bottom |
||||
anchors.right: avatar.right |
||||
} |
||||
|
||||
visible: !!userid |
||||
} |
||||
|
||||
height: avatar.height / 6 |
||||
width: height |
||||
radius: Settings.avatarCircles ? height / 2 : height / 4 |
||||
color: switch (TimelineManager.userPresence(userid)) { |
||||
case "online": return "#00cc66" |
||||
case "unavailable": return "#ff9933" |
||||
case "offline": // return "#a82353" don't show anything if offline, since it is confusing, if presence is disabled |
||||
default: "transparent" |
||||
} |
||||
} |
||||
Rectangle { |
||||
anchors.bottom: avatar.bottom |
||||
anchors.right: avatar.right |
||||
visible: !!userid |
||||
height: avatar.height / 6 |
||||
width: height |
||||
radius: Settings.avatarCircles ? height / 2 : height / 4 |
||||
color: { |
||||
switch (TimelineManager.userPresence(userid)) { |
||||
case "online": |
||||
return "#00cc66"; |
||||
case "unavailable": |
||||
return "#ff9933"; |
||||
case "offline": |
||||
default: |
||||
// return "#a82353" don't show anything if offline, since it is confusing, if presence is disabled |
||||
"transparent"; |
||||
} |
||||
} |
||||
} |
||||
|
||||
color: colors.base |
||||
} |
||||
|
@ -1,35 +1,37 @@ |
||||
import QtQuick 2.5 |
||||
import QtQuick.Controls 2.3 |
||||
|
||||
import im.nheko 1.0 |
||||
|
||||
TextEdit { |
||||
textFormat: TextEdit.RichText |
||||
readOnly: true |
||||
wrapMode: Text.Wrap |
||||
selectByMouse: true |
||||
activeFocusOnPress: false |
||||
color: colors.text |
||||
textFormat: TextEdit.RichText |
||||
readOnly: true |
||||
wrapMode: Text.Wrap |
||||
selectByMouse: true |
||||
activeFocusOnPress: false |
||||
color: colors.text |
||||
onLinkActivated: { |
||||
if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) { |
||||
chat.model.openUserProfile(/^https:\/\/matrix.to\/#\/(@.*)$/.exec(link)[1]); |
||||
} else if (/^https:\/\/matrix.to\/#\/(![^\/]*)$/.test(link)) { |
||||
TimelineManager.setHistoryView(/^https:\/\/matrix.to\/#\/(!.*)$/.exec(link)[1]); |
||||
} else if (/^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.test(link)) { |
||||
var match = /^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.exec(link); |
||||
TimelineManager.setHistoryView(match[1]); |
||||
chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain); |
||||
} else { |
||||
TimelineManager.openLink(link); |
||||
} |
||||
} |
||||
ToolTip.visible: hoveredLink |
||||
ToolTip.text: hoveredLink |
||||
|
||||
MouseArea { |
||||
id: ma |
||||
|
||||
onLinkActivated: { |
||||
if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) chat.model.openUserProfile(/^https:\/\/matrix.to\/#\/(@.*)$/.exec(link)[1]) |
||||
else if (/^https:\/\/matrix.to\/#\/(![^\/]*)$/.test(link)) TimelineManager.setHistoryView(/^https:\/\/matrix.to\/#\/(!.*)$/.exec(link)[1]) |
||||
else if (/^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.test(link)) { |
||||
var match = /^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.exec(link) |
||||
TimelineManager.setHistoryView(match[1]) |
||||
chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain) |
||||
} |
||||
else TimelineManager.openLink(link) |
||||
} |
||||
MouseArea |
||||
{ |
||||
id: ma |
||||
anchors.fill: parent |
||||
propagateComposedEvents: true |
||||
acceptedButtons: Qt.NoButton |
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor |
||||
} |
||||
anchors.fill: parent |
||||
propagateComposedEvents: true |
||||
acceptedButtons: Qt.NoButton |
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor |
||||
} |
||||
|
||||
ToolTip.visible: hoveredLink |
||||
ToolTip.text: hoveredLink |
||||
} |
||||
|
@ -1,94 +1,95 @@ |
||||
import QtQuick 2.6 |
||||
import QtQuick.Controls 2.2 |
||||
|
||||
import im.nheko 1.0 |
||||
|
||||
// This class is for showing Reactions in the timeline row, not for |
||||
// adding new reactions via the emoji picker |
||||
Flow { |
||||
id: reactionFlow |
||||
|
||||
// highlight colors for selfReactedEvent background |
||||
property real highlightHue: colors.highlight.hslHue |
||||
property real highlightSat: colors.highlight.hslSaturation |
||||
property real highlightLight: colors.highlight.hslLightness |
||||
|
||||
property string eventId |
||||
|
||||
anchors.left: parent.left |
||||
anchors.right: parent.right |
||||
spacing: 4 |
||||
|
||||
property alias reactions: repeater.model |
||||
|
||||
Repeater { |
||||
id: repeater |
||||
|
||||
delegate: AbstractButton { |
||||
id: reaction |
||||
hoverEnabled: true |
||||
implicitWidth: contentItem.childrenRect.width + contentItem.leftPadding*2 |
||||
implicitHeight: contentItem.childrenRect.height |
||||
|
||||
ToolTip.visible: hovered |
||||
ToolTip.text: modelData.users |
||||
|
||||
onClicked: { |
||||
console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + ". selfReactedEvent: " + modelData.selfReactedEvent) |
||||
TimelineManager.queueReactionMessage(reactionFlow.eventId, modelData.key) |
||||
} |
||||
|
||||
|
||||
contentItem: Row { |
||||
anchors.centerIn: parent |
||||
spacing: reactionText.implicitHeight/4 |
||||
leftPadding: reactionText.implicitHeight / 2 |
||||
rightPadding: reactionText.implicitHeight / 2 |
||||
|
||||
TextMetrics { |
||||
id: textMetrics |
||||
font.family: Settings.emojiFont |
||||
elide: Text.ElideRight |
||||
elideWidth: 150 |
||||
text: modelData.key |
||||
} |
||||
|
||||
Text { |
||||
anchors.baseline: reactionCounter.baseline |
||||
id: reactionText |
||||
text: textMetrics.elidedText + (textMetrics.elidedText == modelData.key ? "" : "…") |
||||
font.family: Settings.emojiFont |
||||
color: reaction.hovered ? colors.highlight : colors.text |
||||
maximumLineCount: 1 |
||||
} |
||||
|
||||
Rectangle { |
||||
id: divider |
||||
height: Math.floor(reactionCounter.implicitHeight * 1.4) |
||||
width: 1 |
||||
color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text |
||||
} |
||||
|
||||
Text { |
||||
anchors.verticalCenter: divider.verticalCenter |
||||
id: reactionCounter |
||||
text: modelData.count |
||||
font: reaction.font |
||||
color: reaction.hovered ? colors.highlight : colors.text |
||||
} |
||||
} |
||||
|
||||
background: Rectangle { |
||||
anchors.centerIn: parent |
||||
|
||||
implicitWidth: reaction.implicitWidth |
||||
implicitHeight: reaction.implicitHeight |
||||
border.color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text |
||||
color: modelData.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.20) : colors.base |
||||
border.width: 1 |
||||
radius: reaction.height / 2.0 |
||||
} |
||||
} |
||||
} |
||||
} |
||||
id: reactionFlow |
||||
|
||||
// highlight colors for selfReactedEvent background |
||||
property real highlightHue: colors.highlight.hslHue |
||||
property real highlightSat: colors.highlight.hslSaturation |
||||
property real highlightLight: colors.highlight.hslLightness |
||||
property string eventId |
||||
property alias reactions: repeater.model |
||||
|
||||
anchors.left: parent.left |
||||
anchors.right: parent.right |
||||
spacing: 4 |
||||
|
||||
Repeater { |
||||
id: repeater |
||||
|
||||
delegate: AbstractButton { |
||||
id: reaction |
||||
|
||||
hoverEnabled: true |
||||
implicitWidth: contentItem.childrenRect.width + contentItem.leftPadding * 2 |
||||
implicitHeight: contentItem.childrenRect.height |
||||
ToolTip.visible: hovered |
||||
ToolTip.text: modelData.users |
||||
onClicked: { |
||||
console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + ". selfReactedEvent: " + modelData.selfReactedEvent); |
||||
TimelineManager.queueReactionMessage(reactionFlow.eventId, modelData.key); |
||||
} |
||||
|
||||
contentItem: Row { |
||||
anchors.centerIn: parent |
||||
spacing: reactionText.implicitHeight / 4 |
||||
leftPadding: reactionText.implicitHeight / 2 |
||||
rightPadding: reactionText.implicitHeight / 2 |
||||
|
||||
TextMetrics { |
||||
id: textMetrics |
||||
|
||||
font.family: Settings.emojiFont |
||||
elide: Text.ElideRight |
||||
elideWidth: 150 |
||||
text: modelData.key |
||||
} |
||||
|
||||
Text { |
||||
id: reactionText |
||||
|
||||
anchors.baseline: reactionCounter.baseline |
||||
text: textMetrics.elidedText + (textMetrics.elidedText == modelData.key ? "" : "…") |
||||
font.family: Settings.emojiFont |
||||
color: reaction.hovered ? colors.highlight : colors.text |
||||
maximumLineCount: 1 |
||||
} |
||||
|
||||
Rectangle { |
||||
id: divider |
||||
|
||||
height: Math.floor(reactionCounter.implicitHeight * 1.4) |
||||
width: 1 |
||||
color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text |
||||
} |
||||
|
||||
Text { |
||||
id: reactionCounter |
||||
|
||||
anchors.verticalCenter: divider.verticalCenter |
||||
text: modelData.count |
||||
font: reaction.font |
||||
color: reaction.hovered ? colors.highlight : colors.text |
||||
} |
||||
|
||||
} |
||||
|
||||
background: Rectangle { |
||||
anchors.centerIn: parent |
||||
implicitWidth: reaction.implicitWidth |
||||
implicitHeight: reaction.implicitHeight |
||||
border.color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text |
||||
color: modelData.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.2) : colors.base |
||||
border.width: 1 |
||||
radius: reaction.height / 2 |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
@ -1,146 +1,148 @@ |
||||
import "./delegates" |
||||
import "./emoji" |
||||
import QtQuick 2.6 |
||||
import QtQuick.Controls 2.3 |
||||
import QtQuick.Layouts 1.2 |
||||
import QtQuick.Window 2.2 |
||||
|
||||
import im.nheko 1.0 |
||||
|
||||
import "./delegates" |
||||
import "./emoji" |
||||
|
||||
Item { |
||||
anchors.left: parent.left |
||||
anchors.right: parent.right |
||||
height: row.height |
||||
|
||||
MouseArea { |
||||
anchors.fill: parent |
||||
propagateComposedEvents: true |
||||
preventStealing: true |
||||
hoverEnabled: true |
||||
|
||||
acceptedButtons: Qt.AllButtons |
||||
onClicked: { |
||||
if (mouse.button === Qt.RightButton) |
||||
messageContextMenu.show(model.id, model.type, model.isEncrypted, row) |
||||
} |
||||
onPressAndHold: { |
||||
messageContextMenu.show(model.id, model.type, model.isEncrypted, row, mapToItem(timelineRoot, mouse.x, mouse.y)) |
||||
} |
||||
} |
||||
Rectangle { |
||||
color: (Settings.messageHoverHighlight && parent.containsMouse) ? colors.base : "transparent" |
||||
anchors.fill: row |
||||
} |
||||
RowLayout { |
||||
id: row |
||||
|
||||
anchors.leftMargin: avatarSize + 16 |
||||
anchors.left: parent.left |
||||
anchors.right: parent.right |
||||
|
||||
|
||||
Column { |
||||
Layout.fillWidth: true |
||||
Layout.alignment: Qt.AlignTop |
||||
spacing: 4 |
||||
|
||||
// fancy reply, if this is a reply |
||||
Reply { |
||||
visible: model.replyTo |
||||
modelData: chat.model.getDump(model.replyTo,model.id) |
||||
userColor: TimelineManager.userColor(modelData.userId, colors.window) |
||||
} |
||||
|
||||
// actual message content |
||||
MessageDelegate { |
||||
id: contentItem |
||||
|
||||
width: parent.width |
||||
|
||||
modelData: model |
||||
} |
||||
|
||||
Reactions { |
||||
id: reactionRow |
||||
reactions: model.reactions |
||||
eventId: model.id |
||||
} |
||||
} |
||||
|
||||
StatusIndicator { |
||||
state: model.state |
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop |
||||
Layout.preferredHeight: 16 |
||||
width: 16 |
||||
} |
||||
|
||||
EncryptionIndicator { |
||||
visible: model.isRoomEncrypted |
||||
encrypted: model.isEncrypted |
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop |
||||
Layout.preferredHeight: 16 |
||||
width: 16 |
||||
} |
||||
EmojiButton { |
||||
visible: Settings.buttonsInTimeline |
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop |
||||
Layout.preferredHeight: 16 |
||||
width: 16 |
||||
id: reactButton |
||||
hoverEnabled: true |
||||
ToolTip.visible: hovered |
||||
ToolTip.text: qsTr("React") |
||||
emojiPicker: emojiPopup |
||||
event_id: model.id |
||||
} |
||||
ImageButton { |
||||
visible: Settings.buttonsInTimeline |
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop |
||||
Layout.preferredHeight: 16 |
||||
width: 16 |
||||
id: replyButton |
||||
hoverEnabled: true |
||||
|
||||
|
||||
image: ":/icons/icons/ui/mail-reply.png" |
||||
|
||||
ToolTip.visible: hovered |
||||
ToolTip.text: qsTr("Reply") |
||||
|
||||
onClicked: chat.model.replyAction(model.id) |
||||
} |
||||
ImageButton { |
||||
visible: Settings.buttonsInTimeline |
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop |
||||
Layout.preferredHeight: 16 |
||||
width: 16 |
||||
id: optionsButton |
||||
hoverEnabled: true |
||||
|
||||
image: ":/icons/icons/ui/vertical-ellipsis.png" |
||||
|
||||
ToolTip.visible: hovered |
||||
ToolTip.text: qsTr("Options") |
||||
|
||||
onClicked: messageContextMenu.show(model.id, model.type, model.isEncrypted, optionsButton) |
||||
} |
||||
|
||||
Label { |
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop |
||||
text: model.timestamp.toLocaleTimeString("HH:mm") |
||||
width: Math.max(implicitWidth, text.length*fontMetrics.maximumCharacterWidth) |
||||
color: inactiveColors.text |
||||
|
||||
MouseArea{ |
||||
id: ma |
||||
anchors.fill: parent |
||||
hoverEnabled: true |
||||
propagateComposedEvents: true |
||||
} |
||||
|
||||
ToolTip.visible: ma.containsMouse |
||||
ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate) |
||||
} |
||||
} |
||||
anchors.left: parent.left |
||||
anchors.right: parent.right |
||||
height: row.height |
||||
|
||||
MouseArea { |
||||
anchors.fill: parent |
||||
propagateComposedEvents: true |
||||
preventStealing: true |
||||
hoverEnabled: true |
||||
acceptedButtons: Qt.AllButtons |
||||
onClicked: { |
||||
if (mouse.button === Qt.RightButton) |
||||
messageContextMenu.show(model.id, model.type, model.isEncrypted, row); |
||||
|
||||
} |
||||
onPressAndHold: { |
||||
messageContextMenu.show(model.id, model.type, model.isEncrypted, row, mapToItem(timelineRoot, mouse.x, mouse.y)); |
||||
} |
||||
} |
||||
|
||||
Rectangle { |
||||
color: (Settings.messageHoverHighlight && parent.containsMouse) ? colors.base : "transparent" |
||||
anchors.fill: row |
||||
} |
||||
|
||||
RowLayout { |
||||
id: row |
||||
|
||||
anchors.leftMargin: avatarSize + 16 |
||||
anchors.left: parent.left |
||||
anchors.right: parent.right |
||||
|
||||
Column { |
||||
Layout.fillWidth: true |
||||
Layout.alignment: Qt.AlignTop |
||||
spacing: 4 |
||||
|
||||
// fancy reply, if this is a reply |
||||
Reply { |
||||
visible: model.replyTo |
||||
modelData: chat.model.getDump(model.replyTo, model.id) |
||||
userColor: TimelineManager.userColor(modelData.userId, colors.window) |
||||
} |
||||
|
||||
// actual message content |
||||
MessageDelegate { |
||||
id: contentItem |
||||
|
||||
width: parent.width |
||||
modelData: model |
||||
} |
||||
|
||||
Reactions { |
||||
id: reactionRow |
||||
|
||||
reactions: model.reactions |
||||
eventId: model.id |
||||
} |
||||
|
||||
} |
||||
|
||||
StatusIndicator { |
||||
state: model.state |
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop |
||||
Layout.preferredHeight: 16 |
||||
width: 16 |
||||
} |
||||
|
||||
EncryptionIndicator { |
||||
visible: model.isRoomEncrypted |
||||
encrypted: model.isEncrypted |
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop |
||||
Layout.preferredHeight: 16 |
||||
width: 16 |
||||
} |
||||
|
||||
EmojiButton { |
||||
id: reactButton |
||||
|
||||
visible: Settings.buttonsInTimeline |
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop |
||||
Layout.preferredHeight: 16 |
||||
width: 16 |
||||
hoverEnabled: true |
||||
ToolTip.visible: hovered |
||||
ToolTip.text: qsTr("React") |
||||
emojiPicker: emojiPopup |
||||
event_id: model.id |
||||
} |
||||
|
||||
ImageButton { |
||||
id: replyButton |
||||
|
||||
visible: Settings.buttonsInTimeline |
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop |
||||
Layout.preferredHeight: 16 |
||||
width: 16 |
||||
hoverEnabled: true |
||||
image: ":/icons/icons/ui/mail-reply.png" |
||||
ToolTip.visible: hovered |
||||
ToolTip.text: qsTr("Reply") |
||||
onClicked: chat.model.replyAction(model.id) |
||||
} |
||||
|
||||
ImageButton { |
||||
id: optionsButton |
||||
|
||||
visible: Settings.buttonsInTimeline |
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop |
||||
Layout.preferredHeight: 16 |
||||
width: 16 |
||||
hoverEnabled: true |
||||
image: ":/icons/icons/ui/vertical-ellipsis.png" |
||||
ToolTip.visible: hovered |
||||
ToolTip.text: qsTr("Options") |
||||
onClicked: messageContextMenu.show(model.id, model.type, model.isEncrypted, optionsButton) |
||||
} |
||||
|
||||
Label { |
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop |
||||
text: model.timestamp.toLocaleTimeString("HH:mm") |
||||
width: Math.max(implicitWidth, text.length * fontMetrics.maximumCharacterWidth) |
||||
color: inactiveColors.text |
||||
ToolTip.visible: ma.containsMouse |
||||
ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate) |
||||
|
||||
MouseArea { |
||||
id: ma |
||||
|
||||
anchors.fill: parent |
||||
hoverEnabled: true |
||||
propagateComposedEvents: true |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,172 +1,175 @@ |
||||
import "./device-verification" |
||||
import QtQuick 2.9 |
||||
import QtQuick.Controls 2.3 |
||||
import QtQuick.Layouts 1.2 |
||||
import QtQuick.Window 2.3 |
||||
|
||||
import im.nheko 1.0 |
||||
|
||||
import "./device-verification" |
||||
ApplicationWindow { |
||||
id: userProfileDialog |
||||
|
||||
property var profile |
||||
|
||||
height: 650 |
||||
width: 420 |
||||
minimumHeight: 420 |
||||
palette: colors |
||||
|
||||
Component { |
||||
id: deviceVerificationDialog |
||||
|
||||
DeviceVerification { |
||||
} |
||||
|
||||
} |
||||
|
||||
ColumnLayout { |
||||
id: contentL |
||||
|
||||
anchors.fill: parent |
||||
anchors.margins: 10 |
||||
spacing: 10 |
||||
|
||||
Avatar { |
||||
url: profile.avatarUrl.replace("mxc://", "image://MxcImage/") |
||||
height: 130 |
||||
width: 130 |
||||
displayName: profile.displayName |
||||
userid: profile.userid |
||||
Layout.alignment: Qt.AlignHCenter |
||||
} |
||||
|
||||
Label { |
||||
text: profile.displayName |
||||
fontSizeMode: Text.HorizontalFit |
||||
font.pixelSize: 20 |
||||
color: TimelineManager.userColor(profile.userid, colors.window) |
||||
font.bold: true |
||||
Layout.alignment: Qt.AlignHCenter |
||||
} |
||||
|
||||
MatrixText { |
||||
text: profile.userid |
||||
font.pixelSize: 15 |
||||
Layout.alignment: Qt.AlignHCenter |
||||
} |
||||
|
||||
Button { |
||||
id: verifyUserButton |
||||
|
||||
text: qsTr("Verify") |
||||
Layout.alignment: Qt.AlignHCenter |
||||
enabled: !profile.isUserVerified |
||||
visible: !profile.isUserVerified |
||||
onClicked: profile.verify() |
||||
} |
||||
|
||||
RowLayout { |
||||
Layout.alignment: Qt.AlignHCenter |
||||
spacing: 8 |
||||
|
||||
ImageButton { |
||||
image: ":/icons/icons/ui/do-not-disturb-rounded-sign.png" |
||||
hoverEnabled: true |
||||
ToolTip.visible: hovered |
||||
ToolTip.text: qsTr("Ban the user") |
||||
onClicked: profile.banUser() |
||||
} |
||||
// ImageButton{ |
||||
|
||||
// image:":/icons/icons/ui/volume-off-indicator.png" |
||||
// Layout.margins: { |
||||
// left: 5 |
||||
// right: 5 |
||||
// } |
||||
// ToolTip.visible: hovered |
||||
// ToolTip.text: qsTr("Ignore messages from this user") |
||||
// onClicked : { |
||||
// profile.ignoreUser() |
||||
// } |
||||
// } |
||||
ImageButton { |
||||
image: ":/icons/icons/ui/black-bubble-speech.png" |
||||
hoverEnabled: true |
||||
ToolTip.visible: hovered |
||||
ToolTip.text: qsTr("Start a private chat") |
||||
onClicked: profile.startChat() |
||||
} |
||||
|
||||
ImageButton { |
||||
image: ":/icons/icons/ui/round-remove-button.png" |
||||
hoverEnabled: true |
||||
ToolTip.visible: hovered |
||||
ToolTip.text: qsTr("Kick the user") |
||||
onClicked: profile.kickUser() |
||||
} |
||||
|
||||
} |
||||
|
||||
ListView { |
||||
id: devicelist |
||||
|
||||
Layout.fillHeight: true |
||||
Layout.minimumHeight: 200 |
||||
Layout.fillWidth: true |
||||
clip: true |
||||
spacing: 8 |
||||
boundsBehavior: Flickable.StopAtBounds |
||||
model: profile.deviceList |
||||
|
||||
delegate: RowLayout { |
||||
width: devicelist.width |
||||
spacing: 4 |
||||
|
||||
ColumnLayout { |
||||
spacing: 0 |
||||
|
||||
Text { |
||||
Layout.fillWidth: true |
||||
Layout.alignment: Qt.AlignLeft |
||||
elide: Text.ElideRight |
||||
font.bold: true |
||||
color: colors.text |
||||
text: model.deviceId |
||||
} |
||||
|
||||
Text { |
||||
Layout.fillWidth: true |
||||
Layout.alignment: Qt.AlignRight |
||||
elide: Text.ElideRight |
||||
color: colors.text |
||||
text: model.deviceName |
||||
} |
||||
|
||||
} |
||||
|
||||
Image { |
||||
Layout.preferredHeight: 16 |
||||
Layout.preferredWidth: 16 |
||||
source: ((model.verificationStatus == VerificationStatus.VERIFIED) ? "image://colorimage/:/icons/icons/ui/lock.png?green" : ((model.verificationStatus == VerificationStatus.UNVERIFIED) ? "image://colorimage/:/icons/icons/ui/unlock.png?yellow" : "image://colorimage/:/icons/icons/ui/unlock.png?red")) |
||||
} |
||||
|
||||
Button { |
||||
id: verifyButton |
||||
|
||||
text: (model.verificationStatus != VerificationStatus.VERIFIED) ? "Verify" : "Unverify" |
||||
onClicked: { |
||||
if (model.verificationStatus == VerificationStatus.VERIFIED) |
||||
profile.unverify(model.deviceId); |
||||
else |
||||
profile.verify(model.deviceId); |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
footer: DialogButtonBox { |
||||
standardButtons: DialogButtonBox.Ok |
||||
onAccepted: userProfileDialog.close() |
||||
} |
||||
|
||||
ApplicationWindow{ |
||||
property var profile |
||||
|
||||
id: userProfileDialog |
||||
height: 650 |
||||
width: 420 |
||||
minimumHeight: 420 |
||||
|
||||
palette: colors |
||||
|
||||
Component { |
||||
id: deviceVerificationDialog |
||||
DeviceVerification {} |
||||
} |
||||
|
||||
ColumnLayout{ |
||||
id: contentL |
||||
|
||||
anchors.fill: parent |
||||
anchors.margins: 10 |
||||
|
||||
spacing: 10 |
||||
|
||||
Avatar { |
||||
url: profile.avatarUrl.replace("mxc://", "image://MxcImage/") |
||||
height: 130 |
||||
width: 130 |
||||
displayName: profile.displayName |
||||
userid: profile.userid |
||||
Layout.alignment: Qt.AlignHCenter |
||||
} |
||||
|
||||
Label { |
||||
text: profile.displayName |
||||
fontSizeMode: Text.HorizontalFit |
||||
font.pixelSize: 20 |
||||
color: TimelineManager.userColor(profile.userid, colors.window) |
||||
font.bold: true |
||||
Layout.alignment: Qt.AlignHCenter |
||||
} |
||||
|
||||
MatrixText { |
||||
text: profile.userid |
||||
font.pixelSize: 15 |
||||
Layout.alignment: Qt.AlignHCenter |
||||
} |
||||
|
||||
Button { |
||||
id: verifyUserButton |
||||
text: qsTr("Verify") |
||||
Layout.alignment: Qt.AlignHCenter |
||||
enabled: !profile.isUserVerified |
||||
visible: !profile.isUserVerified |
||||
|
||||
onClicked: profile.verify() |
||||
} |
||||
|
||||
RowLayout { |
||||
Layout.alignment: Qt.AlignHCenter |
||||
spacing: 8 |
||||
|
||||
ImageButton { |
||||
image:":/icons/icons/ui/do-not-disturb-rounded-sign.png" |
||||
hoverEnabled: true |
||||
ToolTip.visible: hovered |
||||
ToolTip.text: qsTr("Ban the user") |
||||
onClicked: profile.banUser() |
||||
} |
||||
// ImageButton{ |
||||
// image:":/icons/icons/ui/volume-off-indicator.png" |
||||
// Layout.margins: { |
||||
// left: 5 |
||||
// right: 5 |
||||
// } |
||||
// ToolTip.visible: hovered |
||||
// ToolTip.text: qsTr("Ignore messages from this user") |
||||
// onClicked : { |
||||
// profile.ignoreUser() |
||||
// } |
||||
// } |
||||
ImageButton{ |
||||
image:":/icons/icons/ui/black-bubble-speech.png" |
||||
hoverEnabled: true |
||||
ToolTip.visible: hovered |
||||
ToolTip.text: qsTr("Start a private chat") |
||||
onClicked: profile.startChat() |
||||
} |
||||
ImageButton{ |
||||
image:":/icons/icons/ui/round-remove-button.png" |
||||
hoverEnabled: true |
||||
ToolTip.visible: hovered |
||||
ToolTip.text: qsTr("Kick the user") |
||||
onClicked: profile.kickUser() |
||||
} |
||||
} |
||||
|
||||
ListView{ |
||||
id: devicelist |
||||
|
||||
Layout.fillHeight: true |
||||
Layout.minimumHeight: 200 |
||||
Layout.fillWidth: true |
||||
|
||||
clip: true |
||||
spacing: 8 |
||||
boundsBehavior: Flickable.StopAtBounds |
||||
|
||||
model: profile.deviceList |
||||
|
||||
delegate: RowLayout{ |
||||
width: devicelist.width |
||||
spacing: 4 |
||||
|
||||
ColumnLayout{ |
||||
spacing: 0 |
||||
Text{ |
||||
Layout.fillWidth: true |
||||
Layout.alignment: Qt.AlignLeft |
||||
|
||||
elide: Text.ElideRight |
||||
font.bold: true |
||||
color: colors.text |
||||
text: model.deviceId |
||||
} |
||||
Text{ |
||||
Layout.fillWidth: true |
||||
Layout.alignment: Qt.AlignRight |
||||
|
||||
elide: Text.ElideRight |
||||
color: colors.text |
||||
text: model.deviceName |
||||
} |
||||
} |
||||
|
||||
Image{ |
||||
Layout.preferredHeight: 16 |
||||
Layout.preferredWidth: 16 |
||||
|
||||
source: ((model.verificationStatus == VerificationStatus.VERIFIED)?"image://colorimage/:/icons/icons/ui/lock.png?green": |
||||
((model.verificationStatus == VerificationStatus.UNVERIFIED)?"image://colorimage/:/icons/icons/ui/unlock.png?yellow": |
||||
"image://colorimage/:/icons/icons/ui/unlock.png?red")) |
||||
} |
||||
Button{ |
||||
id: verifyButton |
||||
text: (model.verificationStatus != VerificationStatus.VERIFIED)?"Verify":"Unverify" |
||||
onClicked: { |
||||
if(model.verificationStatus == VerificationStatus.VERIFIED){ |
||||
profile.unverify(model.deviceId) |
||||
}else{ |
||||
profile.verify(model.deviceId); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
footer: DialogButtonBox { |
||||
standardButtons: DialogButtonBox.Ok |
||||
|
||||
onAccepted: userProfileDialog.close() |
||||
} |
||||
} |
||||
|
@ -1,68 +1,75 @@ |
||||
import QtQuick 2.6 |
||||
import QtQuick.Layouts 1.2 |
||||
|
||||
import im.nheko 1.0 |
||||
|
||||
Item { |
||||
height: row.height + 24 |
||||
width: parent ? parent.width : undefined |
||||
|
||||
RowLayout { |
||||
id: row |
||||
|
||||
anchors.centerIn: parent |
||||
width: parent.width - 24 |
||||
|
||||
spacing: 15 |
||||
|
||||
Rectangle { |
||||
id: button |
||||
color: colors.light |
||||
radius: 22 |
||||
height: 44 |
||||
width: 44 |
||||
Image { |
||||
id: img |
||||
anchors.centerIn: parent |
||||
|
||||
source: "qrc:/icons/icons/ui/arrow-pointing-down.png" |
||||
fillMode: Image.Pad |
||||
|
||||
} |
||||
MouseArea { |
||||
anchors.fill: parent |
||||
onClicked: TimelineManager.timeline.saveMedia(model.data.id) |
||||
cursorShape: Qt.PointingHandCursor |
||||
} |
||||
} |
||||
ColumnLayout { |
||||
id: col |
||||
|
||||
Text { |
||||
id: filename |
||||
Layout.fillWidth: true |
||||
text: model.data.filename |
||||
textFormat: Text.PlainText |
||||
elide: Text.ElideRight |
||||
color: colors.text |
||||
} |
||||
Text { |
||||
id: filesize |
||||
Layout.fillWidth: true |
||||
text: model.data.filesize |
||||
textFormat: Text.PlainText |
||||
elide: Text.ElideRight |
||||
color: colors.text |
||||
} |
||||
} |
||||
} |
||||
|
||||
Rectangle { |
||||
color: colors.dark |
||||
z: -1 |
||||
radius: 10 |
||||
height: row.height + 24 |
||||
width: 44 + 24 + 24 + Math.max(Math.min(filesize.width, filesize.implicitWidth), Math.min(filename.width, filename.implicitWidth)) |
||||
} |
||||
height: row.height + 24 |
||||
width: parent ? parent.width : undefined |
||||
|
||||
RowLayout { |
||||
id: row |
||||
|
||||
anchors.centerIn: parent |
||||
width: parent.width - 24 |
||||
spacing: 15 |
||||
|
||||
Rectangle { |
||||
id: button |
||||
|
||||
color: colors.light |
||||
radius: 22 |
||||
height: 44 |
||||
width: 44 |
||||
|
||||
Image { |
||||
id: img |
||||
|
||||
anchors.centerIn: parent |
||||
source: "qrc:/icons/icons/ui/arrow-pointing-down.png" |
||||
fillMode: Image.Pad |
||||
} |
||||
|
||||
MouseArea { |
||||
anchors.fill: parent |
||||
onClicked: TimelineManager.timeline.saveMedia(model.data.id) |
||||
cursorShape: Qt.PointingHandCursor |
||||
} |
||||
|
||||
} |
||||
|
||||
ColumnLayout { |
||||
id: col |
||||
|
||||
Text { |
||||
id: filename |
||||
|
||||
Layout.fillWidth: true |
||||
text: model.data.filename |
||||
textFormat: Text.PlainText |
||||
elide: Text.ElideRight |
||||
color: colors.text |
||||
} |
||||
|
||||
Text { |
||||
id: filesize |
||||
|
||||
Layout.fillWidth: true |
||||
text: model.data.filesize |
||||
textFormat: Text.PlainText |
||||
elide: Text.ElideRight |
||||
color: colors.text |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
Rectangle { |
||||
color: colors.dark |
||||
z: -1 |
||||
radius: 10 |
||||
height: row.height + 24 |
||||
width: 44 + 24 + 24 + Math.max(Math.min(filesize.width, filesize.implicitWidth), Math.min(filename.width, filename.implicitWidth)) |
||||
} |
||||
|
||||
} |
||||
|
@ -1,42 +1,41 @@ |
||||
import QtQuick 2.6 |
||||
|
||||
import im.nheko 1.0 |
||||
|
||||
Item { |
||||
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? parent.width : model.data.width) |
||||
property double tempHeight: tempWidth * model.data.proportionalHeight |
||||
|
||||
property double divisor: model.isReply ? 4 : 2 |
||||
property bool tooHigh: tempHeight > timelineRoot.height / divisor |
||||
|
||||
height: Math.round(tooHigh ? timelineRoot.height / divisor : tempHeight) |
||||
width: Math.round(tooHigh ? (timelineRoot.height / divisor) / model.data.proportionalHeight : tempWidth) |
||||
|
||||
Image { |
||||
id: blurhash |
||||
anchors.fill: parent |
||||
visible: img.status != Image.Ready |
||||
|
||||
source: model.data.blurhash ? ("image://blurhash/" + model.data.blurhash) : ("image://colorimage/:/icons/icons/ui/do-not-disturb-rounded-sign@2x.png?"+colors.buttonText) |
||||
asynchronous: true |
||||
fillMode: Image.PreserveAspectFit |
||||
|
||||
sourceSize.width: parent.width |
||||
sourceSize.height: parent.height |
||||
} |
||||
|
||||
Image { |
||||
id: img |
||||
anchors.fill: parent |
||||
|
||||
source: model.data.url.replace("mxc://", "image://MxcImage/") |
||||
asynchronous: true |
||||
fillMode: Image.PreserveAspectFit |
||||
|
||||
MouseArea { |
||||
enabled: model.data.type == MtxEvent.ImageMessage && img.status == Image.Ready |
||||
anchors.fill: parent |
||||
onClicked: TimelineManager.openImageOverlay(model.data.url, model.data.id) |
||||
} |
||||
} |
||||
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? parent.width : model.data.width) |
||||
property double tempHeight: tempWidth * model.data.proportionalHeight |
||||
property double divisor: model.isReply ? 4 : 2 |
||||
property bool tooHigh: tempHeight > timelineRoot.height / divisor |
||||
|
||||
height: Math.round(tooHigh ? timelineRoot.height / divisor : tempHeight) |
||||
width: Math.round(tooHigh ? (timelineRoot.height / divisor) / model.data.proportionalHeight : tempWidth) |
||||
|
||||
Image { |
||||
id: blurhash |
||||
|
||||
anchors.fill: parent |
||||
visible: img.status != Image.Ready |
||||
source: model.data.blurhash ? ("image://blurhash/" + model.data.blurhash) : ("image://colorimage/:/icons/icons/ui/do-not-disturb-rounded-sign@2x.png?" + colors.buttonText) |
||||
asynchronous: true |
||||
fillMode: Image.PreserveAspectFit |
||||
sourceSize.width: parent.width |
||||
sourceSize.height: parent.height |
||||
} |
||||
|
||||
Image { |
||||
id: img |
||||
|
||||
anchors.fill: parent |
||||
source: model.data.url.replace("mxc://", "image://MxcImage/") |
||||
asynchronous: true |
||||
fillMode: Image.PreserveAspectFit |
||||
|
||||
MouseArea { |
||||
enabled: model.data.type == MtxEvent.ImageMessage && img.status == Image.Ready |
||||
anchors.fill: parent |
||||
onClicked: TimelineManager.openImageOverlay(model.data.url, model.data.id) |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
@ -1,6 +1,6 @@ |
||||
TextMessage { |
||||
font.italic: true |
||||
color: colors.buttonText |
||||
height: isReply ? Math.min(chat.height / 8, implicitHeight) : undefined |
||||
clip: true |
||||
font.italic: true |
||||
color: colors.buttonText |
||||
height: isReply ? Math.min(chat.height / 8, implicitHeight) : undefined |
||||
clip: true |
||||
} |
||||
|
@ -1,7 +1,7 @@ |
||||
import ".." |
||||
|
||||
MatrixText { |
||||
text: qsTr("unimplemented event: ") + model.data.typeString |
||||
width: parent ? parent.width : undefined |
||||
color: inactiveColors.text |
||||
text: qsTr("unimplemented event: ") + model.data.typeString |
||||
width: parent ? parent.width : undefined |
||||
color: inactiveColors.text |
||||
} |
||||
|
@ -1,173 +1,216 @@ |
||||
import QtMultimedia 5.6 |
||||
import QtQuick 2.6 |
||||
import QtQuick.Layouts 1.2 |
||||
import QtQuick.Controls 2.1 |
||||
import QtMultimedia 5.6 |
||||
|
||||
import QtQuick.Layouts 1.2 |
||||
import im.nheko 1.0 |
||||
|
||||
Rectangle { |
||||
id: bg |
||||
radius: 10 |
||||
color: colors.dark |
||||
height: Math.round(content.height + 24) |
||||
width: parent ? parent.width : undefined |
||||
|
||||
Column { |
||||
id: content |
||||
width: parent.width - 24 |
||||
anchors.centerIn: parent |
||||
|
||||
Rectangle { |
||||
id: videoContainer |
||||
visible: model.data.type == MtxEvent.VideoMessage |
||||
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? 400 : model.data.width) |
||||
property double tempHeight: tempWidth * model.data.proportionalHeight |
||||
|
||||
property double divisor: model.isReply ? 4 : 2 |
||||
property bool tooHigh: tempHeight > timelineRoot.height / divisor |
||||
|
||||
height: tooHigh ? timelineRoot.height / divisor : tempHeight |
||||
width: tooHigh ? (timelineRoot.height / divisor) / model.data.proportionalHeight : tempWidth |
||||
Image { |
||||
anchors.fill: parent |
||||
source: model.data.thumbnailUrl.replace("mxc://", "image://MxcImage/") |
||||
asynchronous: true |
||||
fillMode: Image.PreserveAspectFit |
||||
|
||||
VideoOutput { |
||||
anchors.fill: parent |
||||
fillMode: VideoOutput.PreserveAspectFit |
||||
source: media |
||||
} |
||||
} |
||||
} |
||||
|
||||
RowLayout { |
||||
width: parent.width |
||||
Text { |
||||
id: positionText |
||||
text: "--:--:--" |
||||
color: colors.text |
||||
} |
||||
Slider { |
||||
Layout.fillWidth: true |
||||
id: progress |
||||
value: media.position |
||||
from: 0 |
||||
to: media.duration |
||||
|
||||
onMoved: media.seek(value) |
||||
//indeterminate: true |
||||
function updatePositionTexts() { |
||||
function formatTime(date) { |
||||
var hh = date.getUTCHours(); |
||||
var mm = date.getUTCMinutes(); |
||||
var ss = date.getSeconds(); |
||||
if (hh < 10) {hh = "0"+hh;} |
||||
if (mm < 10) {mm = "0"+mm;} |
||||
if (ss < 10) {ss = "0"+ss;} |
||||
return hh+":"+mm+":"+ss; |
||||
} |
||||
positionText.text = formatTime(new Date(media.position)) |
||||
durationText.text = formatTime(new Date(media.duration)) |
||||
} |
||||
onValueChanged: updatePositionTexts() |
||||
|
||||
palette: colors |
||||
} |
||||
Text { |
||||
id: durationText |
||||
text: "--:--:--" |
||||
color: colors.text |
||||
} |
||||
} |
||||
|
||||
RowLayout { |
||||
width: parent.width |
||||
|
||||
spacing: 15 |
||||
|
||||
Rectangle { |
||||
id: button |
||||
color: colors.window |
||||
radius: 22 |
||||
height: 44 |
||||
width: 44 |
||||
Image { |
||||
id: img |
||||
anchors.centerIn: parent |
||||
z: 3 |
||||
|
||||
source: "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?"+colors.text |
||||
fillMode: Image.Pad |
||||
|
||||
} |
||||
MouseArea { |
||||
anchors.fill: parent |
||||
onClicked: { |
||||
switch (button.state) { |
||||
case "": TimelineManager.timeline.cacheMedia(model.data.id); break; |
||||
case "stopped": |
||||
media.play(); console.log("play"); |
||||
button.state = "playing" |
||||
break |
||||
case "playing": |
||||
media.pause(); console.log("pause"); |
||||
button.state = "stopped" |
||||
break |
||||
} |
||||
} |
||||
cursorShape: Qt.PointingHandCursor |
||||
} |
||||
MediaPlayer { |
||||
id: media |
||||
onError: console.log(errorString) |
||||
onStatusChanged: if(status == MediaPlayer.Loaded) progress.updatePositionTexts() |
||||
onStopped: button.state = "stopped" |
||||
} |
||||
|
||||
Connections { |
||||
target: TimelineManager.timeline |
||||
onMediaCached: { |
||||
if (mxcUrl == model.data.url) { |
||||
media.source = "file://" + cacheUrl |
||||
button.state = "stopped" |
||||
console.log("media loaded: " + mxcUrl + " at " + cacheUrl) |
||||
} |
||||
console.log("media cached: " + mxcUrl + " at " + cacheUrl) |
||||
} |
||||
} |
||||
|
||||
states: [ |
||||
State { |
||||
name: "stopped" |
||||
PropertyChanges { target: img; source: "image://colorimage/:/icons/icons/ui/play-sign.png?"+colors.text } |
||||
}, |
||||
State { |
||||
name: "playing" |
||||
PropertyChanges { target: img; source: "image://colorimage/:/icons/icons/ui/pause-symbol.png?"+colors.text } |
||||
} |
||||
] |
||||
} |
||||
ColumnLayout { |
||||
id: col |
||||
|
||||
Text { |
||||
Layout.fillWidth: true |
||||
text: model.data.body |
||||
textFormat: Text.PlainText |
||||
elide: Text.ElideRight |
||||
color: colors.text |
||||
} |
||||
Text { |
||||
Layout.fillWidth: true |
||||
text: model.data.filesize |
||||
textFormat: Text.PlainText |
||||
elide: Text.ElideRight |
||||
color: colors.text |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
id: bg |
||||
|
||||
radius: 10 |
||||
color: colors.dark |
||||
height: Math.round(content.height + 24) |
||||
width: parent ? parent.width : undefined |
||||
|
||||
Column { |
||||
id: content |
||||
|
||||
width: parent.width - 24 |
||||
anchors.centerIn: parent |
||||
|
||||
Rectangle { |
||||
id: videoContainer |
||||
|
||||
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? 400 : model.data.width) |
||||
property double tempHeight: tempWidth * model.data.proportionalHeight |
||||
property double divisor: model.isReply ? 4 : 2 |
||||
property bool tooHigh: tempHeight > timelineRoot.height / divisor |
||||
|
||||
visible: model.data.type == MtxEvent.VideoMessage |
||||
height: tooHigh ? timelineRoot.height / divisor : tempHeight |
||||
width: tooHigh ? (timelineRoot.height / divisor) / model.data.proportionalHeight : tempWidth |
||||
|
||||
Image { |
||||
anchors.fill: parent |
||||
source: model.data.thumbnailUrl.replace("mxc://", "image://MxcImage/") |
||||
asynchronous: true |
||||
fillMode: Image.PreserveAspectFit |
||||
|
||||
VideoOutput { |
||||
anchors.fill: parent |
||||
fillMode: VideoOutput.PreserveAspectFit |
||||
source: media |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
RowLayout { |
||||
width: parent.width |
||||
|
||||
Text { |
||||
id: positionText |
||||
|
||||
text: "--:--:--" |
||||
color: colors.text |
||||
} |
||||
|
||||
Slider { |
||||
id: progress |
||||
|
||||
//indeterminate: true |
||||
function updatePositionTexts() { |
||||
function formatTime(date) { |
||||
var hh = date.getUTCHours(); |
||||
var mm = date.getUTCMinutes(); |
||||
var ss = date.getSeconds(); |
||||
if (hh < 10) |
||||
hh = "0" + hh; |
||||
|
||||
if (mm < 10) |
||||
mm = "0" + mm; |
||||
|
||||
if (ss < 10) |
||||
ss = "0" + ss; |
||||
|
||||
return hh + ":" + mm + ":" + ss; |
||||
} |
||||
|
||||
positionText.text = formatTime(new Date(media.position)); |
||||
durationText.text = formatTime(new Date(media.duration)); |
||||
} |
||||
|
||||
Layout.fillWidth: true |
||||
value: media.position |
||||
from: 0 |
||||
to: media.duration |
||||
onMoved: media.seek(value) |
||||
onValueChanged: updatePositionTexts() |
||||
palette: colors |
||||
} |
||||
|
||||
Text { |
||||
id: durationText |
||||
|
||||
text: "--:--:--" |
||||
color: colors.text |
||||
} |
||||
|
||||
} |
||||
|
||||
RowLayout { |
||||
width: parent.width |
||||
spacing: 15 |
||||
|
||||
Rectangle { |
||||
id: button |
||||
|
||||
color: colors.window |
||||
radius: 22 |
||||
height: 44 |
||||
width: 44 |
||||
states: [ |
||||
State { |
||||
name: "stopped" |
||||
|
||||
PropertyChanges { |
||||
target: img |
||||
source: "image://colorimage/:/icons/icons/ui/play-sign.png?" + colors.text |
||||
} |
||||
|
||||
}, |
||||
State { |
||||
name: "playing" |
||||
|
||||
PropertyChanges { |
||||
target: img |
||||
source: "image://colorimage/:/icons/icons/ui/pause-symbol.png?" + colors.text |
||||
} |
||||
|
||||
} |
||||
] |
||||
|
||||
Image { |
||||
id: img |
||||
|
||||
anchors.centerIn: parent |
||||
z: 3 |
||||
source: "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?" + colors.text |
||||
fillMode: Image.Pad |
||||
} |
||||
|
||||
MouseArea { |
||||
anchors.fill: parent |
||||
onClicked: { |
||||
switch (button.state) { |
||||
case "": |
||||
TimelineManager.timeline.cacheMedia(model.data.id); |
||||
break; |
||||
case "stopped": |
||||
media.play(); |
||||
console.log("play"); |
||||
button.state = "playing"; |
||||
break; |
||||
case "playing": |
||||
media.pause(); |
||||
console.log("pause"); |
||||
button.state = "stopped"; |
||||
break; |
||||
} |
||||
} |
||||
cursorShape: Qt.PointingHandCursor |
||||
} |
||||
|
||||
MediaPlayer { |
||||
id: media |
||||
|
||||
onError: console.log(errorString) |
||||
onStatusChanged: { |
||||
if (status == MediaPlayer.Loaded) |
||||
progress.updatePositionTexts(); |
||||
|
||||
} |
||||
onStopped: button.state = "stopped" |
||||
} |
||||
|
||||
Connections { |
||||
target: TimelineManager.timeline |
||||
onMediaCached: { |
||||
if (mxcUrl == model.data.url) { |
||||
media.source = "file://" + cacheUrl; |
||||
button.state = "stopped"; |
||||
console.log("media loaded: " + mxcUrl + " at " + cacheUrl); |
||||
} |
||||
console.log("media cached: " + mxcUrl + " at " + cacheUrl); |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
ColumnLayout { |
||||
id: col |
||||
|
||||
Text { |
||||
Layout.fillWidth: true |
||||
text: model.data.body |
||||
textFormat: Text.PlainText |
||||
elide: Text.ElideRight |
||||
color: colors.text |
||||
} |
||||
|
||||
Text { |
||||
Layout.fillWidth: true |
||||
text: model.data.filesize |
||||
textFormat: Text.PlainText |
||||
elide: Text.ElideRight |
||||
color: colors.text |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
@ -1,12 +1,12 @@ |
||||
import ".." |
||||
|
||||
import im.nheko 1.0 |
||||
|
||||
MatrixText { |
||||
property string formatted: model.data.formattedBody |
||||
text: "<style type=\"text/css\">a { color:"+colors.link+";}</style>" + formatted.replace("<pre>", "<pre style='white-space: pre-wrap'>") |
||||
width: parent ? parent.width : undefined |
||||
height: isReply ? Math.round(Math.min(timelineRoot.height / 8, implicitHeight)) : undefined |
||||
clip: true |
||||
font.pointSize: (Settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize |
||||
property string formatted: model.data.formattedBody |
||||
|
||||
text: "<style type=\"text/css\">a { color:" + colors.link + ";}</style>" + formatted.replace("<pre>", "<pre style='white-space: pre-wrap'>") |
||||
width: parent ? parent.width : undefined |
||||
height: isReply ? Math.round(Math.min(timelineRoot.height / 8, implicitHeight)) : undefined |
||||
clip: true |
||||
font.pointSize: (Settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize |
||||
} |
||||
|
@ -1,39 +1,46 @@ |
||||
import QtQuick 2.3 |
||||
import QtQuick.Controls 2.10 |
||||
import QtQuick.Layouts 1.10 |
||||
|
||||
import im.nheko 1.0 |
||||
|
||||
Pane { |
||||
property string title: qsTr("Awaiting Confirmation") |
||||
ColumnLayout { |
||||
spacing: 16 |
||||
Label { |
||||
Layout.maximumWidth: 400 |
||||
Layout.fillHeight: true |
||||
Layout.fillWidth: true |
||||
wrapMode: Text.Wrap |
||||
id: content |
||||
text: qsTr("Waiting for other side to complete verification.") |
||||
color:colors.text |
||||
verticalAlignment: Text.AlignVCenter |
||||
} |
||||
BusyIndicator { |
||||
Layout.alignment: Qt.AlignHCenter |
||||
} |
||||
RowLayout { |
||||
Button { |
||||
Layout.alignment: Qt.AlignLeft |
||||
text: qsTr("Cancel") |
||||
|
||||
onClicked: { |
||||
flow.cancel(); |
||||
dialog.close(); |
||||
} |
||||
} |
||||
Item { |
||||
Layout.fillWidth: true |
||||
} |
||||
} |
||||
} |
||||
property string title: qsTr("Awaiting Confirmation") |
||||
|
||||
ColumnLayout { |
||||
spacing: 16 |
||||
|
||||
Label { |
||||
id: content |
||||
|
||||
Layout.maximumWidth: 400 |
||||
Layout.fillHeight: true |
||||
Layout.fillWidth: true |
||||
wrapMode: Text.Wrap |
||||
text: qsTr("Waiting for other side to complete verification.") |
||||
color: colors.text |
||||
verticalAlignment: Text.AlignVCenter |
||||
} |
||||
|
||||
BusyIndicator { |
||||
Layout.alignment: Qt.AlignHCenter |
||||
} |
||||
|
||||
RowLayout { |
||||
Button { |
||||
Layout.alignment: Qt.AlignLeft |
||||
text: qsTr("Cancel") |
||||
onClicked: { |
||||
flow.cancel(); |
||||
dialog.close(); |
||||
} |
||||
} |
||||
|
||||
Item { |
||||
Layout.fillWidth: true |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
@ -1,97 +1,144 @@ |
||||
import QtQuick 2.10 |
||||
import QtQuick.Controls 2.10 |
||||
import QtQuick.Window 2.10 |
||||
|
||||
import im.nheko 1.0 |
||||
|
||||
ApplicationWindow { |
||||
property var flow |
||||
|
||||
onClosing: TimelineManager.removeVerificationFlow(flow) |
||||
|
||||
title: stack.currentItem.title |
||||
id: dialog |
||||
|
||||
flags: Qt.Dialog |
||||
|
||||
palette: colors |
||||
|
||||
height: stack.implicitHeight |
||||
width: stack.implicitWidth |
||||
|
||||
StackView { |
||||
id: stack |
||||
initialItem: newVerificationRequest |
||||
implicitWidth: currentItem.implicitWidth |
||||
implicitHeight: currentItem.implicitHeight |
||||
} |
||||
|
||||
Component{ |
||||
id: newVerificationRequest |
||||
NewVerificationRequest {} |
||||
} |
||||
|
||||
Component { |
||||
id: waiting |
||||
Waiting {} |
||||
} |
||||
|
||||
Component { |
||||
id: success |
||||
Success {} |
||||
} |
||||
|
||||
Component { |
||||
id: failed |
||||
Failed {} |
||||
} |
||||
|
||||
Component { |
||||
id: digitVerification |
||||
DigitVerification {} |
||||
} |
||||
|
||||
Component { |
||||
id: emojiVerification |
||||
EmojiVerification {} |
||||
} |
||||
|
||||
Item { |
||||
state: flow.state |
||||
|
||||
states: [ |
||||
State { |
||||
name: "PromptStartVerification" |
||||
StateChangeScript { script: stack.replace(newVerificationRequest) } |
||||
}, |
||||
State { |
||||
name: "CompareEmoji" |
||||
StateChangeScript { script: stack.replace(emojiVerification) } |
||||
}, |
||||
State { |
||||
name: "CompareNumber" |
||||
StateChangeScript { script: stack.replace(digitVerification) } |
||||
}, |
||||
State { |
||||
name: "WaitingForKeys" |
||||
StateChangeScript { script: stack.replace(waiting) } |
||||
}, |
||||
State { |
||||
name: "WaitingForOtherToAccept" |
||||
StateChangeScript { script: stack.replace(waiting) } |
||||
}, |
||||
State { |
||||
name: "WaitingForMac" |
||||
StateChangeScript { script: stack.replace(waiting) } |
||||
}, |
||||
State { |
||||
name: "Success" |
||||
StateChangeScript { script: stack.replace(success) } |
||||
}, |
||||
State { |
||||
name: "Failed" |
||||
StateChangeScript { script: stack.replace(failed); } |
||||
} |
||||
] |
||||
} |
||||
id: dialog |
||||
|
||||
property var flow |
||||
|
||||
onClosing: TimelineManager.removeVerificationFlow(flow) |
||||
title: stack.currentItem.title |
||||
flags: Qt.Dialog |
||||
palette: colors |
||||
height: stack.implicitHeight |
||||
width: stack.implicitWidth |
||||
|
||||
StackView { |
||||
id: stack |
||||
|
||||
initialItem: newVerificationRequest |
||||
implicitWidth: currentItem.implicitWidth |
||||
implicitHeight: currentItem.implicitHeight |
||||
} |
||||
|
||||
Component { |
||||
id: newVerificationRequest |
||||
|
||||
NewVerificationRequest { |
||||
} |
||||
|
||||
} |
||||
|
||||
Component { |
||||
id: waiting |
||||
|
||||
Waiting { |
||||
} |
||||
|
||||
} |
||||
|
||||
Component { |
||||
id: success |
||||
|
||||
Success { |
||||
} |
||||
|
||||
} |
||||
|
||||
Component { |
||||
id: failed |
||||
|
||||
Failed { |
||||
} |
||||
|
||||
} |
||||
|
||||
Component { |
||||
id: digitVerification |
||||
|
||||
DigitVerification { |
||||
} |
||||
|
||||
} |
||||
|
||||
Component { |
||||
id: emojiVerification |
||||
|
||||
EmojiVerification { |
||||
} |
||||
|
||||
} |
||||
|
||||
Item { |
||||
state: flow.state |
||||
states: [ |
||||
State { |
||||
name: "PromptStartVerification" |
||||
|
||||
StateChangeScript { |
||||
script: stack.replace(newVerificationRequest) |
||||
} |
||||
|
||||
}, |
||||
State { |
||||
name: "CompareEmoji" |
||||
|
||||
StateChangeScript { |
||||
script: stack.replace(emojiVerification) |
||||
} |
||||
|
||||
}, |
||||
State { |
||||
name: "CompareNumber" |
||||
|
||||
StateChangeScript { |
||||
script: stack.replace(digitVerification) |
||||
} |
||||
|
||||
}, |
||||
State { |
||||
name: "WaitingForKeys" |
||||
|
||||
StateChangeScript { |
||||
script: stack.replace(waiting) |
||||
} |
||||
|
||||
}, |
||||
State { |
||||
name: "WaitingForOtherToAccept" |
||||
|
||||
StateChangeScript { |
||||
script: stack.replace(waiting) |
||||
} |
||||
|
||||
}, |
||||
State { |
||||
name: "WaitingForMac" |
||||
|
||||
StateChangeScript { |
||||
script: stack.replace(waiting) |
||||
} |
||||
|
||||
}, |
||||
State { |
||||
name: "Success" |
||||
|
||||
StateChangeScript { |
||||
script: stack.replace(success) |
||||
} |
||||
|
||||
}, |
||||
State { |
||||
name: "Failed" |
||||
|
||||
StateChangeScript { |
||||
script: stack.replace(failed) |
||||
} |
||||
|
||||
} |
||||
] |
||||
} |
||||
|
||||
} |
||||
|
@ -1,60 +1,69 @@ |
||||
import QtQuick 2.3 |
||||
import QtQuick.Controls 2.10 |
||||
import QtQuick.Layouts 1.10 |
||||
|
||||
import im.nheko 1.0 |
||||
|
||||
Pane { |
||||
property string title: qsTr("Verification Code") |
||||
|
||||
ColumnLayout { |
||||
spacing: 16 |
||||
Label { |
||||
Layout.maximumWidth: 400 |
||||
Layout.fillHeight: true |
||||
Layout.fillWidth: true |
||||
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!") |
||||
color:colors.text |
||||
verticalAlignment: Text.AlignVCenter |
||||
} |
||||
RowLayout { |
||||
Layout.alignment: Qt.AlignHCenter |
||||
Label { |
||||
font.pixelSize: Qt.application.font.pixelSize * 2 |
||||
text: flow.sasList[0] |
||||
color:colors.text |
||||
} |
||||
Label { |
||||
font.pixelSize: Qt.application.font.pixelSize * 2 |
||||
text: flow.sasList[1] |
||||
color:colors.text |
||||
} |
||||
Label { |
||||
font.pixelSize: Qt.application.font.pixelSize * 2 |
||||
text: flow.sasList[2] |
||||
color:colors.text |
||||
} |
||||
} |
||||
RowLayout { |
||||
Button { |
||||
Layout.alignment: Qt.AlignLeft |
||||
text: qsTr("They do not match!") |
||||
|
||||
onClicked: { |
||||
flow.cancel(); |
||||
dialog.close(); |
||||
} |
||||
} |
||||
Item { |
||||
Layout.fillWidth: true |
||||
} |
||||
Button { |
||||
Layout.alignment: Qt.AlignRight |
||||
text: qsTr("They match!") |
||||
|
||||
onClicked: flow.next(); |
||||
} |
||||
} |
||||
} |
||||
property string title: qsTr("Verification Code") |
||||
|
||||
ColumnLayout { |
||||
spacing: 16 |
||||
|
||||
Label { |
||||
Layout.maximumWidth: 400 |
||||
Layout.fillHeight: true |
||||
Layout.fillWidth: true |
||||
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!") |
||||
color: colors.text |
||||
verticalAlignment: Text.AlignVCenter |
||||
} |
||||
|
||||
RowLayout { |
||||
Layout.alignment: Qt.AlignHCenter |
||||
|
||||
Label { |
||||
font.pixelSize: Qt.application.font.pixelSize * 2 |
||||
text: flow.sasList[0] |
||||
color: colors.text |
||||
} |
||||
|
||||
Label { |
||||
font.pixelSize: Qt.application.font.pixelSize * 2 |
||||
text: flow.sasList[1] |
||||
color: colors.text |
||||
} |
||||
|
||||
Label { |
||||
font.pixelSize: Qt.application.font.pixelSize * 2 |
||||
text: flow.sasList[2] |
||||
color: colors.text |
||||
} |
||||
|
||||
} |
||||
|
||||
RowLayout { |
||||
Button { |
||||
Layout.alignment: Qt.AlignLeft |
||||
text: qsTr("They do not match!") |
||||
onClicked: { |
||||
flow.cancel(); |
||||
dialog.close(); |
||||
} |
||||
} |
||||
|
||||
Item { |
||||
Layout.fillWidth: true |
||||
} |
||||
|
||||
Button { |
||||
Layout.alignment: Qt.AlignRight |
||||
text: qsTr("They match!") |
||||
onClicked: flow.next() |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
@ -1,44 +1,56 @@ |
||||
import QtQuick 2.3 |
||||
import QtQuick.Controls 2.10 |
||||
import QtQuick.Layouts 1.10 |
||||
|
||||
import im.nheko 1.0 |
||||
|
||||
Pane { |
||||
property string title: qsTr("Verification failed") |
||||
ColumnLayout { |
||||
spacing: 16 |
||||
Text { |
||||
id: content |
||||
|
||||
Layout.maximumWidth: 400 |
||||
Layout.fillHeight: true |
||||
Layout.fillWidth: true |
||||
|
||||
wrapMode: Text.Wrap |
||||
text: switch (flow.error) { |
||||
case DeviceVerificationFlow.UnknownMethod: return qsTr("Other client does not support our verification protocol.") |
||||
case DeviceVerificationFlow.MismatchedCommitment: |
||||
case DeviceVerificationFlow.MismatchedSAS: |
||||
case DeviceVerificationFlow.KeyMismatch: return qsTr("Key mismatch detected!") |
||||
case DeviceVerificationFlow.Timeout: return qsTr("Device verification timed out.") |
||||
case DeviceVerificationFlow.User: return qsTr("Other party canceled the verification.") |
||||
case DeviceVerificationFlow.OutOfOrder: return qsTr("Device verification timed out.") |
||||
default: return "Unknown verification error."; |
||||
} |
||||
color:colors.text |
||||
verticalAlignment: Text.AlignVCenter |
||||
} |
||||
RowLayout { |
||||
Item { |
||||
Layout.fillWidth: true |
||||
} |
||||
Button { |
||||
Layout.alignment: Qt.AlignRight |
||||
text: qsTr("Close") |
||||
|
||||
onClicked: dialog.close() |
||||
} |
||||
} |
||||
} |
||||
property string title: qsTr("Verification failed") |
||||
|
||||
ColumnLayout { |
||||
spacing: 16 |
||||
|
||||
Text { |
||||
id: content |
||||
|
||||
Layout.maximumWidth: 400 |
||||
Layout.fillHeight: true |
||||
Layout.fillWidth: true |
||||
wrapMode: Text.Wrap |
||||
text: { |
||||
switch (flow.error) { |
||||
case DeviceVerificationFlow.UnknownMethod: |
||||
return qsTr("Other client does not support our verification protocol."); |
||||
case DeviceVerificationFlow.MismatchedCommitment: |
||||
case DeviceVerificationFlow.MismatchedSAS: |
||||
case DeviceVerificationFlow.KeyMismatch: |
||||
return qsTr("Key mismatch detected!"); |
||||
case DeviceVerificationFlow.Timeout: |
||||
return qsTr("Device verification timed out."); |
||||
case DeviceVerificationFlow.User: |
||||
return qsTr("Other party canceled the verification."); |
||||
case DeviceVerificationFlow.OutOfOrder: |
||||
return qsTr("Device verification timed out."); |
||||
default: |
||||
return "Unknown verification error."; |
||||
} |
||||
} |
||||
color: colors.text |
||||
verticalAlignment: Text.AlignVCenter |
||||
} |
||||
|
||||
RowLayout { |
||||
Item { |
||||
Layout.fillWidth: true |
||||
} |
||||
|
||||
Button { |
||||
Layout.alignment: Qt.AlignRight |
||||
text: qsTr("Close") |
||||
onClicked: dialog.close() |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
@ -1,44 +1,46 @@ |
||||
import QtQuick 2.3 |
||||
import QtQuick.Controls 2.10 |
||||
import QtQuick.Layouts 1.10 |
||||
|
||||
import im.nheko 1.0 |
||||
|
||||
Pane { |
||||
property string title: flow.sender ? qsTr("Send Device Verification Request") : qsTr("Recieved Device Verification Request") |
||||
|
||||
ColumnLayout { |
||||
spacing: 16 |
||||
Label { |
||||
Layout.maximumWidth: 400 |
||||
Layout.fillHeight: true |
||||
Layout.fillWidth: true |
||||
wrapMode: Text.Wrap |
||||
text: flow.sender ? |
||||
qsTr("To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device.") |
||||
: qsTr("The device was requested to be verified") |
||||
color:colors.text |
||||
verticalAlignment: Text.AlignVCenter |
||||
} |
||||
RowLayout { |
||||
Button { |
||||
Layout.alignment: Qt.AlignLeft |
||||
text: flow.sender ? qsTr("Cancel") : qsTr("Deny") |
||||
|
||||
onClicked: { |
||||
flow.cancel(); |
||||
dialog.close(); |
||||
} |
||||
} |
||||
Item { |
||||
Layout.fillWidth: true |
||||
} |
||||
Button { |
||||
Layout.alignment: Qt.AlignRight |
||||
text: flow.sender ? qsTr("Start verification") : qsTr("Accept") |
||||
|
||||
onClicked: flow.next(); |
||||
} |
||||
} |
||||
} |
||||
property string title: flow.sender ? qsTr("Send Device Verification Request") : qsTr("Recieved Device Verification Request") |
||||
|
||||
ColumnLayout { |
||||
spacing: 16 |
||||
|
||||
Label { |
||||
Layout.maximumWidth: 400 |
||||
Layout.fillHeight: true |
||||
Layout.fillWidth: true |
||||
wrapMode: Text.Wrap |
||||
text: flow.sender ? qsTr("To ensure that no malicious user can eavesdrop on your encrypted communications, you can verify this device.") : qsTr("The device was requested to be verified") |
||||
color: colors.text |
||||
verticalAlignment: Text.AlignVCenter |
||||
} |
||||
|
||||
RowLayout { |
||||
Button { |
||||
Layout.alignment: Qt.AlignLeft |
||||
text: flow.sender ? qsTr("Cancel") : qsTr("Deny") |
||||
onClicked: { |
||||
flow.cancel(); |
||||
dialog.close(); |
||||
} |
||||
} |
||||
|
||||
Item { |
||||
Layout.fillWidth: true |
||||
} |
||||
|
||||
Button { |
||||
Layout.alignment: Qt.AlignRight |
||||
text: flow.sender ? qsTr("Start verification") : qsTr("Accept") |
||||
onClicked: flow.next() |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
@ -1,45 +1,56 @@ |
||||
import QtQuick 2.3 |
||||
import QtQuick.Controls 2.10 |
||||
import QtQuick.Layouts 1.10 |
||||
|
||||
import im.nheko 1.0 |
||||
|
||||
Pane { |
||||
property string title: qsTr("Waiting for other party") |
||||
ColumnLayout { |
||||
spacing: 16 |
||||
Label { |
||||
Layout.maximumWidth: 400 |
||||
Layout.fillHeight: true |
||||
Layout.fillWidth: true |
||||
wrapMode: Text.Wrap |
||||
id: content |
||||
text: switch (flow.state) { |
||||
case "WaitingForOtherToAccept": return qsTr("Waiting for other side to accept the verification request.") |
||||
case "WaitingForKeys": return qsTr("Waiting for other side to continue the verification request.") |
||||
case "WaitingForMac": return qsTr("Waiting for other side to complete the verification request.") |
||||
} |
||||
|
||||
color: colors.text |
||||
verticalAlignment: Text.AlignVCenter |
||||
} |
||||
BusyIndicator { |
||||
Layout.alignment: Qt.AlignHCenter |
||||
palette: colors |
||||
} |
||||
RowLayout { |
||||
Button { |
||||
Layout.alignment: Qt.AlignLeft |
||||
text: qsTr("Cancel") |
||||
|
||||
onClicked: { |
||||
flow.cancel(); |
||||
dialog.close(); |
||||
} |
||||
} |
||||
Item { |
||||
Layout.fillWidth: true |
||||
} |
||||
} |
||||
} |
||||
property string title: qsTr("Waiting for other party") |
||||
|
||||
ColumnLayout { |
||||
spacing: 16 |
||||
|
||||
Label { |
||||
id: content |
||||
|
||||
Layout.maximumWidth: 400 |
||||
Layout.fillHeight: true |
||||
Layout.fillWidth: true |
||||
wrapMode: Text.Wrap |
||||
text: { |
||||
switch (flow.state) { |
||||
case "WaitingForOtherToAccept": |
||||
return qsTr("Waiting for other side to accept the verification request."); |
||||
case "WaitingForKeys": |
||||
return qsTr("Waiting for other side to continue the verification request."); |
||||
case "WaitingForMac": |
||||
return qsTr("Waiting for other side to complete the verification request."); |
||||
} |
||||
} |
||||
color: colors.text |
||||
verticalAlignment: Text.AlignVCenter |
||||
} |
||||
|
||||
BusyIndicator { |
||||
Layout.alignment: Qt.AlignHCenter |
||||
palette: colors |
||||
} |
||||
|
||||
RowLayout { |
||||
Button { |
||||
Layout.alignment: Qt.AlignLeft |
||||
text: qsTr("Cancel") |
||||
onClicked: { |
||||
flow.cancel(); |
||||
dialog.close(); |
||||
} |
||||
} |
||||
|
||||
Item { |
||||
Layout.fillWidth: true |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
@ -1,17 +1,16 @@ |
||||
import "../" |
||||
import QtQuick 2.10 |
||||
import QtQuick.Controls 2.1 |
||||
import im.nheko 1.0 |
||||
import im.nheko.EmojiModel 1.0 |
||||
|
||||
import "../" |
||||
|
||||
ImageButton { |
||||
id: emojiButton |
||||
|
||||
property var colors: currentActivePalette |
||||
property var emojiPicker |
||||
property string event_id |
||||
|
||||
image: ":/icons/icons/ui/smile.png" |
||||
id: emojiButton |
||||
onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.show(emojiButton, event_id) |
||||
|
||||
} |
||||
|
Loading…
Reference in new issue