mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-10-30 01:30:46 +03:00
Format qml
This commit is contained in:
parent
de8522a185
commit
5aee8d609a
28 changed files with 2623 additions and 2954 deletions
7
.qmlformat.ini
Normal file
7
.qmlformat.ini
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[General]
|
||||||
|
FunctionsSpacing=
|
||||||
|
IndentWidth=4
|
||||||
|
NewlineType=native
|
||||||
|
NormalizeOrder=true
|
||||||
|
ObjectsSpacing=
|
||||||
|
UseTabs=false
|
|
@ -11,45 +11,44 @@ import im.nheko 1.0
|
||||||
AbstractButton {
|
AbstractButton {
|
||||||
id: avatar
|
id: avatar
|
||||||
|
|
||||||
|
property alias color: bg.color
|
||||||
|
property bool crop: true
|
||||||
|
property string displayName
|
||||||
|
property string roomid
|
||||||
|
property alias textColor: label.color
|
||||||
property string url
|
property string url
|
||||||
property string userid
|
property string userid
|
||||||
property string roomid
|
|
||||||
property string displayName
|
|
||||||
property alias textColor: label.color
|
|
||||||
property bool crop: true
|
|
||||||
property alias color: bg.color
|
|
||||||
|
|
||||||
width: 48
|
|
||||||
height: 48
|
height: 48
|
||||||
|
width: 48
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
id: bg
|
id: bg
|
||||||
radius: Settings.avatarCircles ? height / 2 : height / 8
|
|
||||||
color: palette.alternateBase
|
color: palette.alternateBase
|
||||||
|
radius: Settings.avatarCircles ? height / 2 : height / 8
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: label
|
id: label
|
||||||
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
color: palette.text
|
||||||
|
enabled: false
|
||||||
|
font.pixelSize: avatar.height / 2
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
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
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
visible: img.status != Image.Ready && !Settings.useIdenticon
|
visible: img.status != Image.Ready && !Settings.useIdenticon
|
||||||
color: palette.text
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: identicon
|
id: identicon
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: Settings.useIdenticon && img.status != Image.Ready
|
|
||||||
source: Settings.useIdenticon ? ("image://jdenticon/" + (userid !== "" ? userid : roomid) + "?radius=" + (Settings.avatarCircles ? 100 : 25)) : ""
|
source: Settings.useIdenticon ? ("image://jdenticon/" + (userid !== "" ? userid : roomid) + "?radius=" + (Settings.avatarCircles ? 100 : 25)) : ""
|
||||||
|
visible: Settings.useIdenticon && img.status != Image.Ready
|
||||||
}
|
}
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: img
|
id: img
|
||||||
|
|
||||||
|
@ -58,8 +57,6 @@ AbstractButton {
|
||||||
fillMode: avatar.crop ? Image.PreserveAspectCrop : Image.PreserveAspectFit
|
fillMode: avatar.crop ? Image.PreserveAspectCrop : Image.PreserveAspectFit
|
||||||
mipmap: true
|
mipmap: true
|
||||||
smooth: true
|
smooth: true
|
||||||
sourceSize.width: avatar.width * Screen.devicePixelRatio
|
|
||||||
sourceSize.height: avatar.height * Screen.devicePixelRatio
|
|
||||||
source: if (avatar.url.startsWith('image://')) {
|
source: if (avatar.url.startsWith('image://')) {
|
||||||
return avatar.url + "?radius=" + (Settings.avatarCircles ? 100 : 25) + ((avatar.crop) ? "" : "&scale");
|
return avatar.url + "?radius=" + (Settings.avatarCircles ? 100 : 25) + ((avatar.crop) ? "" : "&scale");
|
||||||
} else if (avatar.url.startsWith(':/')) {
|
} else if (avatar.url.startsWith(':/')) {
|
||||||
|
@ -67,20 +64,12 @@ AbstractButton {
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
sourceSize.height: avatar.height * Screen.devicePixelRatio
|
||||||
|
sourceSize.width: avatar.width * Screen.devicePixelRatio
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: onlineIndicator
|
id: onlineIndicator
|
||||||
|
|
||||||
anchors.bottom: avatar.bottom
|
|
||||||
anchors.right: avatar.right
|
|
||||||
visible: !!userid
|
|
||||||
height: avatar.height / 6
|
|
||||||
width: height
|
|
||||||
radius: Settings.avatarCircles ? height / 2 : height / 8
|
|
||||||
color: updatePresence()
|
|
||||||
|
|
||||||
function updatePresence() {
|
function updatePresence() {
|
||||||
switch (Presence.userPresence(userid)) {
|
switch (Presence.userPresence(userid)) {
|
||||||
case "online":
|
case "online":
|
||||||
|
@ -94,22 +83,28 @@ AbstractButton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
anchors.bottom: avatar.bottom
|
||||||
target: Presence
|
anchors.right: avatar.right
|
||||||
|
color: updatePresence()
|
||||||
|
height: avatar.height / 6
|
||||||
|
radius: Settings.avatarCircles ? height / 2 : height / 8
|
||||||
|
visible: !!userid
|
||||||
|
width: height
|
||||||
|
|
||||||
|
Connections {
|
||||||
function onPresenceChanged(id) {
|
function onPresenceChanged(id) {
|
||||||
if (id == userid) onlineIndicator.color = onlineIndicator.updatePresence();
|
if (id == userid)
|
||||||
|
onlineIndicator.color = onlineIndicator.updatePresence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
target: Presence
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CursorShape {
|
CursorShape {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
}
|
}
|
||||||
|
|
||||||
Ripple {
|
Ripple {
|
||||||
color: Qt.rgba(palette.alternateBase.r, palette.alternateBase.g, palette.alternateBase.b, 0.5)
|
color: Qt.rgba(palette.alternateBase.r, palette.alternateBase.g, palette.alternateBase.b, 0.5)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,16 +17,16 @@ Rectangle {
|
||||||
color: palette.window
|
color: palette.window
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
spacing: 0
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: offlineIndicator
|
id: offlineIndicator
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: offlineLabel.height + Nheko.paddingMedium
|
||||||
color: Nheko.theme.error
|
color: Nheko.theme.error
|
||||||
visible: !TimelineManager.isConnected
|
visible: !TimelineManager.isConnected
|
||||||
Layout.preferredHeight: offlineLabel.height + Nheko.paddingMedium
|
|
||||||
Layout.fillWidth: true
|
|
||||||
z: 1
|
z: 1
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
@ -36,18 +36,9 @@ Rectangle {
|
||||||
text: qsTr("No network connection")
|
text: qsTr("No network connection")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AdaptiveLayout {
|
AdaptiveLayout {
|
||||||
id: adaptiveView
|
id: adaptiveView
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
singlePageMode: communityListC.preferredWidth + roomListC.preferredWidth + timlineViewC.minimumWidth > width
|
|
||||||
pageIndex: 1
|
|
||||||
|
|
||||||
Component.onCompleted: initializePageIndex()
|
|
||||||
onSinglePageModeChanged: initializePageIndex()
|
|
||||||
|
|
||||||
function initializePageIndex() {
|
function initializePageIndex() {
|
||||||
if (!singlePageMode)
|
if (!singlePageMode)
|
||||||
adaptiveView.pageIndex = 0;
|
adaptiveView.pageIndex = 0;
|
||||||
|
@ -57,67 +48,67 @@ Rectangle {
|
||||||
adaptiveView.pageIndex = 1;
|
adaptiveView.pageIndex = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
pageIndex: 1
|
||||||
|
singlePageMode: communityListC.preferredWidth + roomListC.preferredWidth + timlineViewC.minimumWidth > width
|
||||||
|
|
||||||
|
Component.onCompleted: initializePageIndex()
|
||||||
|
onSinglePageModeChanged: initializePageIndex()
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: Rooms
|
|
||||||
function onCurrentRoomChanged() {
|
function onCurrentRoomChanged() {
|
||||||
adaptiveView.initializePageIndex();
|
adaptiveView.initializePageIndex();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
target: Rooms
|
||||||
|
}
|
||||||
AdaptiveLayoutElement {
|
AdaptiveLayoutElement {
|
||||||
id: communityListC
|
id: communityListC
|
||||||
|
|
||||||
visible: Settings.groupView
|
|
||||||
minimumWidth: communitiesList.avatarSize * 4 + Nheko.paddingMedium * 2
|
|
||||||
collapsedWidth: communitiesList.avatarSize + 2 * Nheko.paddingMedium
|
collapsedWidth: communitiesList.avatarSize + 2 * Nheko.paddingMedium
|
||||||
preferredWidth: Settings.communityListWidth >= minimumWidth ? Settings.communityListWidth : collapsedWidth
|
|
||||||
maximumWidth: communitiesList.avatarSize * 10 + 2 * Nheko.paddingMedium
|
maximumWidth: communitiesList.avatarSize * 10 + 2 * Nheko.paddingMedium
|
||||||
|
minimumWidth: communitiesList.avatarSize * 4 + Nheko.paddingMedium * 2
|
||||||
|
preferredWidth: Settings.communityListWidth >= minimumWidth ? Settings.communityListWidth : collapsedWidth
|
||||||
|
visible: Settings.groupView
|
||||||
|
|
||||||
CommunitiesList {
|
CommunitiesList {
|
||||||
id: communitiesList
|
id: communitiesList
|
||||||
|
|
||||||
collapsed: parent.collapsed
|
collapsed: parent.collapsed
|
||||||
}
|
}
|
||||||
|
|
||||||
Binding {
|
Binding {
|
||||||
target: Settings
|
delayed: true
|
||||||
property: 'communityListWidth'
|
property: 'communityListWidth'
|
||||||
|
restoreMode: Binding.RestoreBindingOrValue
|
||||||
|
target: Settings
|
||||||
value: communityListC.preferredWidth
|
value: communityListC.preferredWidth
|
||||||
when: !adaptiveView.singlePageMode
|
when: !adaptiveView.singlePageMode
|
||||||
delayed: true
|
|
||||||
restoreMode: Binding.RestoreBindingOrValue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AdaptiveLayoutElement {
|
AdaptiveLayoutElement {
|
||||||
id: roomListC
|
id: roomListC
|
||||||
|
|
||||||
minimumWidth: roomlist.avatarSize * 4 + Nheko.paddingSmall * 2
|
|
||||||
preferredWidth: (Settings.roomListWidth == - 1)
|
|
||||||
? (roomlist.avatarSize * 5 + Nheko.paddingSmall * 2)
|
|
||||||
: (Settings.roomListWidth >= minimumWidth ? Settings.roomListWidth : collapsedWidth)
|
|
||||||
maximumWidth: roomlist.avatarSize * 10 + Nheko.paddingSmall * 2
|
|
||||||
collapsedWidth: roomlist.avatarSize + 2 * Nheko.paddingMedium
|
collapsedWidth: roomlist.avatarSize + 2 * Nheko.paddingMedium
|
||||||
|
maximumWidth: roomlist.avatarSize * 10 + Nheko.paddingSmall * 2
|
||||||
|
minimumWidth: roomlist.avatarSize * 4 + Nheko.paddingSmall * 2
|
||||||
|
preferredWidth: (Settings.roomListWidth == -1) ? (roomlist.avatarSize * 5 + Nheko.paddingSmall * 2) : (Settings.roomListWidth >= minimumWidth ? Settings.roomListWidth : collapsedWidth)
|
||||||
|
|
||||||
RoomList {
|
RoomList {
|
||||||
id: roomlist
|
id: roomlist
|
||||||
|
|
||||||
height: adaptiveView.height
|
|
||||||
collapsed: parent.collapsed
|
collapsed: parent.collapsed
|
||||||
|
height: adaptiveView.height
|
||||||
}
|
}
|
||||||
|
|
||||||
Binding {
|
Binding {
|
||||||
target: Settings
|
delayed: true
|
||||||
property: 'roomListWidth'
|
property: 'roomListWidth'
|
||||||
|
restoreMode: Binding.RestoreBindingOrValue
|
||||||
|
target: Settings
|
||||||
value: roomListC.preferredWidth
|
value: roomListC.preferredWidth
|
||||||
when: !adaptiveView.singlePageMode
|
when: !adaptiveView.singlePageMode
|
||||||
delayed: true
|
|
||||||
restoreMode: Binding.RestoreBindingOrValue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AdaptiveLayoutElement {
|
AdaptiveLayoutElement {
|
||||||
id: timlineViewC
|
id: timlineViewC
|
||||||
|
|
||||||
|
@ -127,25 +118,20 @@ Rectangle {
|
||||||
id: timeline
|
id: timeline
|
||||||
|
|
||||||
privacyScreen: privacyScreen
|
privacyScreen: privacyScreen
|
||||||
showBackButton: adaptiveView.singlePageMode
|
|
||||||
room: Rooms.currentRoom
|
room: Rooms.currentRoom
|
||||||
roomPreview: Rooms.currentRoomPreview.roomid ? Rooms.currentRoomPreview : null
|
roomPreview: Rooms.currentRoomPreview.roomid ? Rooms.currentRoomPreview : null
|
||||||
|
showBackButton: adaptiveView.singlePageMode
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PrivacyScreen {
|
PrivacyScreen {
|
||||||
id: privacyScreen
|
id: privacyScreen
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: Settings.privacyScreen
|
|
||||||
screenTimeout: Settings.privacyScreenTimeout
|
screenTimeout: Settings.privacyScreenTimeout
|
||||||
timelineRoot: adaptiveView
|
timelineRoot: adaptiveView
|
||||||
|
visible: Settings.privacyScreen
|
||||||
windowTarget: MainWindow
|
windowTarget: MainWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,19 +13,24 @@ import im.nheko 1.0
|
||||||
|
|
||||||
Page {
|
Page {
|
||||||
id: communitySidebar
|
id: communitySidebar
|
||||||
|
|
||||||
//leftPadding: Nheko.paddingSmall
|
//leftPadding: Nheko.paddingSmall
|
||||||
//rightPadding: Nheko.paddingSmall
|
//rightPadding: Nheko.paddingSmall
|
||||||
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 1.6)
|
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 1.6)
|
||||||
property bool collapsed: false
|
property bool collapsed: false
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: Nheko.theme.sidebarBackground
|
||||||
|
}
|
||||||
|
|
||||||
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
|
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
|
||||||
Connections {
|
Connections {
|
||||||
function onHideMenu() {
|
function onHideMenu() {
|
||||||
communityContextMenu.close()
|
communityContextMenu.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
target: MainWindow
|
target: MainWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
id: communitiesList
|
id: communitiesList
|
||||||
|
|
||||||
|
@ -36,15 +41,158 @@ Page {
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
ScrollBar.vertical: ScrollBar {
|
||||||
id: scrollbar
|
id: scrollbar
|
||||||
|
|
||||||
parent: !collapsed && Settings.scrollbarsInRoomlist ? communitiesList : null
|
parent: !collapsed && Settings.scrollbarsInRoomlist ? communitiesList : null
|
||||||
}
|
}
|
||||||
|
delegate: ItemDelegate {
|
||||||
|
id: communityItem
|
||||||
|
|
||||||
|
property color backgroundColor: palette.window
|
||||||
|
property color bubbleBackground: palette.highlight
|
||||||
|
property color bubbleText: palette.highlightedText
|
||||||
|
property color importantText: palette.text
|
||||||
|
required property var model
|
||||||
|
property color unimportantText: palette.buttonText
|
||||||
|
|
||||||
|
ToolTip.delay: Nheko.tooltipDelay
|
||||||
|
ToolTip.text: model.tooltip
|
||||||
|
ToolTip.visible: hovered && collapsed
|
||||||
|
height: avatarSize + 2 * Nheko.paddingMedium
|
||||||
|
state: "normal"
|
||||||
|
width: ListView.view.width - ((scrollbar.interactive && scrollbar.visible && scrollbar.parent) ? scrollbar.width : 0)
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: communityItem.backgroundColor
|
||||||
|
}
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
name: "highlight"
|
||||||
|
when: (communityItem.hovered || model.hidden) && !(Communities.currentTagId === model.id)
|
||||||
|
|
||||||
|
PropertyChanges {
|
||||||
|
backgroundColor: palette.dark
|
||||||
|
bubbleBackground: palette.highlight
|
||||||
|
bubbleText: palette.highlightedText
|
||||||
|
importantText: palette.brightText
|
||||||
|
target: communityItem
|
||||||
|
unimportantText: palette.brightText
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "selected"
|
||||||
|
when: Communities.currentTagId == model.id
|
||||||
|
|
||||||
|
PropertyChanges {
|
||||||
|
backgroundColor: palette.highlight
|
||||||
|
bubbleBackground: palette.highlightedText
|
||||||
|
bubbleText: palette.highlight
|
||||||
|
importantText: palette.highlightedText
|
||||||
|
target: communityItem
|
||||||
|
unimportantText: palette.highlightedText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
onClicked: Communities.setCurrentTagId(model.id)
|
||||||
|
onPressAndHold: communityContextMenu.show(model.id, model.hidden, model.muted)
|
||||||
|
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
|
||||||
|
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||||
|
|
||||||
|
onSingleTapped: communityContextMenu.show(model.id, model.hidden, model.muted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
id: r
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: Nheko.paddingMedium + (communitySidebar.collapsed ? 0 : (fontMetrics.lineSpacing * model.depth))
|
||||||
|
anchors.margins: Nheko.paddingMedium
|
||||||
|
spacing: Nheko.paddingMedium
|
||||||
|
|
||||||
|
ImageButton {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.preferredHeight: fontMetrics.lineSpacing
|
||||||
|
Layout.preferredWidth: fontMetrics.lineSpacing
|
||||||
|
ToolTip.delay: Nheko.tooltipDelay
|
||||||
|
ToolTip.text: model.collapsed ? qsTr("Expand") : qsTr("Collapse")
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
height: fontMetrics.lineSpacing
|
||||||
|
hoverEnabled: true
|
||||||
|
image: model.collapsed ? ":/icons/icons/ui/collapsed.svg" : ":/icons/icons/ui/expanded.svg"
|
||||||
|
visible: !communitySidebar.collapsed && model.collapsible
|
||||||
|
width: fontMetrics.lineSpacing
|
||||||
|
|
||||||
|
onClicked: model.collapsed = !model.collapsed
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
Layout.preferredWidth: fontMetrics.lineSpacing
|
||||||
|
visible: !communitySidebar.collapsed && !model.collapsible && Communities.containsSubspaces
|
||||||
|
}
|
||||||
|
Avatar {
|
||||||
|
id: avatar
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
color: communityItem.backgroundColor
|
||||||
|
displayName: model.displayName
|
||||||
|
enabled: false
|
||||||
|
height: avatarSize
|
||||||
|
roomid: model.id
|
||||||
|
url: {
|
||||||
|
if (model.avatarUrl.startsWith("mxc://"))
|
||||||
|
return model.avatarUrl.replace("mxc://", "image://MxcImage/");
|
||||||
|
else
|
||||||
|
return "image://colorimage/" + model.avatarUrl + "?" + communityItem.unimportantText;
|
||||||
|
}
|
||||||
|
width: avatarSize
|
||||||
|
|
||||||
|
NotificationBubble {
|
||||||
|
anchors.bottom: avatar.bottom
|
||||||
|
anchors.margins: -Nheko.paddingSmall
|
||||||
|
anchors.right: avatar.right
|
||||||
|
bubbleBackgroundColor: communityItem.bubbleBackground
|
||||||
|
bubbleTextColor: communityItem.bubbleText
|
||||||
|
font.pixelSize: fontMetrics.font.pixelSize * 0.6
|
||||||
|
hasLoudNotification: model.hasLoudNotification
|
||||||
|
mayBeVisible: communitySidebar.collapsed && !model.muted && Settings.spaceNotifications
|
||||||
|
notificationCount: model.unreadMessages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ElidedLabel {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
color: communityItem.importantText
|
||||||
|
elideWidth: width
|
||||||
|
fullText: model.displayName
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
visible: !communitySidebar.collapsed
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
NotificationBubble {
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
Layout.leftMargin: Nheko.paddingSmall
|
||||||
|
bubbleBackgroundColor: communityItem.bubbleBackground
|
||||||
|
bubbleTextColor: communityItem.bubbleText
|
||||||
|
hasLoudNotification: model.hasLoudNotification
|
||||||
|
mayBeVisible: !communitySidebar.collapsed && !model.muted && Settings.spaceNotifications
|
||||||
|
notificationCount: model.unreadMessages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Platform.Menu {
|
Platform.Menu {
|
||||||
id: communityContextMenu
|
id: communityContextMenu
|
||||||
|
|
||||||
property string tagId
|
|
||||||
property bool hidden
|
property bool hidden
|
||||||
property bool muted
|
property bool muted
|
||||||
|
property string tagId
|
||||||
|
|
||||||
function show(id_, hidden_, muted_) {
|
function show(id_, hidden_, muted_) {
|
||||||
tagId = id_;
|
tagId = id_;
|
||||||
|
@ -54,177 +202,19 @@ Page {
|
||||||
}
|
}
|
||||||
|
|
||||||
Platform.MenuItem {
|
Platform.MenuItem {
|
||||||
text: qsTr("Do not show notification counts for this community or tag.")
|
|
||||||
checkable: true
|
checkable: true
|
||||||
checked: communityContextMenu.muted
|
checked: communityContextMenu.muted
|
||||||
|
text: qsTr("Do not show notification counts for this community or tag.")
|
||||||
|
|
||||||
onTriggered: Communities.toggleTagMute(communityContextMenu.tagId)
|
onTriggered: Communities.toggleTagMute(communityContextMenu.tagId)
|
||||||
}
|
}
|
||||||
|
|
||||||
Platform.MenuItem {
|
Platform.MenuItem {
|
||||||
text: qsTr("Hide rooms with this tag or from this community by default.")
|
|
||||||
checkable: true
|
checkable: true
|
||||||
checked: communityContextMenu.hidden
|
checked: communityContextMenu.hidden
|
||||||
|
text: qsTr("Hide rooms with this tag or from this community by default.")
|
||||||
|
|
||||||
onTriggered: Communities.toggleTagId(communityContextMenu.tagId)
|
onTriggered: Communities.toggleTagId(communityContextMenu.tagId)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: ItemDelegate {
|
|
||||||
id: communityItem
|
|
||||||
|
|
||||||
property color backgroundColor: palette.window
|
|
||||||
property color importantText: palette.text
|
|
||||||
property color unimportantText: palette.buttonText
|
|
||||||
property color bubbleBackground: palette.highlight
|
|
||||||
property color bubbleText: palette.highlightedText
|
|
||||||
required property var model
|
|
||||||
|
|
||||||
height: avatarSize + 2 * Nheko.paddingMedium
|
|
||||||
width: ListView.view.width - ((scrollbar.interactive && scrollbar.visible && scrollbar.parent) ? scrollbar.width : 0)
|
|
||||||
state: "normal"
|
|
||||||
ToolTip.visible: hovered && collapsed
|
|
||||||
ToolTip.text: model.tooltip
|
|
||||||
ToolTip.delay: Nheko.tooltipDelay
|
|
||||||
onClicked: Communities.setCurrentTagId(model.id)
|
|
||||||
onPressAndHold: communityContextMenu.show(model.id, model.hidden, model.muted)
|
|
||||||
states: [
|
|
||||||
State {
|
|
||||||
name: "highlight"
|
|
||||||
when: (communityItem.hovered || model.hidden) && !(Communities.currentTagId === model.id)
|
|
||||||
|
|
||||||
PropertyChanges {
|
|
||||||
target: communityItem
|
|
||||||
backgroundColor: palette.dark
|
|
||||||
importantText: palette.brightText
|
|
||||||
unimportantText: palette.brightText
|
|
||||||
bubbleBackground: palette.highlight
|
|
||||||
bubbleText: palette.highlightedText
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
State {
|
|
||||||
name: "selected"
|
|
||||||
when: Communities.currentTagId == model.id
|
|
||||||
|
|
||||||
PropertyChanges {
|
|
||||||
target: communityItem
|
|
||||||
backgroundColor: palette.highlight
|
|
||||||
importantText: palette.highlightedText
|
|
||||||
unimportantText: palette.highlightedText
|
|
||||||
bubbleBackground: palette.highlightedText
|
|
||||||
bubbleText: palette.highlight
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
Item {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.RightButton
|
|
||||||
onSingleTapped: communityContextMenu.show(model.id, model.hidden, model.muted)
|
|
||||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
|
||||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: r
|
|
||||||
spacing: Nheko.paddingMedium
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Nheko.paddingMedium
|
|
||||||
anchors.leftMargin: Nheko.paddingMedium + (communitySidebar.collapsed ? 0 : (fontMetrics.lineSpacing * model.depth))
|
|
||||||
|
|
||||||
ImageButton {
|
|
||||||
visible: !communitySidebar.collapsed && model.collapsible
|
|
||||||
Layout.preferredHeight: fontMetrics.lineSpacing
|
|
||||||
Layout.preferredWidth: fontMetrics.lineSpacing
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
height: fontMetrics.lineSpacing
|
|
||||||
width: fontMetrics.lineSpacing
|
|
||||||
image: model.collapsed ? ":/icons/icons/ui/collapsed.svg" : ":/icons/icons/ui/expanded.svg"
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: Nheko.tooltipDelay
|
|
||||||
ToolTip.text: model.collapsed ? qsTr("Expand") : qsTr("Collapse")
|
|
||||||
hoverEnabled: true
|
|
||||||
|
|
||||||
onClicked: model.collapsed = !model.collapsed
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.preferredWidth: fontMetrics.lineSpacing
|
|
||||||
visible: !communitySidebar.collapsed && !model.collapsible && Communities.containsSubspaces
|
|
||||||
}
|
|
||||||
|
|
||||||
Avatar {
|
|
||||||
id: avatar
|
|
||||||
|
|
||||||
enabled: false
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
height: avatarSize
|
|
||||||
width: avatarSize
|
|
||||||
url: {
|
|
||||||
if (model.avatarUrl.startsWith("mxc://"))
|
|
||||||
return model.avatarUrl.replace("mxc://", "image://MxcImage/");
|
|
||||||
else
|
|
||||||
return "image://colorimage/" + model.avatarUrl + "?" + communityItem.unimportantText;
|
|
||||||
}
|
|
||||||
roomid: model.id
|
|
||||||
displayName: model.displayName
|
|
||||||
color: communityItem.backgroundColor
|
|
||||||
|
|
||||||
NotificationBubble {
|
|
||||||
notificationCount: model.unreadMessages
|
|
||||||
hasLoudNotification: model.hasLoudNotification
|
|
||||||
bubbleBackgroundColor: communityItem.bubbleBackground
|
|
||||||
bubbleTextColor: communityItem.bubbleText
|
|
||||||
font.pixelSize: fontMetrics.font.pixelSize * 0.6
|
|
||||||
mayBeVisible: communitySidebar.collapsed && !model.muted && Settings.spaceNotifications
|
|
||||||
anchors.right: avatar.right
|
|
||||||
anchors.bottom: avatar.bottom
|
|
||||||
anchors.margins: -Nheko.paddingSmall
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ElidedLabel {
|
|
||||||
visible: !communitySidebar.collapsed
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
color: communityItem.importantText
|
|
||||||
Layout.fillWidth: true
|
|
||||||
elideWidth: width
|
|
||||||
fullText: model.displayName
|
|
||||||
textFormat: Text.PlainText
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationBubble {
|
|
||||||
notificationCount: model.unreadMessages
|
|
||||||
hasLoudNotification: model.hasLoudNotification
|
|
||||||
bubbleBackgroundColor: communityItem.bubbleBackground
|
|
||||||
bubbleTextColor: communityItem.bubbleText
|
|
||||||
mayBeVisible: !communitySidebar.collapsed && !model.muted && Settings.spaceNotifications
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
Layout.leftMargin: Nheko.paddingSmall
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
color: communityItem.backgroundColor
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
color: Nheko.theme.sidebarBackground
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,117 +11,102 @@ import im.nheko 1.0
|
||||||
Control {
|
Control {
|
||||||
id: popup
|
id: popup
|
||||||
|
|
||||||
property alias currentIndex: listView.currentIndex
|
|
||||||
property string roomId
|
|
||||||
property string completerName
|
|
||||||
property var completer
|
|
||||||
property bool bottomToTop: true
|
|
||||||
property bool fullWidth: false
|
|
||||||
property bool centerRowContent: true
|
|
||||||
property int avatarHeight: 24
|
property int avatarHeight: 24
|
||||||
property int avatarWidth: 24
|
property int avatarWidth: 24
|
||||||
|
property bool bottomToTop: true
|
||||||
|
property bool centerRowContent: true
|
||||||
|
property var completer
|
||||||
|
property string completerName
|
||||||
|
property alias count: listView.count
|
||||||
|
property alias currentIndex: listView.currentIndex
|
||||||
|
property bool fullWidth: false
|
||||||
|
property string roomId
|
||||||
property int rowMargin: 0
|
property int rowMargin: 0
|
||||||
property int rowSpacing: Nheko.paddingSmall
|
property int rowSpacing: Nheko.paddingSmall
|
||||||
property alias count: listView.count
|
|
||||||
|
|
||||||
signal completionClicked(string completion)
|
signal completionClicked(string completion)
|
||||||
signal completionSelected(string id)
|
signal completionSelected(string id)
|
||||||
|
|
||||||
function up() {
|
function changeCompleter() {
|
||||||
if (bottomToTop)
|
if (completerName) {
|
||||||
down_();
|
completer = TimelineManager.completerFor(completerName, completerName == "room" ? "" : (popup.roomId != "" ? popup.roomId : room.roomId));
|
||||||
else
|
completer.setSearchString("");
|
||||||
up_();
|
} else {
|
||||||
|
completer = undefined;
|
||||||
|
}
|
||||||
|
currentIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function down() {
|
|
||||||
if (bottomToTop)
|
|
||||||
up_();
|
|
||||||
else
|
|
||||||
down_();
|
|
||||||
}
|
|
||||||
|
|
||||||
function up_() {
|
|
||||||
currentIndex = currentIndex - 1;
|
|
||||||
if (currentIndex == -2)
|
|
||||||
currentIndex = listView.count - 1;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function down_() {
|
|
||||||
currentIndex = currentIndex + 1;
|
|
||||||
if (currentIndex >= listView.count)
|
|
||||||
currentIndex = -1;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function currentCompletion() {
|
function currentCompletion() {
|
||||||
if (currentIndex > -1 && currentIndex < listView.count)
|
if (currentIndex > -1 && currentIndex < listView.count)
|
||||||
return completer.completionAt(currentIndex);
|
return completer.completionAt(currentIndex);
|
||||||
else
|
else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
function down() {
|
||||||
|
if (bottomToTop)
|
||||||
|
up_();
|
||||||
|
else
|
||||||
|
down_();
|
||||||
|
}
|
||||||
|
function down_() {
|
||||||
|
currentIndex = currentIndex + 1;
|
||||||
|
if (currentIndex >= listView.count)
|
||||||
|
currentIndex = -1;
|
||||||
|
}
|
||||||
function finishCompletion() {
|
function finishCompletion() {
|
||||||
if (popup.completerName == "room")
|
if (popup.completerName == "room")
|
||||||
popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.roomid);
|
popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.roomid);
|
||||||
else if (popup.completerName == "user")
|
else if (popup.completerName == "user")
|
||||||
popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.userid);
|
popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.userid);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
function up() {
|
||||||
function changeCompleter() {
|
if (bottomToTop)
|
||||||
if (completerName) {
|
down_();
|
||||||
completer = TimelineManager.completerFor(completerName, completerName == "room" ? "" : (popup.roomId != "" ? popup.roomId : room.roomId));
|
else
|
||||||
completer.setSearchString("");
|
up_();
|
||||||
} else {
|
}
|
||||||
completer = undefined;
|
function up_() {
|
||||||
}
|
currentIndex = currentIndex - 1;
|
||||||
currentIndex = -1
|
if (currentIndex == -2)
|
||||||
|
currentIndex = listView.count - 1;
|
||||||
}
|
}
|
||||||
onCompleterNameChanged: changeCompleter()
|
|
||||||
onRoomIdChanged: changeCompleter()
|
|
||||||
|
|
||||||
bottomPadding: 1
|
bottomPadding: 1
|
||||||
leftPadding: 1
|
leftPadding: 1
|
||||||
topPadding: 1
|
|
||||||
rightPadding: 1
|
rightPadding: 1
|
||||||
|
topPadding: 1
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
border.color: palette.mid
|
||||||
|
color: palette.base
|
||||||
|
}
|
||||||
contentItem: ListView {
|
contentItem: ListView {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
// If we have fewer than 7 items, just use the list view's content height.
|
clip: true
|
||||||
|
displayMarginBeginning: height / 2
|
||||||
|
displayMarginEnd: height / 2
|
||||||
|
highlightFollowsCurrentItem: true
|
||||||
|
|
||||||
|
// If we have fewer than 7 items, just use the list view's content height.
|
||||||
// Otherwise, we want to show 7 items. Each item consists of row spacing between rows, row margins
|
// Otherwise, we want to show 7 items. Each item consists of row spacing between rows, row margins
|
||||||
// on each side of a row, 1px of padding above the first item and below the last item, and nominally
|
// on each side of a row, 1px of padding above the first item and below the last item, and nominally
|
||||||
// some kind of content height. avatarHeight is used for just about every delegate, so we're using
|
// some kind of content height. avatarHeight is used for just about every delegate, so we're using
|
||||||
// that until we find something better. Put is all together and you have the formula below!
|
// that until we find something better. Put is all together and you have the formula below!
|
||||||
implicitHeight: Math.min(contentHeight, 6*rowSpacing + 7*(popup.avatarHeight + 2*rowMargin))
|
implicitHeight: Math.min(contentHeight, 6 * rowSpacing + 7 * (popup.avatarHeight + 2 * rowMargin))
|
||||||
clip: true
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: deadTimer
|
|
||||||
interval: 50
|
|
||||||
}
|
|
||||||
|
|
||||||
onContentYChanged: deadTimer.restart()
|
|
||||||
|
|
||||||
// Broken, see https://bugreports.qt.io/browse/QTBUG-102811
|
// Broken, see https://bugreports.qt.io/browse/QTBUG-102811
|
||||||
//reuseItems: true
|
//reuseItems: true
|
||||||
implicitWidth: listView.contentItem.childrenRect.width
|
implicitWidth: listView.contentItem.childrenRect.width
|
||||||
model: completer
|
model: completer
|
||||||
verticalLayoutDirection: popup.bottomToTop ? ListView.BottomToTop : ListView.TopToBottom
|
|
||||||
spacing: rowSpacing
|
|
||||||
pixelAligned: true
|
pixelAligned: true
|
||||||
highlightFollowsCurrentItem: true
|
spacing: rowSpacing
|
||||||
|
verticalLayoutDirection: popup.bottomToTop ? ListView.BottomToTop : ListView.TopToBottom
|
||||||
displayMarginBeginning: height / 2
|
|
||||||
displayMarginEnd: height / 2
|
|
||||||
|
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
property variant modelData: model
|
property variant modelData: model
|
||||||
|
|
||||||
ListView.delayRemove: true
|
ListView.delayRemove: true
|
||||||
|
|
||||||
color: model.index == popup.currentIndex ? palette.highlight : palette.base
|
color: model.index == popup.currentIndex ? palette.highlight : palette.base
|
||||||
height: (chooser.child?.implicitHeight ?? 0) + 2 * popup.rowMargin
|
height: (chooser.child?.implicitHeight ?? 0) + 2 * popup.rowMargin
|
||||||
implicitWidth: fullWidth ? ListView.view.width : chooser.child.implicitWidth + 4
|
implicitWidth: fullWidth ? ListView.view.width : chooser.child.implicitWidth + 4
|
||||||
|
@ -131,26 +116,27 @@ Control {
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
onPositionChanged: if (!listView.moving && !deadTimer.running) popup.currentIndex = model.index
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
popup.completionClicked(completer.completionAt(model.index));
|
popup.completionClicked(completer.completionAt(model.index));
|
||||||
if (popup.completerName == "room")
|
if (popup.completerName == "room")
|
||||||
popup.completionSelected(model.roomid);
|
popup.completionSelected(model.roomid);
|
||||||
else if (popup.completerName == "user")
|
else if (popup.completerName == "user")
|
||||||
popup.completionSelected(model.userid);
|
popup.completionSelected(model.userid);
|
||||||
}
|
}
|
||||||
|
onPositionChanged: if (!listView.moving && !deadTimer.running)
|
||||||
|
popup.currentIndex = model.index
|
||||||
}
|
}
|
||||||
Ripple {
|
Ripple {
|
||||||
color: Qt.rgba(palette.base.r, palette.base.g, palette.base.b, 0.5)
|
color: Qt.rgba(palette.base.r, palette.base.g, palette.base.b, 0.5)
|
||||||
}
|
}
|
||||||
|
|
||||||
DelegateChooser {
|
DelegateChooser {
|
||||||
id: chooser
|
id: chooser
|
||||||
|
|
||||||
roleValue: popup.completerName
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: popup.rowMargin
|
anchors.margins: popup.rowMargin
|
||||||
enabled: false
|
enabled: false
|
||||||
|
roleValue: popup.completerName
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: "user"
|
roleValue: "user"
|
||||||
|
@ -162,28 +148,23 @@ Control {
|
||||||
spacing: rowSpacing
|
spacing: rowSpacing
|
||||||
|
|
||||||
Avatar {
|
Avatar {
|
||||||
height: popup.avatarHeight
|
|
||||||
width: popup.avatarWidth
|
|
||||||
displayName: model.displayName
|
displayName: model.displayName
|
||||||
userid: model.userid
|
|
||||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
|
||||||
enabled: false
|
enabled: false
|
||||||
|
height: popup.avatarHeight
|
||||||
|
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||||
|
userid: model.userid
|
||||||
|
width: popup.avatarWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: model.displayName
|
|
||||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
||||||
|
text: model.displayName
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: "(" + model.userid + ")"
|
|
||||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
|
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
|
||||||
|
text: "(" + model.userid + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: "emoji"
|
roleValue: "emoji"
|
||||||
|
|
||||||
|
@ -194,39 +175,33 @@ Control {
|
||||||
spacing: rowSpacing
|
spacing: rowSpacing
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
visible: !!model.unicode
|
|
||||||
text: model.unicode
|
|
||||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
||||||
font: Settings.emojiFont
|
font: Settings.emojiFont
|
||||||
|
text: model.unicode
|
||||||
|
visible: !!model.unicode
|
||||||
}
|
}
|
||||||
|
|
||||||
Avatar {
|
Avatar {
|
||||||
visible: !model.unicode
|
crop: false
|
||||||
height: popup.avatarHeight
|
|
||||||
width: popup.avatarWidth
|
|
||||||
displayName: model.shortcode
|
displayName: model.shortcode
|
||||||
|
enabled: false
|
||||||
|
height: popup.avatarHeight
|
||||||
//userid: model.shortcode
|
//userid: model.shortcode
|
||||||
url: (model.url ? model.url : "").replace("mxc://", "image://MxcImage/")
|
url: (model.url ? model.url : "").replace("mxc://", "image://MxcImage/")
|
||||||
enabled: false
|
visible: !model.unicode
|
||||||
crop: false
|
width: popup.avatarWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.leftMargin: Nheko.paddingSmall
|
Layout.leftMargin: Nheko.paddingSmall
|
||||||
Layout.rightMargin: Nheko.paddingSmall
|
Layout.rightMargin: Nheko.paddingSmall
|
||||||
text: model.shortcode
|
|
||||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
||||||
|
text: model.shortcode
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: "(" + model.packname + ")"
|
|
||||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
|
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
|
||||||
|
text: "(" + model.packname + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: "command"
|
roleValue: "command"
|
||||||
|
|
||||||
|
@ -237,20 +212,16 @@ Control {
|
||||||
spacing: rowSpacing
|
spacing: rowSpacing
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: model.name
|
|
||||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
||||||
font.bold: true
|
font.bold: true
|
||||||
|
text: model.name
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: model.description
|
|
||||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
|
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
|
||||||
|
text: model.description
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: "room"
|
roleValue: "room"
|
||||||
|
|
||||||
|
@ -261,26 +232,22 @@ Control {
|
||||||
spacing: rowSpacing
|
spacing: rowSpacing
|
||||||
|
|
||||||
Avatar {
|
Avatar {
|
||||||
height: popup.avatarHeight
|
|
||||||
width: popup.avatarWidth
|
|
||||||
displayName: model.roomName
|
displayName: model.roomName
|
||||||
|
enabled: false
|
||||||
|
height: popup.avatarHeight
|
||||||
roomid: model.roomid
|
roomid: model.roomid
|
||||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||||
enabled: false
|
width: popup.avatarWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: model.roomName
|
|
||||||
font.pixelSize: popup.avatarHeight * 0.5
|
|
||||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
||||||
font.italic: model.isTombstoned
|
font.italic: model.isTombstoned
|
||||||
|
font.pixelSize: popup.avatarHeight * 0.5
|
||||||
|
text: model.roomName
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: "roomAliases"
|
roleValue: "roomAliases"
|
||||||
|
|
||||||
|
@ -291,41 +258,38 @@ Control {
|
||||||
spacing: rowSpacing
|
spacing: rowSpacing
|
||||||
|
|
||||||
Avatar {
|
Avatar {
|
||||||
height: popup.avatarHeight
|
|
||||||
width: popup.avatarWidth
|
|
||||||
displayName: model.roomName
|
displayName: model.roomName
|
||||||
|
enabled: false
|
||||||
|
height: popup.avatarHeight
|
||||||
roomid: model.roomid
|
roomid: model.roomid
|
||||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||||
enabled: false
|
width: popup.avatarWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: model.roomName
|
|
||||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
||||||
font.italic: model.isTombstoned
|
font.italic: model.isTombstoned
|
||||||
|
text: model.roomName
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: "(" + model.roomAlias + ")"
|
|
||||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
|
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
|
||||||
|
text: "(" + model.roomAlias + ")"
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onContentYChanged: deadTimer.restart()
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: deadTimer
|
||||||
|
|
||||||
|
interval: 50
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCompleterNameChanged: changeCompleter()
|
||||||
background: Rectangle {
|
onRoomIdChanged: changeCompleter()
|
||||||
color: palette.base
|
|
||||||
border.color: palette.mid
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,21 +9,20 @@ import im.nheko 1.0
|
||||||
Label {
|
Label {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property alias fullText: metrics.text
|
|
||||||
property alias elideWidth: metrics.elideWidth
|
property alias elideWidth: metrics.elideWidth
|
||||||
|
property alias fullText: metrics.text
|
||||||
property int fullTextWidth: Math.ceil(metrics.advanceWidth)
|
property int fullTextWidth: Math.ceil(metrics.advanceWidth)
|
||||||
|
|
||||||
color: palette.text
|
color: palette.text
|
||||||
text: (textFormat == Text.PlainText) ? metrics.elidedText : TimelineManager.escapeEmoji(metrics.elidedText)
|
|
||||||
maximumLineCount: 1
|
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
text: (textFormat == Text.PlainText) ? metrics.elidedText : TimelineManager.escapeEmoji(metrics.elidedText)
|
||||||
textFormat: Text.PlainText
|
textFormat: Text.PlainText
|
||||||
|
|
||||||
TextMetrics {
|
TextMetrics {
|
||||||
id: metrics
|
id: metrics
|
||||||
|
|
||||||
font.pointSize: root.font.pointSize
|
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
|
font.pointSize: root.font.pointSize
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,32 +11,40 @@ Image {
|
||||||
id: stateImg
|
id: stateImg
|
||||||
|
|
||||||
property bool encrypted: false
|
property bool encrypted: false
|
||||||
property int trust: Crypto.Unverified
|
|
||||||
property string unencryptedIcon: ":/icons/icons/ui/shield-filled-cross.svg"
|
|
||||||
property color unencryptedColor: Nheko.theme.error
|
|
||||||
property color unencryptedHoverColor: unencryptedColor
|
|
||||||
property bool hovered: ma.hovered
|
property bool hovered: ma.hovered
|
||||||
|
|
||||||
property string sourceUrl: {
|
property string sourceUrl: {
|
||||||
if (!encrypted)
|
if (!encrypted)
|
||||||
return "image://colorimage/" + unencryptedIcon + "?";
|
return "image://colorimage/" + unencryptedIcon + "?";
|
||||||
|
|
||||||
switch (trust) {
|
switch (trust) {
|
||||||
case Crypto.Verified:
|
case Crypto.Verified:
|
||||||
return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?";
|
return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?";
|
||||||
case Crypto.TOFU:
|
case Crypto.TOFU:
|
||||||
return "image://colorimage/:/icons/icons/ui/shield-filled.svg?";
|
return "image://colorimage/:/icons/icons/ui/shield-filled.svg?";
|
||||||
case Crypto.Unverified:
|
case Crypto.Unverified:
|
||||||
return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?";
|
return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?";
|
||||||
default:
|
default:
|
||||||
return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?";
|
return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
property int trust: Crypto.Unverified
|
||||||
|
property color unencryptedColor: Nheko.theme.error
|
||||||
|
property color unencryptedHoverColor: unencryptedColor
|
||||||
|
property string unencryptedIcon: ":/icons/icons/ui/shield-filled-cross.svg"
|
||||||
|
|
||||||
width: 16
|
ToolTip.text: {
|
||||||
|
if (!encrypted)
|
||||||
|
return qsTr("This message is not encrypted!");
|
||||||
|
switch (trust) {
|
||||||
|
case Crypto.Verified:
|
||||||
|
return qsTr("Encrypted by a verified device");
|
||||||
|
case Crypto.TOFU:
|
||||||
|
return qsTr("Encrypted by an unverified device, but you have trusted that user so far.");
|
||||||
|
default:
|
||||||
|
return qsTr("Encrypted by an unverified device or the key is from an untrusted source like the key backup.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToolTip.visible: stateImg.hovered
|
||||||
height: 16
|
height: 16
|
||||||
sourceSize.height: height
|
|
||||||
sourceSize.width: width
|
|
||||||
source: {
|
source: {
|
||||||
if (encrypted) {
|
if (encrypted) {
|
||||||
switch (trust) {
|
switch (trust) {
|
||||||
|
@ -51,23 +59,12 @@ Image {
|
||||||
return sourceUrl + (stateImg.hovered ? unencryptedHoverColor : unencryptedColor);
|
return sourceUrl + (stateImg.hovered ? unencryptedHoverColor : unencryptedColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToolTip.visible: stateImg.hovered
|
sourceSize.height: height
|
||||||
ToolTip.text: {
|
sourceSize.width: width
|
||||||
if (!encrypted)
|
width: 16
|
||||||
return qsTr("This message is not encrypted!");
|
|
||||||
|
|
||||||
switch (trust) {
|
|
||||||
case Crypto.Verified:
|
|
||||||
return qsTr("Encrypted by a verified device");
|
|
||||||
case Crypto.TOFU:
|
|
||||||
return qsTr("Encrypted by an unverified device, but you have trusted that user so far.");
|
|
||||||
default:
|
|
||||||
return qsTr("Encrypted by an unverified device or the key is from an untrusted source like the key backup.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HoverHandler {
|
HoverHandler {
|
||||||
id: ma
|
id: ma
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,21 @@ Popup {
|
||||||
mid = mid_in;
|
mid = mid_in;
|
||||||
}
|
}
|
||||||
|
|
||||||
x: Math.round(parent.width / 2 - width / 2)
|
leftPadding: 10
|
||||||
y: Math.round(parent.height / 4)
|
|
||||||
modal: true
|
modal: true
|
||||||
parent: Overlay.overlay
|
parent: Overlay.overlay
|
||||||
width: timelineRoot.width * 0.8
|
|
||||||
leftPadding: 10
|
|
||||||
rightPadding: 10
|
rightPadding: 10
|
||||||
|
width: timelineRoot.width * 0.8
|
||||||
|
x: Math.round(parent.width / 2 - width / 2)
|
||||||
|
y: Math.round(parent.height / 4)
|
||||||
|
|
||||||
|
Overlay.modal: Rectangle {
|
||||||
|
color: Qt.rgba(palette.window.r, palette.window.g, palette.window.b, 0.7)
|
||||||
|
}
|
||||||
|
background: Rectangle {
|
||||||
|
color: palette.window
|
||||||
|
}
|
||||||
|
|
||||||
onOpened: {
|
onOpened: {
|
||||||
roomTextInput.forceActiveFocus();
|
roomTextInput.forceActiveFocus();
|
||||||
}
|
}
|
||||||
|
@ -35,46 +43,40 @@ Popup {
|
||||||
Label {
|
Label {
|
||||||
id: titleLabel
|
id: titleLabel
|
||||||
|
|
||||||
text: qsTr("Forward Message")
|
|
||||||
font.bold: true
|
|
||||||
bottomPadding: 10
|
bottomPadding: 10
|
||||||
color: palette.text
|
color: palette.text
|
||||||
|
font.bold: true
|
||||||
|
text: qsTr("Forward Message")
|
||||||
}
|
}
|
||||||
|
|
||||||
Reply {
|
Reply {
|
||||||
id: replyPreview
|
id: replyPreview
|
||||||
|
|
||||||
property var modelData: room ? room.getDump(mid, "") : {
|
property var modelData: room ? room.getDump(mid, "") : {}
|
||||||
}
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
userColor: TimelineManager.userColor(modelData.userId, palette.window)
|
|
||||||
blurhash: modelData.blurhash ?? ""
|
blurhash: modelData.blurhash ?? ""
|
||||||
body: modelData.body ?? ""
|
body: modelData.body ?? ""
|
||||||
formattedBody: modelData.formattedBody ?? ""
|
encryptionError: modelData.encryptionError ?? ""
|
||||||
eventId: modelData.eventId ?? ""
|
eventId: modelData.eventId ?? ""
|
||||||
filename: modelData.filename ?? ""
|
filename: modelData.filename ?? ""
|
||||||
filesize: modelData.filesize ?? ""
|
filesize: modelData.filesize ?? ""
|
||||||
|
formattedBody: modelData.formattedBody ?? ""
|
||||||
|
isOnlyEmoji: modelData.isOnlyEmoji ?? false
|
||||||
|
originalWidth: modelData.originalWidth ?? 0
|
||||||
proportionalHeight: modelData.proportionalHeight ?? 1
|
proportionalHeight: modelData.proportionalHeight ?? 1
|
||||||
type: modelData.type ?? MtxEvent.UnknownMessage
|
type: modelData.type ?? MtxEvent.UnknownMessage
|
||||||
typeString: modelData.typeString ?? ""
|
typeString: modelData.typeString ?? ""
|
||||||
url: modelData.url ?? ""
|
url: modelData.url ?? ""
|
||||||
originalWidth: modelData.originalWidth ?? 0
|
userColor: TimelineManager.userColor(modelData.userId, palette.window)
|
||||||
isOnlyEmoji: modelData.isOnlyEmoji ?? false
|
|
||||||
userId: modelData.userId ?? ""
|
userId: modelData.userId ?? ""
|
||||||
userName: modelData.userName ?? ""
|
userName: modelData.userName ?? ""
|
||||||
encryptionError: modelData.encryptionError ?? ""
|
width: parent.width
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixTextField {
|
MatrixTextField {
|
||||||
id: roomTextInput
|
id: roomTextInput
|
||||||
|
|
||||||
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
|
|
||||||
color: palette.text
|
color: palette.text
|
||||||
onTextEdited: {
|
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
|
||||||
completerPopup.completer.searchString = text;
|
|
||||||
}
|
|
||||||
Keys.onPressed: {
|
Keys.onPressed: {
|
||||||
if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
|
if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
|
@ -90,43 +92,32 @@ Popup {
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onTextEdited: {
|
||||||
|
completerPopup.completer.searchString = text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Completer {
|
Completer {
|
||||||
id: completerPopup
|
id: completerPopup
|
||||||
|
|
||||||
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
|
|
||||||
completerName: "room"
|
|
||||||
fullWidth: true
|
|
||||||
centerRowContent: false
|
|
||||||
avatarHeight: 24
|
avatarHeight: 24
|
||||||
avatarWidth: 24
|
avatarWidth: 24
|
||||||
bottomToTop: false
|
bottomToTop: false
|
||||||
|
centerRowContent: false
|
||||||
|
completerName: "room"
|
||||||
|
fullWidth: true
|
||||||
|
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onCompletionSelected(id) {
|
function onCompletionSelected(id) {
|
||||||
room.forwardMessage(messageContextMenu.eventId, id);
|
room.forwardMessage(messageContextMenu.eventId, id);
|
||||||
forwardMessagePopup.close();
|
forwardMessagePopup.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCountChanged() {
|
function onCountChanged() {
|
||||||
if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count))
|
if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count))
|
||||||
completerPopup.currentIndex = 0;
|
completerPopup.currentIndex = 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
target: completerPopup
|
target: completerPopup
|
||||||
}
|
}
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
color: palette.window
|
|
||||||
}
|
|
||||||
|
|
||||||
Overlay.modal: Rectangle {
|
|
||||||
color: Qt.rgba(palette.window.r, palette.window.g, palette.window.b, 0.7)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,38 +10,35 @@ import im.nheko 1.0 // for cursor shape
|
||||||
AbstractButton {
|
AbstractButton {
|
||||||
id: button
|
id: button
|
||||||
|
|
||||||
property alias cursor: mouseArea.cursorShape
|
|
||||||
property string image: undefined
|
|
||||||
property color highlightColor: palette.highlight
|
|
||||||
property color buttonTextColor: palette.buttonText
|
property color buttonTextColor: palette.buttonText
|
||||||
property bool changeColorOnHover: true
|
property bool changeColorOnHover: true
|
||||||
|
property alias cursor: mouseArea.cursorShape
|
||||||
|
property color highlightColor: palette.highlight
|
||||||
|
property string image: undefined
|
||||||
property bool ripple: true
|
property bool ripple: true
|
||||||
|
|
||||||
focusPolicy: Qt.NoFocus
|
focusPolicy: Qt.NoFocus
|
||||||
width: 16
|
|
||||||
height: 16
|
height: 16
|
||||||
|
width: 16
|
||||||
|
|
||||||
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
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
source: image != "" ? ("image://colorimage/" + image + "?" + ((button.hovered && changeColorOnHover) ? highlightColor : buttonTextColor)) : ""
|
source: image != "" ? ("image://colorimage/" + image + "?" + ((button.hovered && changeColorOnHover) ? highlightColor : buttonTextColor)) : ""
|
||||||
sourceSize.height: button.height
|
sourceSize.height: button.height
|
||||||
sourceSize.width: button.width
|
sourceSize.width: button.width
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CursorShape {
|
CursorShape {
|
||||||
id: mouseArea
|
id: mouseArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
}
|
}
|
||||||
|
|
||||||
Ripple {
|
Ripple {
|
||||||
enabled: button.ripple
|
|
||||||
color: Qt.rgba(buttonTextColor.r, buttonTextColor.g, buttonTextColor.b, 0.5)
|
color: Qt.rgba(buttonTextColor.r, buttonTextColor.g, buttonTextColor.b, 0.5)
|
||||||
|
enabled: button.ripple
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,22 +11,23 @@ TextEdit {
|
||||||
|
|
||||||
property alias cursorShape: cs.cursorShape
|
property alias cursorShape: cs.cursorShape
|
||||||
|
|
||||||
textFormat: TextEdit.RichText
|
ToolTip.text: hoveredLink
|
||||||
readOnly: true
|
ToolTip.visible: hoveredLink || false
|
||||||
focus: false
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
selectByMouse: !Settings.mobileMode
|
|
||||||
// this always has to be enabled, otherwise you can't click links anymore!
|
// this always has to be enabled, otherwise you can't click links anymore!
|
||||||
//enabled: selectByMouse
|
//enabled: selectByMouse
|
||||||
color: palette.text
|
color: palette.text
|
||||||
onLinkActivated: Nheko.openLink(link)
|
focus: false
|
||||||
ToolTip.visible: hoveredLink || false
|
readOnly: true
|
||||||
ToolTip.text: hoveredLink
|
selectByMouse: !Settings.mobileMode
|
||||||
|
textFormat: TextEdit.RichText
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
|
||||||
// Setting a tooltip delay makes the hover text empty .-.
|
// Setting a tooltip delay makes the hover text empty .-.
|
||||||
//ToolTip.delay: Nheko.tooltipDelay
|
//ToolTip.delay: Nheko.tooltipDelay
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
TimelineManager.fixImageRendering(r.textDocument, r);
|
TimelineManager.fixImageRendering(r.textDocument, r);
|
||||||
}
|
}
|
||||||
|
onLinkActivated: Nheko.openLink(link)
|
||||||
|
|
||||||
CursorShape {
|
CursorShape {
|
||||||
id: cs
|
id: cs
|
||||||
|
@ -34,5 +35,4 @@ TextEdit {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
cursorShape: hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,67 +7,63 @@ import QtQuick.Controls 2.12
|
||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
import im.nheko 1.0
|
import im.nheko 1.0
|
||||||
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: c
|
id: c
|
||||||
|
|
||||||
property color backgroundColor: palette.base
|
property color backgroundColor: palette.base
|
||||||
property alias color: labelC.color
|
property alias color: labelC.color
|
||||||
property alias textPadding: input.padding
|
property alias echoMode: input.echoMode
|
||||||
property alias text: input.text
|
property alias font: input.font
|
||||||
|
property var hasClear: false
|
||||||
property alias label: labelC.text
|
property alias label: labelC.text
|
||||||
property alias placeholderText: input.placeholderText
|
property alias placeholderText: input.placeholderText
|
||||||
property alias font: input.font
|
|
||||||
property alias echoMode: input.echoMode
|
|
||||||
property alias selectByMouse: input.selectByMouse
|
property alias selectByMouse: input.selectByMouse
|
||||||
property var hasClear: false
|
property alias text: input.text
|
||||||
|
property alias textPadding: input.padding
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: timer
|
|
||||||
interval: 350
|
|
||||||
onTriggered: editingFinished()
|
|
||||||
}
|
|
||||||
|
|
||||||
onTextChanged: timer.restart()
|
|
||||||
|
|
||||||
signal textEdited
|
|
||||||
signal accepted
|
signal accepted
|
||||||
signal editingFinished
|
signal editingFinished
|
||||||
|
signal textEdited
|
||||||
function forceActiveFocus() {
|
|
||||||
input.forceActiveFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
function clear() {
|
function clear() {
|
||||||
input.clear();
|
input.clear();
|
||||||
}
|
}
|
||||||
|
function forceActiveFocus() {
|
||||||
|
input.forceActiveFocus();
|
||||||
|
}
|
||||||
|
|
||||||
ToolTip.delay: Nheko.tooltipDelay
|
ToolTip.delay: Nheko.tooltipDelay
|
||||||
ToolTip.visible: hover.hovered
|
ToolTip.visible: hover.hovered
|
||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
Item {
|
onTextChanged: timer.restart()
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: labelC.contentHeight
|
|
||||||
Layout.margins: input.padding
|
|
||||||
Layout.bottomMargin: Nheko.paddingSmall
|
|
||||||
visible: labelC.text
|
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: timer
|
||||||
|
|
||||||
|
interval: 350
|
||||||
|
|
||||||
|
onTriggered: editingFinished()
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
Layout.bottomMargin: Nheko.paddingSmall
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: input.padding
|
||||||
|
Layout.preferredHeight: labelC.contentHeight
|
||||||
|
visible: labelC.text
|
||||||
z: 1
|
z: 1
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: labelC
|
id: labelC
|
||||||
|
|
||||||
y: contentHeight + input.padding + Nheko.paddingSmall
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
color: palette.text
|
color: palette.text
|
||||||
|
enabled: false
|
||||||
|
font.letterSpacing: input.font.pixelSize * 0.02
|
||||||
font.pixelSize: input.font.pixelSize
|
font.pixelSize: input.font.pixelSize
|
||||||
font.weight: Font.DemiBold
|
font.weight: Font.DemiBold
|
||||||
font.letterSpacing: input.font.pixelSize * 0.02
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
state: labelC.text && (input.activeFocus == true || input.text) ? "focused" : ""
|
state: labelC.text && (input.activeFocus == true || input.text) ? "focused" : ""
|
||||||
|
width: parent.width
|
||||||
|
y: contentHeight + input.padding + Nheko.paddingSmall
|
||||||
|
|
||||||
states: State {
|
states: State {
|
||||||
name: "focused"
|
name: "focused"
|
||||||
|
@ -76,50 +72,40 @@ ColumnLayout {
|
||||||
target: labelC
|
target: labelC
|
||||||
y: 0
|
y: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: input
|
|
||||||
opacity: 1
|
opacity: 1
|
||||||
|
target: input
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
transitions: Transition {
|
transitions: Transition {
|
||||||
from: ""
|
from: ""
|
||||||
to: "focused"
|
|
||||||
reversible: true
|
reversible: true
|
||||||
|
to: "focused"
|
||||||
|
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: labelC
|
alwaysRunToEnd: true
|
||||||
|
duration: 210
|
||||||
|
easing.type: Easing.InCubic
|
||||||
properties: "y"
|
properties: "y"
|
||||||
duration: 210
|
target: labelC
|
||||||
easing.type: Easing.InCubic
|
|
||||||
alwaysRunToEnd: true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: input
|
alwaysRunToEnd: true
|
||||||
properties: "opacity"
|
|
||||||
duration: 210
|
duration: 210
|
||||||
easing.type: Easing.InCubic
|
easing.type: Easing.InCubic
|
||||||
alwaysRunToEnd: true
|
properties: "opacity"
|
||||||
|
target: input
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
id: input
|
id: input
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
color: labelC.color
|
color: labelC.color
|
||||||
opacity: labelC.text ? 0 : 1
|
|
||||||
focus: true
|
focus: true
|
||||||
|
opacity: labelC.text ? 0 : 1
|
||||||
onTextEdited: c.textEdited()
|
|
||||||
onAccepted: c.accepted()
|
|
||||||
onEditingFinished: c.editingFinished()
|
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
id: backgroundRect
|
id: backgroundRect
|
||||||
|
@ -127,44 +113,46 @@ ColumnLayout {
|
||||||
color: labelC.text ? "transparent" : backgroundColor
|
color: labelC.text ? "transparent" : backgroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onAccepted: c.accepted()
|
||||||
|
onEditingFinished: c.editingFinished()
|
||||||
|
onTextEdited: c.textEdited()
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
id: clearText
|
id: clearText
|
||||||
|
|
||||||
|
focusPolicy: Qt.NoFocus
|
||||||
|
hoverEnabled: true
|
||||||
|
image: ":/icons/icons/ui/round-remove-button.svg"
|
||||||
visible: c.hasClear && searchField.text !== ''
|
visible: c.hasClear && searchField.text !== ''
|
||||||
|
|
||||||
image: ":/icons/icons/ui/round-remove-button.svg"
|
|
||||||
focusPolicy: Qt.NoFocus
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
searchField.clear()
|
searchField.clear();
|
||||||
topBar.searchString = "";
|
topBar.searchString = "";
|
||||||
}
|
}
|
||||||
hoverEnabled: true
|
|
||||||
anchors {
|
anchors {
|
||||||
top: parent.top
|
|
||||||
bottom: parent.bottom
|
bottom: parent.bottom
|
||||||
right: parent.right
|
right: parent.right
|
||||||
rightMargin: Nheko.paddingSmall
|
rightMargin: Nheko.paddingSmall
|
||||||
|
top: parent.top
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: blueBar
|
id: blueBar
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
color: palette.highlight
|
color: palette.highlight
|
||||||
height: 1
|
height: 1
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: blackBar
|
id: blackBar
|
||||||
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
height: parent.height*2
|
anchors.top: parent.top
|
||||||
width: 0
|
|
||||||
color: palette.text
|
color: palette.text
|
||||||
|
height: parent.height * 2
|
||||||
|
width: 0
|
||||||
|
|
||||||
states: State {
|
states: State {
|
||||||
name: "focused"
|
name: "focused"
|
||||||
|
@ -174,31 +162,25 @@ ColumnLayout {
|
||||||
target: blackBar
|
target: blackBar
|
||||||
width: blueBar.width
|
width: blueBar.width
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
transitions: Transition {
|
transitions: Transition {
|
||||||
from: ""
|
from: ""
|
||||||
to: "focused"
|
|
||||||
reversible: true
|
reversible: true
|
||||||
|
to: "focused"
|
||||||
|
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: blackBar
|
alwaysRunToEnd: true
|
||||||
properties: "width"
|
|
||||||
duration: 310
|
duration: 310
|
||||||
easing.type: Easing.InCubic
|
easing.type: Easing.InCubic
|
||||||
alwaysRunToEnd: true
|
properties: "width"
|
||||||
|
target: blackBar
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HoverHandler {
|
HoverHandler {
|
||||||
id: hover
|
id: hover
|
||||||
|
|
||||||
enabled: c.ToolTip.text
|
enabled: c.ToolTip.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,60 +14,54 @@ import im.nheko 1.0
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: inputBar
|
id: inputBar
|
||||||
|
|
||||||
|
property bool showAllButtons: width > 450 || (messageInput.length == 0 && !messageInput.inputMethodComposing)
|
||||||
readonly property string text: messageInput.text
|
readonly property string text: messageInput.text
|
||||||
|
|
||||||
color: palette.window
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: row.implicitHeight
|
|
||||||
Layout.minimumHeight: 40
|
Layout.minimumHeight: 40
|
||||||
property bool showAllButtons: width > 450 || (messageInput.length == 0 && !messageInput.inputMethodComposing)
|
Layout.preferredHeight: row.implicitHeight
|
||||||
|
color: palette.window
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: placeCallDialog
|
id: placeCallDialog
|
||||||
|
|
||||||
PlaceCall {
|
PlaceCall {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: screenShareDialog
|
id: screenShareDialog
|
||||||
|
|
||||||
ScreenShare {
|
ScreenShare {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: row
|
id: row
|
||||||
|
|
||||||
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
visible: CallManager.callsSupported && showAllButtons
|
|
||||||
opacity: (CallManager.haveCallInvite || CallManager.isOnCallOnOtherDevice) ? 0.3 : 1
|
|
||||||
Layout.alignment: Qt.AlignBottom
|
Layout.alignment: Qt.AlignBottom
|
||||||
hoverEnabled: true
|
|
||||||
width: 22
|
|
||||||
height: 22
|
|
||||||
image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.svg" : ":/icons/icons/ui/place-call.svg"
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : (CallManager.isOnCallOnOtherDevice ? qsTr("Already on a call") : qsTr("Place a call"))
|
|
||||||
Layout.margins: 8
|
Layout.margins: 8
|
||||||
|
ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : (CallManager.isOnCallOnOtherDevice ? qsTr("Already on a call") : qsTr("Place a call"))
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
height: 22
|
||||||
|
hoverEnabled: true
|
||||||
|
image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.svg" : ":/icons/icons/ui/place-call.svg"
|
||||||
|
opacity: (CallManager.haveCallInvite || CallManager.isOnCallOnOtherDevice) ? 0.3 : 1
|
||||||
|
visible: CallManager.callsSupported && showAllButtons
|
||||||
|
width: 22
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (room) {
|
if (room) {
|
||||||
if (CallManager.haveCallInvite) {
|
if (CallManager.haveCallInvite) {
|
||||||
return ;
|
return;
|
||||||
} else if (CallManager.isOnCall) {
|
} else if (CallManager.isOnCall) {
|
||||||
CallManager.hangUp();
|
CallManager.hangUp();
|
||||||
}
|
} else if (CallManager.isOnCallOnOtherDevice) {
|
||||||
else if(CallManager.isOnCallOnOtherDevice) {
|
|
||||||
return;
|
return;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
var dialog = placeCallDialog.createObject(timelineRoot);
|
var dialog = placeCallDialog.createObject(timelineRoot);
|
||||||
dialog.open();
|
dialog.open();
|
||||||
timelineRoot.destroyOnClose(dialog);
|
timelineRoot.destroyOnClose(dialog);
|
||||||
|
@ -75,18 +69,18 @@ Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
visible: showAllButtons
|
|
||||||
Layout.alignment: Qt.AlignBottom
|
Layout.alignment: Qt.AlignBottom
|
||||||
hoverEnabled: true
|
|
||||||
width: 22
|
|
||||||
height: 22
|
|
||||||
image: ":/icons/icons/ui/attach.svg"
|
|
||||||
Layout.margins: 8
|
Layout.margins: 8
|
||||||
onClicked: room.input.openFileSelection()
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.text: qsTr("Send a file")
|
ToolTip.text: qsTr("Send a file")
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
height: 22
|
||||||
|
hoverEnabled: true
|
||||||
|
image: ":/icons/icons/ui/attach.svg"
|
||||||
|
visible: showAllButtons
|
||||||
|
width: 22
|
||||||
|
|
||||||
|
onClicked: room.input.openFileSelection()
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
@ -98,112 +92,67 @@ Rectangle {
|
||||||
height: parent.height / 2
|
height: parent.height / 2
|
||||||
running: parent.visible
|
running: parent.visible
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
id: textInput
|
id: textInput
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
Layout.maximumHeight: Window.height / 4
|
Layout.maximumHeight: Window.height / 4
|
||||||
Layout.minimumHeight: fontMetrics.lineSpacing
|
Layout.minimumHeight: fontMetrics.lineSpacing
|
||||||
Layout.preferredHeight: contentHeight
|
Layout.preferredHeight: contentHeight
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||||
|
|
||||||
contentWidth: availableWidth
|
contentWidth: availableWidth
|
||||||
|
|
||||||
TextArea {
|
TextArea {
|
||||||
id: messageInput
|
id: messageInput
|
||||||
|
|
||||||
property int completerTriggeredAt: 0
|
property int completerTriggeredAt: 0
|
||||||
|
property string lastChar
|
||||||
|
|
||||||
function insertCompletion(completion) {
|
function insertCompletion(completion) {
|
||||||
messageInput.remove(completerTriggeredAt, cursorPosition);
|
messageInput.remove(completerTriggeredAt, cursorPosition);
|
||||||
messageInput.insert(cursorPosition, completion);
|
messageInput.insert(cursorPosition, completion);
|
||||||
}
|
}
|
||||||
|
|
||||||
function openCompleter(pos, type) {
|
function openCompleter(pos, type) {
|
||||||
if (popup.opened) return;
|
if (popup.opened)
|
||||||
|
return;
|
||||||
completerTriggeredAt = pos;
|
completerTriggeredAt = pos;
|
||||||
completer.completerName = type;
|
completer.completerName = type;
|
||||||
popup.open();
|
popup.open();
|
||||||
completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition)+messageInput.preeditText);
|
completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition) + messageInput.preeditText);
|
||||||
}
|
}
|
||||||
|
|
||||||
function positionCursorAtEnd() {
|
function positionCursorAtEnd() {
|
||||||
cursorPosition = messageInput.length;
|
cursorPosition = messageInput.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
function positionCursorAtStart() {
|
function positionCursorAtStart() {
|
||||||
cursorPosition = 0;
|
cursorPosition = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectByMouse: true
|
background: null
|
||||||
|
bottomPadding: 8
|
||||||
|
color: palette.text
|
||||||
|
focus: true
|
||||||
|
leftPadding: inputBar.showAllButtons ? 0 : 8
|
||||||
|
padding: 0
|
||||||
placeholderText: qsTr("Write a message...")
|
placeholderText: qsTr("Write a message...")
|
||||||
placeholderTextColor: palette.buttonText
|
placeholderTextColor: palette.buttonText
|
||||||
color: palette.text
|
selectByMouse: true
|
||||||
width: textInput.width
|
|
||||||
verticalAlignment: TextEdit.AlignVCenter
|
|
||||||
wrapMode: TextEdit.Wrap
|
|
||||||
padding: 0
|
|
||||||
topPadding: 8
|
topPadding: 8
|
||||||
bottomPadding: 8
|
verticalAlignment: TextEdit.AlignVCenter
|
||||||
leftPadding: inputBar.showAllButtons? 0 : 8
|
width: textInput.width
|
||||||
focus: true
|
wrapMode: TextEdit.Wrap
|
||||||
property string lastChar
|
|
||||||
onTextChanged: {
|
|
||||||
if (room)
|
|
||||||
room.input.updateState(selectionStart, selectionEnd, cursorPosition, text);
|
|
||||||
forceActiveFocus();
|
|
||||||
if (cursorPosition > 0)
|
|
||||||
lastChar = text.charAt(cursorPosition-1)
|
|
||||||
else
|
|
||||||
lastChar = ''
|
|
||||||
if (lastChar == '@') {
|
|
||||||
messageInput.openCompleter(selectionStart-1, "user");
|
|
||||||
} else if (lastChar == ':') {
|
|
||||||
messageInput.openCompleter(selectionStart-1, "emoji");
|
|
||||||
} else if (lastChar == '#') {
|
|
||||||
messageInput.openCompleter(selectionStart-1, "roomAliases");
|
|
||||||
} else if (lastChar == "/" && cursorPosition == 1) {
|
|
||||||
messageInput.openCompleter(selectionStart-1, "command");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onCursorPositionChanged: {
|
|
||||||
if (!room)
|
|
||||||
return ;
|
|
||||||
|
|
||||||
room.input.updateState(selectionStart, selectionEnd, cursorPosition, text);
|
Keys.onPressed: event => {
|
||||||
if (popup.opened && cursorPosition <= completerTriggeredAt)
|
|
||||||
popup.close();
|
|
||||||
|
|
||||||
if (popup.opened)
|
|
||||||
completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition)+messageInput.preeditText);
|
|
||||||
|
|
||||||
}
|
|
||||||
onPreeditTextChanged: {
|
|
||||||
if (popup.opened)
|
|
||||||
completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition)+messageInput.preeditText);
|
|
||||||
}
|
|
||||||
onSelectionStartChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
|
|
||||||
onSelectionEndChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
|
|
||||||
// Ensure that we get escape key press events first.
|
|
||||||
Keys.onShortcutOverride: (event) => event.accepted = (popup.opened && (event.key === Qt.Key_Escape || event.key === Qt.Key_Tab || event.key === Qt.Key_Enter || event.key === Qt.Key_Space))
|
|
||||||
Keys.onPressed: (event) => {
|
|
||||||
if (event.matches(StandardKey.Paste)) {
|
if (event.matches(StandardKey.Paste)) {
|
||||||
event.accepted = room.input.tryPasteAttachment(false);
|
event.accepted = room.input.tryPasteAttachment(false);
|
||||||
} else if (event.key == Qt.Key_Space) {
|
} else if (event.key == Qt.Key_Space) {
|
||||||
// close popup if user enters space after colon
|
// close popup if user enters space after colon
|
||||||
if (cursorPosition == completerTriggeredAt + 1)
|
if (cursorPosition == completerTriggeredAt + 1)
|
||||||
popup.close();
|
popup.close();
|
||||||
|
|
||||||
if (popup.opened && completer.count <= 0)
|
if (popup.opened && completer.count <= 0)
|
||||||
popup.close();
|
popup.close();
|
||||||
|
|
||||||
} else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_U) {
|
} else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_U) {
|
||||||
messageInput.clear();
|
messageInput.clear();
|
||||||
} else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_P) {
|
} else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_P) {
|
||||||
|
@ -218,8 +167,8 @@ Rectangle {
|
||||||
completer.completerName = "";
|
completer.completerName = "";
|
||||||
popup.close();
|
popup.close();
|
||||||
} else if (event.matches(StandardKey.InsertLineSeparator)) {
|
} else if (event.matches(StandardKey.InsertLineSeparator)) {
|
||||||
if (popup.opened) popup.close();
|
if (popup.opened)
|
||||||
|
popup.close();
|
||||||
if (Settings.invertEnterKey && (!Qt.inputMethod.visible || Qt.platform.os === "windows")) {
|
if (Settings.invertEnterKey && (!Qt.inputMethod.visible || Qt.platform.os === "windows")) {
|
||||||
room.input.send();
|
room.input.send();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
|
@ -253,16 +202,16 @@ Rectangle {
|
||||||
console.log('"' + t + '"');
|
console.log('"' + t + '"');
|
||||||
if (t == '@') {
|
if (t == '@') {
|
||||||
messageInput.openCompleter(pos, "user");
|
messageInput.openCompleter(pos, "user");
|
||||||
return ;
|
return;
|
||||||
} else if (t == ' ' || t == '\t') {
|
} else if (t == ' ' || t == '\t') {
|
||||||
messageInput.openCompleter(pos + 1, "user");
|
messageInput.openCompleter(pos + 1, "user");
|
||||||
return ;
|
return;
|
||||||
} else if (t == ':') {
|
} else if (t == ':') {
|
||||||
messageInput.openCompleter(pos, "emoji");
|
messageInput.openCompleter(pos, "emoji");
|
||||||
return ;
|
return;
|
||||||
} else if (t == '~') {
|
} else if (t == '~') {
|
||||||
messageInput.openCompleter(pos, "customEmoji");
|
messageInput.openCompleter(pos, "customEmoji");
|
||||||
return ;
|
return;
|
||||||
}
|
}
|
||||||
pos = pos - 1;
|
pos = pos - 1;
|
||||||
}
|
}
|
||||||
|
@ -312,21 +261,53 @@ Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
background: null
|
// Ensure that we get escape key press events first.
|
||||||
|
Keys.onShortcutOverride: event => event.accepted = (popup.opened && (event.key === Qt.Key_Escape || event.key === Qt.Key_Tab || event.key === Qt.Key_Enter || event.key === Qt.Key_Space))
|
||||||
|
onCursorPositionChanged: {
|
||||||
|
if (!room)
|
||||||
|
return;
|
||||||
|
room.input.updateState(selectionStart, selectionEnd, cursorPosition, text);
|
||||||
|
if (popup.opened && cursorPosition <= completerTriggeredAt)
|
||||||
|
popup.close();
|
||||||
|
if (popup.opened)
|
||||||
|
completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition) + messageInput.preeditText);
|
||||||
|
}
|
||||||
|
onPreeditTextChanged: {
|
||||||
|
if (popup.opened)
|
||||||
|
completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition) + messageInput.preeditText);
|
||||||
|
}
|
||||||
|
onSelectionEndChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
|
||||||
|
onSelectionStartChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
|
||||||
|
onTextChanged: {
|
||||||
|
if (room)
|
||||||
|
room.input.updateState(selectionStart, selectionEnd, cursorPosition, text);
|
||||||
|
forceActiveFocus();
|
||||||
|
if (cursorPosition > 0)
|
||||||
|
lastChar = text.charAt(cursorPosition - 1);
|
||||||
|
else
|
||||||
|
lastChar = '';
|
||||||
|
if (lastChar == '@') {
|
||||||
|
messageInput.openCompleter(selectionStart - 1, "user");
|
||||||
|
} else if (lastChar == ':') {
|
||||||
|
messageInput.openCompleter(selectionStart - 1, "emoji");
|
||||||
|
} else if (lastChar == '#') {
|
||||||
|
messageInput.openCompleter(selectionStart - 1, "roomAliases");
|
||||||
|
} else if (lastChar == "/" && cursorPosition == 1) {
|
||||||
|
messageInput.openCompleter(selectionStart - 1, "command");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onRoomChanged() {
|
function onRoomChanged() {
|
||||||
messageInput.clear();
|
messageInput.clear();
|
||||||
if (room)
|
if (room)
|
||||||
messageInput.append(room.input.text);
|
messageInput.append(room.input.text);
|
||||||
|
|
||||||
completer.completerName = "";
|
completer.completerName = "";
|
||||||
messageInput.forceActiveFocus();
|
messageInput.forceActiveFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
target: timelineView
|
target: timelineView
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onCompletionClicked(completion) {
|
function onCompletionClicked(completion) {
|
||||||
messageInput.insertCompletion(completion);
|
messageInput.insertCompletion(completion);
|
||||||
|
@ -334,43 +315,39 @@ Rectangle {
|
||||||
|
|
||||||
target: completer
|
target: completer
|
||||||
}
|
}
|
||||||
|
|
||||||
Popup {
|
Popup {
|
||||||
id: popup
|
id: popup
|
||||||
|
|
||||||
|
background: null
|
||||||
|
padding: 0
|
||||||
x: messageInput.positionToRectangle(messageInput.completerTriggeredAt).x
|
x: messageInput.positionToRectangle(messageInput.completerTriggeredAt).x
|
||||||
y: messageInput.positionToRectangle(messageInput.completerTriggeredAt).y - height
|
y: messageInput.positionToRectangle(messageInput.completerTriggeredAt).y - height
|
||||||
|
|
||||||
background: null
|
enter: Transition {
|
||||||
padding: 0
|
NumberAnimation {
|
||||||
|
duration: 100
|
||||||
|
from: 0
|
||||||
|
property: "opacity"
|
||||||
|
to: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exit: Transition {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 100
|
||||||
|
from: 1
|
||||||
|
property: "opacity"
|
||||||
|
to: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Completer {
|
Completer {
|
||||||
anchors.fill: parent
|
|
||||||
id: completer
|
id: completer
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
rowMargin: 2
|
rowMargin: 2
|
||||||
rowSpacing: 0
|
rowSpacing: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
enter: Transition {
|
|
||||||
NumberAnimation {
|
|
||||||
property: "opacity"
|
|
||||||
from: 0
|
|
||||||
to: 1
|
|
||||||
duration: 100
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
exit: Transition {
|
|
||||||
NumberAnimation {
|
|
||||||
property: "opacity"
|
|
||||||
from: 1
|
|
||||||
to: 0
|
|
||||||
duration: 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onTextChanged(newText) {
|
function onTextChanged(newText) {
|
||||||
messageInput.text = newText;
|
messageInput.text = newText;
|
||||||
|
@ -380,16 +357,13 @@ Rectangle {
|
||||||
ignoreUnknownSignals: true
|
ignoreUnknownSignals: true
|
||||||
target: room ? room.input : null
|
target: room ? room.input : null
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onReplyChanged() {
|
|
||||||
messageInput.forceActiveFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onEditChanged() {
|
function onEditChanged() {
|
||||||
messageInput.forceActiveFocus();
|
messageInput.forceActiveFocus();
|
||||||
}
|
}
|
||||||
|
function onReplyChanged() {
|
||||||
|
messageInput.forceActiveFocus();
|
||||||
|
}
|
||||||
function onThreadChanged() {
|
function onThreadChanged() {
|
||||||
messageInput.forceActiveFocus();
|
messageInput.forceActiveFocus();
|
||||||
}
|
}
|
||||||
|
@ -397,7 +371,6 @@ Rectangle {
|
||||||
ignoreUnknownSignals: true
|
ignoreUnknownSignals: true
|
||||||
target: room
|
target: room
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onFocusInput() {
|
function onFocusInput() {
|
||||||
messageInput.forceActiveFocus();
|
messageInput.forceActiveFocus();
|
||||||
|
@ -405,59 +378,56 @@ Rectangle {
|
||||||
|
|
||||||
target: TimelineManager
|
target: TimelineManager
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
acceptedButtons: Qt.MiddleButton
|
||||||
// workaround for wrong cursor shape on some platforms
|
// workaround for wrong cursor shape on some platforms
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.MiddleButton
|
|
||||||
cursorShape: Qt.IBeamCursor
|
cursorShape: Qt.IBeamCursor
|
||||||
onPressed: (mouse) => mouse.accepted = room.input.tryPasteAttachment(true)
|
|
||||||
|
onPressed: mouse => mouse.accepted = room.input.tryPasteAttachment(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
id: stickerButton
|
id: stickerButton
|
||||||
visible: showAllButtons
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||||
Layout.margins: 8
|
Layout.margins: 8
|
||||||
hoverEnabled: true
|
|
||||||
width: 22
|
|
||||||
height: 22
|
|
||||||
image: ":/icons/icons/ui/sticky-note-solid.svg"
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.text: qsTr("Stickers")
|
ToolTip.text: qsTr("Stickers")
|
||||||
onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId, function(row) {
|
ToolTip.visible: hovered
|
||||||
room.input.sticker(row);
|
height: 22
|
||||||
TimelineManager.focusMessageInput();
|
hoverEnabled: true
|
||||||
})
|
image: ":/icons/icons/ui/sticky-note-solid.svg"
|
||||||
|
visible: showAllButtons
|
||||||
|
width: 22
|
||||||
|
|
||||||
|
onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId, function (row) {
|
||||||
|
room.input.sticker(row);
|
||||||
|
TimelineManager.focusMessageInput();
|
||||||
|
})
|
||||||
|
|
||||||
StickerPicker {
|
StickerPicker {
|
||||||
id: stickerPopup
|
id: stickerPopup
|
||||||
|
|
||||||
emoji: false
|
emoji: false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
id: emojiButton
|
id: emojiButton
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||||
Layout.margins: 8
|
Layout.margins: 8
|
||||||
hoverEnabled: true
|
|
||||||
width: 22
|
|
||||||
height: 22
|
|
||||||
image: ":/icons/icons/ui/smile.svg"
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.text: qsTr("Emoji")
|
ToolTip.text: qsTr("Emoji")
|
||||||
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, room.roomId, function(plaintext, markdown) {
|
ToolTip.visible: hovered
|
||||||
messageInput.insert(messageInput.cursorPosition, markdown);
|
height: 22
|
||||||
TimelineManager.focusMessageInput();
|
hoverEnabled: true
|
||||||
})
|
image: ":/icons/icons/ui/smile.svg"
|
||||||
|
width: 22
|
||||||
|
|
||||||
|
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, room.roomId, function (plaintext, markdown) {
|
||||||
|
messageInput.insert(messageInput.cursorPosition, markdown);
|
||||||
|
TimelineManager.focusMessageInput();
|
||||||
|
})
|
||||||
|
|
||||||
StickerPicker {
|
StickerPicker {
|
||||||
id: emojiPopup
|
id: emojiPopup
|
||||||
|
@ -465,28 +435,25 @@ Rectangle {
|
||||||
emoji: true
|
emoji: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||||
Layout.margins: 8
|
Layout.margins: 8
|
||||||
hoverEnabled: true
|
|
||||||
width: 22
|
|
||||||
height: 22
|
|
||||||
image: ":/icons/icons/ui/send.svg"
|
|
||||||
Layout.rightMargin: 8
|
Layout.rightMargin: 8
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.text: qsTr("Send")
|
ToolTip.text: qsTr("Send")
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
height: 22
|
||||||
|
hoverEnabled: true
|
||||||
|
image: ":/icons/icons/ui/send.svg"
|
||||||
|
width: 22
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
room.input.send();
|
room.input.send();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
visible: room ? (!room.permissions.canSend(MtxEvent.TextMessage)) : false
|
|
||||||
text: qsTr("You don't have permission to send messages in this room")
|
text: qsTr("You don't have permission to send messages in this room")
|
||||||
|
visible: room ? (!room.permissions.canSend(MtxEvent.TextMessage)) : false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,37 +10,35 @@ import im.nheko 1.0
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: warningRoot
|
id: warningRoot
|
||||||
|
|
||||||
required property string text
|
|
||||||
property color bubbleColor: Nheko.theme.error
|
property color bubbleColor: Nheko.theme.error
|
||||||
|
required property string text
|
||||||
|
|
||||||
implicitHeight: visible ? warningDisplay.implicitHeight + 4 * Nheko.paddingSmall : 0
|
|
||||||
height: implicitHeight
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
color: palette.window // required to hide the timeline behind this warning
|
color: palette.window // required to hide the timeline behind this warning
|
||||||
|
height: implicitHeight
|
||||||
|
implicitHeight: visible ? warningDisplay.implicitHeight + 4 * Nheko.paddingSmall : 0
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: warningRect
|
id: warningRect
|
||||||
|
|
||||||
visible: warningRoot.visible
|
|
||||||
// TODO: Qt.alpha() would make more sense but it wasn't working...
|
|
||||||
color: Qt.rgba(bubbleColor.r, bubbleColor.g, bubbleColor.b, 0.3)
|
|
||||||
border.width: 1
|
|
||||||
border.color: bubbleColor
|
|
||||||
radius: 3
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: visible ? Nheko.paddingSmall : 0
|
anchors.margins: visible ? Nheko.paddingSmall : 0
|
||||||
|
border.color: bubbleColor
|
||||||
|
border.width: 1
|
||||||
|
// TODO: Qt.alpha() would make more sense but it wasn't working...
|
||||||
|
color: Qt.rgba(bubbleColor.r, bubbleColor.g, bubbleColor.b, 0.3)
|
||||||
|
radius: 3
|
||||||
|
visible: warningRoot.visible
|
||||||
z: 3
|
z: 3
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: warningDisplay
|
id: warningDisplay
|
||||||
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.margins: Nheko.paddingSmall
|
anchors.margins: Nheko.paddingSmall
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: warningRoot.text
|
text: warningRoot.text
|
||||||
textFormat: Text.PlainText
|
textFormat: Text.PlainText
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,9 +11,8 @@ Item {
|
||||||
id: privacyScreen
|
id: privacyScreen
|
||||||
|
|
||||||
readonly property bool active: Settings.privacyScreen && screenSaver.state === "Visible"
|
readonly property bool active: Settings.privacyScreen && screenSaver.state === "Visible"
|
||||||
property var timelineRoot
|
|
||||||
property int screenTimeout
|
property int screenTimeout
|
||||||
|
property var timelineRoot
|
||||||
required property var windowTarget
|
required property var windowTarget
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
|
@ -24,29 +23,28 @@ Item {
|
||||||
} else {
|
} else {
|
||||||
if (timelineRoot.visible)
|
if (timelineRoot.visible)
|
||||||
screenSaverTimer.start();
|
screenSaverTimer.start();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
target: windowTarget
|
target: windowTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: screenSaverTimer
|
id: screenSaverTimer
|
||||||
|
|
||||||
interval: screenTimeout * 1000
|
interval: screenTimeout * 1000
|
||||||
running: !windowTarget.active
|
running: !windowTarget.active
|
||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
screenSaver.state = "Visible";
|
screenSaver.state = "Visible";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: screenSaver
|
id: screenSaver
|
||||||
|
|
||||||
state: "Invisible"
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
state: "Invisible"
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
states: [
|
states: [
|
||||||
State {
|
State {
|
||||||
name: "Visible"
|
name: "Visible"
|
||||||
|
@ -55,20 +53,18 @@ Item {
|
||||||
target: screenSaver
|
target: screenSaver
|
||||||
visible: true
|
visible: true
|
||||||
}
|
}
|
||||||
|
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: screenSaver
|
|
||||||
opacity: 1
|
opacity: 1
|
||||||
|
target: screenSaver
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
State {
|
State {
|
||||||
name: "Invisible"
|
name: "Invisible"
|
||||||
|
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: screenSaver
|
|
||||||
opacity: 0
|
opacity: 0
|
||||||
|
target: screenSaver
|
||||||
}
|
}
|
||||||
|
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: screenSaver
|
target: screenSaver
|
||||||
visible: false
|
visible: false
|
||||||
|
@ -78,39 +74,33 @@ Item {
|
||||||
transitions: [
|
transitions: [
|
||||||
Transition {
|
Transition {
|
||||||
from: "Invisible"
|
from: "Invisible"
|
||||||
to: "Visible"
|
|
||||||
reversible: true
|
reversible: true
|
||||||
|
to: "Visible"
|
||||||
|
|
||||||
SequentialAnimation {
|
SequentialAnimation {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: screenSaver
|
|
||||||
property: "visible"
|
|
||||||
duration: 0
|
duration: 0
|
||||||
}
|
property: "visible"
|
||||||
|
|
||||||
NumberAnimation {
|
|
||||||
target: screenSaver
|
target: screenSaver
|
||||||
property: "opacity"
|
}
|
||||||
|
NumberAnimation {
|
||||||
duration: 300
|
duration: 300
|
||||||
easing.type: Easing.Linear
|
easing.type: Easing.Linear
|
||||||
|
property: "opacity"
|
||||||
|
target: screenSaver
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
MultiEffect {
|
MultiEffect {
|
||||||
id: blur
|
id: blur
|
||||||
|
|
||||||
blurEnabled: true
|
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: timelineRoot
|
|
||||||
blur: 1.0
|
blur: 1.0
|
||||||
|
blurEnabled: true
|
||||||
blurMax: 32
|
blurMax: 32
|
||||||
|
source: timelineRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,33 +11,36 @@ Popup {
|
||||||
id: quickSwitcher
|
id: quickSwitcher
|
||||||
|
|
||||||
property int textHeight: Math.round(Qt.application.font.pixelSize * 2.4)
|
property int textHeight: Math.round(Qt.application.font.pixelSize * 2.4)
|
||||||
|
property int textMargin: Nheko.paddingSmall
|
||||||
|
|
||||||
background: null
|
background: null
|
||||||
width: Math.min(Math.max(Math.round(parent.width / 2),450),parent.width) // limiting width to parent.width/2 can be a bit narrow
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||||
|
modal: true
|
||||||
|
parent: Overlay.overlay
|
||||||
|
width: Math.min(Math.max(Math.round(parent.width / 2), 450), parent.width) // limiting width to parent.width/2 can be a bit narrow
|
||||||
x: Math.round(parent.width / 2 - contentWidth / 2)
|
x: Math.round(parent.width / 2 - contentWidth / 2)
|
||||||
y: Math.round(parent.height / 4)
|
y: Math.round(parent.height / 4)
|
||||||
modal: true
|
|
||||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
Overlay.modal: Rectangle {
|
||||||
parent: Overlay.overlay
|
color: "#aa1E1E1E"
|
||||||
|
}
|
||||||
|
|
||||||
|
onClosed: TimelineManager.focusMessageInput()
|
||||||
onOpened: {
|
onOpened: {
|
||||||
roomTextInput.forceActiveFocus();
|
roomTextInput.forceActiveFocus();
|
||||||
}
|
}
|
||||||
onClosed: TimelineManager.focusMessageInput()
|
|
||||||
property int textMargin: Nheko.paddingSmall
|
|
||||||
|
|
||||||
Column{
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 1
|
spacing: 1
|
||||||
|
|
||||||
MatrixTextField {
|
MatrixTextField {
|
||||||
id: roomTextInput
|
id: roomTextInput
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
|
|
||||||
color: palette.text
|
color: palette.text
|
||||||
onTextEdited: {
|
font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
|
||||||
completerPopup.completer.searchString = text;
|
width: parent.width
|
||||||
}
|
|
||||||
Keys.onPressed: {
|
Keys.onPressed: {
|
||||||
if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
|
if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
|
@ -45,49 +48,43 @@ Popup {
|
||||||
} else if (event.key == Qt.Key_Down || event.key == Qt.Key_Tab) {
|
} else if (event.key == Qt.Key_Down || event.key == Qt.Key_Tab) {
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
if (event.key == Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))
|
if (event.key == Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))
|
||||||
completerPopup.up();
|
completerPopup.up();
|
||||||
else
|
else
|
||||||
completerPopup.down();
|
completerPopup.down();
|
||||||
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
|
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
|
||||||
completerPopup.finishCompletion();
|
completerPopup.finishCompletion();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onTextEdited: {
|
||||||
|
completerPopup.completer.searchString = text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Completer {
|
Completer {
|
||||||
id: completerPopup
|
id: completerPopup
|
||||||
|
|
||||||
visible: roomTextInput.text.length > 0
|
|
||||||
width: parent.width
|
|
||||||
completerName: "room"
|
|
||||||
bottomToTop: false
|
|
||||||
fullWidth: true
|
|
||||||
avatarHeight: quickSwitcher.textHeight
|
avatarHeight: quickSwitcher.textHeight
|
||||||
avatarWidth: quickSwitcher.textHeight
|
avatarWidth: quickSwitcher.textHeight
|
||||||
|
bottomToTop: false
|
||||||
centerRowContent: false
|
centerRowContent: false
|
||||||
|
completerName: "room"
|
||||||
|
fullWidth: true
|
||||||
rowMargin: Math.round(quickSwitcher.textMargin / 2)
|
rowMargin: Math.round(quickSwitcher.textMargin / 2)
|
||||||
rowSpacing: quickSwitcher.textMargin
|
rowSpacing: quickSwitcher.textMargin
|
||||||
|
visible: roomTextInput.text.length > 0
|
||||||
|
width: parent.width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onCompletionSelected(id) {
|
function onCompletionSelected(id) {
|
||||||
Rooms.setCurrentRoom(id);
|
Rooms.setCurrentRoom(id);
|
||||||
quickSwitcher.close();
|
quickSwitcher.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCountChanged() {
|
function onCountChanged() {
|
||||||
if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count))
|
if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count))
|
||||||
completerPopup.currentIndex = 0;
|
completerPopup.currentIndex = 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
target: completerPopup
|
target: completerPopup
|
||||||
}
|
}
|
||||||
|
|
||||||
Overlay.modal: Rectangle {
|
|
||||||
color: "#aa1E1E1E"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,11 @@ import im.nheko 1.0
|
||||||
Flow {
|
Flow {
|
||||||
id: reactionFlow
|
id: reactionFlow
|
||||||
|
|
||||||
|
property string eventId
|
||||||
|
|
||||||
// lower-contrast colors to avoid distracting from text & to enhance hover effect
|
// lower-contrast colors to avoid distracting from text & to enhance hover effect
|
||||||
property color gentleHighlight: Qt.hsla(palette.highlight.hslHue, palette.highlight.hslSaturation, palette.highlight.hslLightness, 0.8)
|
property color gentleHighlight: Qt.hsla(palette.highlight.hslHue, palette.highlight.hslSaturation, palette.highlight.hslLightness, 0.8)
|
||||||
property color gentleText: Qt.hsla(palette.text.hslHue, palette.text.hslSaturation, palette.text.hslLightness, 0.6)
|
property color gentleText: Qt.hsla(palette.text.hslHue, palette.text.hslSaturation, palette.text.hslLightness, 0.6)
|
||||||
property string eventId
|
|
||||||
property alias reactions: repeater.model
|
property alias reactions: repeater.model
|
||||||
|
|
||||||
spacing: 4
|
spacing: 4
|
||||||
|
@ -25,40 +26,39 @@ Flow {
|
||||||
delegate: AbstractButton {
|
delegate: AbstractButton {
|
||||||
id: reaction
|
id: reaction
|
||||||
|
|
||||||
hoverEnabled: true
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: Nheko.tooltipDelay
|
ToolTip.delay: Nheko.tooltipDelay
|
||||||
onClicked: {
|
ToolTip.visible: hovered
|
||||||
console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + ". selfReactedEvent: " + modelData.selfReactedEvent);
|
hoverEnabled: true
|
||||||
room.input.reaction(reactionFlow.eventId, modelData.key);
|
leftPadding: textMetrics.height / 2
|
||||||
}
|
rightPadding: textMetrics.height / 2
|
||||||
Component.onCompleted: {
|
|
||||||
ToolTip.text = Qt.binding(function() {
|
|
||||||
if (textMetrics.elidedText === textMetrics.text) {
|
|
||||||
return modelData.users;
|
|
||||||
}
|
|
||||||
return modelData.displayKey + "\n" + modelData.users;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
leftPadding: textMetrics.height / 2
|
|
||||||
rightPadding: textMetrics.height / 2
|
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
border.color: reaction.hovered ? palette.text : gentleText
|
||||||
|
border.width: 1
|
||||||
|
color: reaction.hovered ? palette.highlight : (modelData.selfReactedEvent !== '' ? gentleHighlight : palette.window)
|
||||||
|
implicitHeight: reaction.implicitHeight
|
||||||
|
implicitWidth: reaction.implicitWidth
|
||||||
|
radius: reaction.height / 2
|
||||||
|
}
|
||||||
contentItem: Row {
|
contentItem: Row {
|
||||||
spacing: textMetrics.height / 4
|
spacing: textMetrics.height / 4
|
||||||
|
|
||||||
TextMetrics {
|
TextMetrics {
|
||||||
id: textMetrics
|
id: textMetrics
|
||||||
|
|
||||||
font.family: Settings.emojiFont
|
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
elideWidth: 150
|
elideWidth: 150
|
||||||
|
font.family: Settings.emojiFont
|
||||||
text: modelData.displayKey
|
text: modelData.displayKey
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: reactionText
|
id: reactionText
|
||||||
|
|
||||||
anchors.baseline: reactionCounter.baseline
|
anchors.baseline: reactionCounter.baseline
|
||||||
|
color: (reaction.hovered || modelData.selfReactedEvent !== '') ? palette.highlightedText : palette.text
|
||||||
|
font.family: Settings.emojiFont
|
||||||
|
maximumLineCount: 1
|
||||||
text: {
|
text: {
|
||||||
// When an emoji font is selected that doesn't have …, it is dropped from elidedText. So we add it back.
|
// When an emoji font is selected that doesn't have …, it is dropped from elidedText. So we add it back.
|
||||||
if (textMetrics.elidedText !== modelData.displayKey) {
|
if (textMetrics.elidedText !== modelData.displayKey) {
|
||||||
|
@ -68,51 +68,45 @@ Flow {
|
||||||
}
|
}
|
||||||
return textMetrics.elidedText;
|
return textMetrics.elidedText;
|
||||||
}
|
}
|
||||||
font.family: Settings.emojiFont
|
|
||||||
color: (reaction.hovered || modelData.selfReactedEvent !== '') ? palette.highlightedText: palette.text
|
|
||||||
maximumLineCount: 1
|
|
||||||
visible: !modelData.key.startsWith("mxc://")
|
visible: !modelData.key.startsWith("mxc://")
|
||||||
}
|
}
|
||||||
Image {
|
Image {
|
||||||
anchors.verticalCenter: divider.verticalCenter
|
anchors.verticalCenter: divider.verticalCenter
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
height: textMetrics.height
|
height: textMetrics.height
|
||||||
width: textMetrics.height
|
|
||||||
source: modelData.key.startsWith("mxc://") ? (modelData.key.replace("mxc://", "image://MxcImage/") + "?scale") : ""
|
source: modelData.key.startsWith("mxc://") ? (modelData.key.replace("mxc://", "image://MxcImage/") + "?scale") : ""
|
||||||
visible: modelData.key.startsWith("mxc://")
|
visible: modelData.key.startsWith("mxc://")
|
||||||
fillMode: Image.PreserveAspectFit
|
width: textMetrics.height
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: divider
|
id: divider
|
||||||
|
|
||||||
|
color: reaction.hovered ? palette.text : gentleText
|
||||||
height: Math.floor(reactionCounter.implicitHeight * 1.4)
|
height: Math.floor(reactionCounter.implicitHeight * 1.4)
|
||||||
width: 1
|
width: 1
|
||||||
color: reaction.hovered ? palette.text: gentleText
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: reactionCounter
|
id: reactionCounter
|
||||||
|
|
||||||
anchors.verticalCenter: divider.verticalCenter
|
anchors.verticalCenter: divider.verticalCenter
|
||||||
text: modelData.count
|
color: (reaction.hovered || modelData.selfReactedEvent !== '') ? palette.highlightedText : palette.windowText
|
||||||
font: reaction.font
|
font: reaction.font
|
||||||
color: (reaction.hovered || modelData.selfReactedEvent !== '') ? palette.highlightedText: palette.windowText
|
text: modelData.count
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
background: Rectangle {
|
Component.onCompleted: {
|
||||||
anchors.centerIn: parent
|
ToolTip.text = Qt.binding(function () {
|
||||||
implicitWidth: reaction.implicitWidth
|
if (textMetrics.elidedText === textMetrics.text) {
|
||||||
implicitHeight: reaction.implicitHeight
|
return modelData.users;
|
||||||
border.color: reaction.hovered ? palette.text: gentleText
|
}
|
||||||
color: reaction.hovered ? palette.highlight : (modelData.selfReactedEvent !== '' ? gentleHighlight : palette.window)
|
return modelData.displayKey + "\n" + modelData.users;
|
||||||
border.width: 1
|
});
|
||||||
radius: reaction.height / 2
|
}
|
||||||
|
onClicked: {
|
||||||
|
console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + ". selfReactedEvent: " + modelData.selfReactedEvent);
|
||||||
|
room.input.reaction(reactionFlow.eventId, modelData.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,91 +12,89 @@ Rectangle {
|
||||||
id: replyPopup
|
id: replyPopup
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
visible: room && (room.reply || room.edit || room.thread)
|
color: palette.window
|
||||||
// Height of child, plus margins, plus border
|
// Height of child, plus margins, plus border
|
||||||
implicitHeight: (room && room.reply ? replyPreview.height : Math.max(closeEditButton.height, closeThreadButton.height)) + Nheko.paddingSmall
|
implicitHeight: (room && room.reply ? replyPreview.height : Math.max(closeEditButton.height, closeThreadButton.height)) + Nheko.paddingSmall
|
||||||
color: palette.window
|
visible: room && (room.reply || room.edit || room.thread)
|
||||||
z: 3
|
z: 3
|
||||||
|
|
||||||
Reply {
|
Reply {
|
||||||
id: replyPreview
|
id: replyPreview
|
||||||
|
|
||||||
property var modelData: room ? room.getDump(room.reply, room.id) : {
|
property var modelData: room ? room.getDump(room.reply, room.id) : {}
|
||||||
}
|
|
||||||
|
|
||||||
visible: room && room.reply
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: replyPopup.width < 450? Nheko.paddingSmall : (CallManager.callsSupported? 2*(22+16) : 1*(22+16))
|
anchors.leftMargin: replyPopup.width < 450 ? Nheko.paddingSmall : (CallManager.callsSupported ? 2 * (22 + 16) : 1 * (22 + 16))
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: replyPopup.width < 450? 2*(22+16) : 3*(22+16)
|
anchors.rightMargin: replyPopup.width < 450 ? 2 * (22 + 16) : 3 * (22 + 16)
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.topMargin: Nheko.paddingSmall
|
anchors.topMargin: Nheko.paddingSmall
|
||||||
userColor: TimelineManager.userColor(modelData.userId, palette.window)
|
|
||||||
blurhash: modelData.blurhash ?? ""
|
blurhash: modelData.blurhash ?? ""
|
||||||
body: modelData.body ?? ""
|
body: modelData.body ?? ""
|
||||||
formattedBody: modelData.formattedBody ?? ""
|
encryptionError: modelData.encryptionError ?? 0
|
||||||
eventId: modelData.eventId ?? ""
|
eventId: modelData.eventId ?? ""
|
||||||
filename: modelData.filename ?? ""
|
filename: modelData.filename ?? ""
|
||||||
filesize: modelData.filesize ?? ""
|
filesize: modelData.filesize ?? ""
|
||||||
|
formattedBody: modelData.formattedBody ?? ""
|
||||||
|
isOnlyEmoji: modelData.isOnlyEmoji ?? false
|
||||||
|
originalWidth: modelData.originalWidth ?? 0
|
||||||
proportionalHeight: modelData.proportionalHeight ?? 1
|
proportionalHeight: modelData.proportionalHeight ?? 1
|
||||||
type: modelData.type ?? MtxEvent.UnknownMessage
|
type: modelData.type ?? MtxEvent.UnknownMessage
|
||||||
typeString: modelData.typeString ?? ""
|
typeString: modelData.typeString ?? ""
|
||||||
url: modelData.url ?? ""
|
url: modelData.url ?? ""
|
||||||
originalWidth: modelData.originalWidth ?? 0
|
userColor: TimelineManager.userColor(modelData.userId, palette.window)
|
||||||
isOnlyEmoji: modelData.isOnlyEmoji ?? false
|
|
||||||
userId: modelData.userId ?? ""
|
userId: modelData.userId ?? ""
|
||||||
userName: modelData.userName ?? ""
|
userName: modelData.userName ?? ""
|
||||||
encryptionError: modelData.encryptionError ?? 0
|
visible: room && room.reply
|
||||||
width: parent.width
|
width: parent.width
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
id: closeReplyButton
|
id: closeReplyButton
|
||||||
|
|
||||||
visible: room && room.reply
|
ToolTip.text: qsTr("Close")
|
||||||
|
ToolTip.visible: closeReplyButton.hovered
|
||||||
|
anchors.margins: Nheko.paddingSmall
|
||||||
anchors.right: replyPreview.right
|
anchors.right: replyPreview.right
|
||||||
anchors.top: replyPreview.top
|
anchors.top: replyPreview.top
|
||||||
anchors.margins: Nheko.paddingSmall
|
|
||||||
hoverEnabled: true
|
|
||||||
width: 16
|
|
||||||
height: 16
|
height: 16
|
||||||
|
hoverEnabled: true
|
||||||
image: ":/icons/icons/ui/dismiss.svg"
|
image: ":/icons/icons/ui/dismiss.svg"
|
||||||
ToolTip.visible: closeReplyButton.hovered
|
visible: room && room.reply
|
||||||
ToolTip.text: qsTr("Close")
|
width: 16
|
||||||
|
|
||||||
onClicked: room.reply = undefined
|
onClicked: room.reply = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
id: closeEditButton
|
id: closeEditButton
|
||||||
|
|
||||||
visible: room && room.edit
|
ToolTip.text: qsTr("Cancel Edit")
|
||||||
anchors.right: closeThreadButton.left
|
ToolTip.visible: closeEditButton.hovered
|
||||||
anchors.margins: 8
|
anchors.margins: 8
|
||||||
|
anchors.right: closeThreadButton.left
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
|
height: 22
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
image: ":/icons/icons/ui/dismiss_edit.svg"
|
image: ":/icons/icons/ui/dismiss_edit.svg"
|
||||||
|
visible: room && room.edit
|
||||||
width: 22
|
width: 22
|
||||||
height: 22
|
|
||||||
ToolTip.visible: closeEditButton.hovered
|
|
||||||
ToolTip.text: qsTr("Cancel Edit")
|
|
||||||
onClicked: room.edit = undefined
|
onClicked: room.edit = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
id: closeThreadButton
|
id: closeThreadButton
|
||||||
|
|
||||||
visible: room && room.thread
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.margins: 8
|
|
||||||
anchors.top: parent.top
|
|
||||||
hoverEnabled: true
|
|
||||||
buttonTextColor: room ? TimelineManager.userColor(room.thread, palette.base) : palette.buttonText
|
|
||||||
image: ":/icons/icons/ui/dismiss_thread.svg"
|
|
||||||
width: 22
|
|
||||||
height: 22
|
|
||||||
ToolTip.visible: closeThreadButton.hovered
|
|
||||||
ToolTip.text: qsTr("Cancel Thread")
|
ToolTip.text: qsTr("Cancel Thread")
|
||||||
|
ToolTip.visible: closeThreadButton.hovered
|
||||||
|
anchors.margins: 8
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
buttonTextColor: room ? TimelineManager.userColor(room.thread, palette.base) : palette.buttonText
|
||||||
|
height: 22
|
||||||
|
hoverEnabled: true
|
||||||
|
image: ":/icons/icons/ui/dismiss_thread.svg"
|
||||||
|
visible: room && room.thread
|
||||||
|
width: 22
|
||||||
|
|
||||||
onClicked: room.thread = undefined
|
onClicked: room.thread = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -20,19 +20,14 @@ import im.nheko.EmojiModel 1.0
|
||||||
Pane {
|
Pane {
|
||||||
id: timelineRoot
|
id: timelineRoot
|
||||||
|
|
||||||
background: null
|
function destroyOnClose(obj) {
|
||||||
padding: 0
|
if (obj.closing != undefined)
|
||||||
|
obj.closing.connect(() => obj.destroy(1000));
|
||||||
FontMetrics {
|
else if (obj.aboutToHide != undefined)
|
||||||
id: fontMetrics
|
obj.aboutToHide.connect(() => obj.destroy(1000));
|
||||||
}
|
}
|
||||||
|
function destroyOnClosed(obj) {
|
||||||
RoomDirectoryModel {
|
obj.aboutToHide.connect(() => obj.destroy(1000));
|
||||||
id: publicRooms
|
|
||||||
}
|
|
||||||
|
|
||||||
UserDirectoryModel {
|
|
||||||
id: userDirectory
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Timer {
|
//Timer {
|
||||||
|
@ -41,54 +36,49 @@ Pane {
|
||||||
// running: true
|
// running: true
|
||||||
// repeat: true
|
// repeat: true
|
||||||
//}
|
//}
|
||||||
|
|
||||||
function showAliasEditor(settings) {
|
function showAliasEditor(settings) {
|
||||||
var component = Qt.createComponent("qrc:/qml/dialogs/AliasEditor.qml")
|
var component = Qt.createComponent("qrc:/qml/dialogs/AliasEditor.qml");
|
||||||
if (component.status == Component.Ready) {
|
if (component.status == Component.Ready) {
|
||||||
var dialog = component.createObject(timelineRoot, {
|
var dialog = component.createObject(timelineRoot, {
|
||||||
"roomSettings": settings
|
"roomSettings": settings
|
||||||
});
|
});
|
||||||
dialog.show();
|
|
||||||
destroyOnClose(dialog);
|
|
||||||
} else {
|
|
||||||
console.error("Failed to create component: " + component.errorString());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function showPLEditor(settings) {
|
|
||||||
var component = Qt.createComponent("qrc:/qml/dialogs/PowerLevelEditor.qml")
|
|
||||||
if (component.status == Component.Ready) {
|
|
||||||
var dialog = component.createObject(timelineRoot, {
|
|
||||||
"roomSettings": settings
|
|
||||||
});
|
|
||||||
dialog.show();
|
dialog.show();
|
||||||
destroyOnClose(dialog);
|
destroyOnClose(dialog);
|
||||||
} else {
|
} else {
|
||||||
console.error("Failed to create component: " + component.errorString());
|
console.error("Failed to create component: " + component.errorString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSpacePLApplyPrompt(settings, editingModel) {
|
|
||||||
var component = Qt.createComponent("qrc:/qml/dialogs/PowerLevelSpacesApplyDialog.qml")
|
|
||||||
if (component.status == Component.Ready) {
|
|
||||||
var dialog = component.createObject(timelineRoot, {
|
|
||||||
"roomSettings": settings,
|
|
||||||
"editingModel": editingModel
|
|
||||||
});
|
|
||||||
dialog.show();
|
|
||||||
destroyOnClose(dialog);
|
|
||||||
} else {
|
|
||||||
console.error("Failed to create component: " + component.errorString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showAllowedRoomsEditor(settings) {
|
function showAllowedRoomsEditor(settings) {
|
||||||
var component = Qt.createComponent("qrc:/qml/dialogs/AllowedRoomsSettingsDialog.qml")
|
var component = Qt.createComponent("qrc:/qml/dialogs/AllowedRoomsSettingsDialog.qml");
|
||||||
if (component.status == Component.Ready) {
|
if (component.status == Component.Ready) {
|
||||||
var dialog = component.createObject(timelineRoot, {
|
var dialog = component.createObject(timelineRoot, {
|
||||||
"roomSettings": settings
|
"roomSettings": settings
|
||||||
});
|
});
|
||||||
|
dialog.show();
|
||||||
|
destroyOnClose(dialog);
|
||||||
|
} else {
|
||||||
|
console.error("Failed to create component: " + component.errorString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function showPLEditor(settings) {
|
||||||
|
var component = Qt.createComponent("qrc:/qml/dialogs/PowerLevelEditor.qml");
|
||||||
|
if (component.status == Component.Ready) {
|
||||||
|
var dialog = component.createObject(timelineRoot, {
|
||||||
|
"roomSettings": settings
|
||||||
|
});
|
||||||
|
dialog.show();
|
||||||
|
destroyOnClose(dialog);
|
||||||
|
} else {
|
||||||
|
console.error("Failed to create component: " + component.errorString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function showSpacePLApplyPrompt(settings, editingModel) {
|
||||||
|
var component = Qt.createComponent("qrc:/qml/dialogs/PowerLevelSpacesApplyDialog.qml");
|
||||||
|
if (component.status == Component.Ready) {
|
||||||
|
var dialog = component.createObject(timelineRoot, {
|
||||||
|
"roomSettings": settings,
|
||||||
|
"editingModel": editingModel
|
||||||
|
});
|
||||||
dialog.show();
|
dialog.show();
|
||||||
destroyOnClose(dialog);
|
destroyOnClose(dialog);
|
||||||
} else {
|
} else {
|
||||||
|
@ -96,23 +86,37 @@ Pane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
background: null
|
||||||
|
padding: 0
|
||||||
|
|
||||||
|
FontMetrics {
|
||||||
|
id: fontMetrics
|
||||||
|
|
||||||
|
}
|
||||||
|
RoomDirectoryModel {
|
||||||
|
id: publicRooms
|
||||||
|
|
||||||
|
}
|
||||||
|
UserDirectoryModel {
|
||||||
|
id: userDirectory
|
||||||
|
|
||||||
|
}
|
||||||
Component {
|
Component {
|
||||||
id: readReceiptsDialog
|
id: readReceiptsDialog
|
||||||
|
|
||||||
ReadReceipts {
|
ReadReceipts {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: StandardKey.Quit
|
sequence: StandardKey.Quit
|
||||||
|
|
||||||
onActivated: Qt.quit()
|
onActivated: Qt.quit()
|
||||||
}
|
}
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: "Ctrl+K"
|
sequence: "Ctrl+K"
|
||||||
|
|
||||||
onActivated: {
|
onActivated: {
|
||||||
var component = Qt.createComponent("qrc:/qml/QuickSwitcher.qml")
|
var component = Qt.createComponent("qrc:/qml/QuickSwitcher.qml");
|
||||||
if (component.status == Component.Ready) {
|
if (component.status == Component.Ready) {
|
||||||
var quickSwitch = component.createObject(timelineRoot);
|
var quickSwitch = component.createObject(timelineRoot);
|
||||||
quickSwitch.open();
|
quickSwitch.open();
|
||||||
|
@ -122,37 +126,25 @@ Pane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
// Add alternative shortcut, because sometimes Alt+A is stolen by the TextEdit
|
// Add alternative shortcut, because sometimes Alt+A is stolen by the TextEdit
|
||||||
sequences: ["Alt+A", "Ctrl+Shift+A"]
|
sequences: ["Alt+A", "Ctrl+Shift+A"]
|
||||||
|
|
||||||
onActivated: Rooms.nextRoomWithActivity()
|
onActivated: Rooms.nextRoomWithActivity()
|
||||||
}
|
}
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: "Ctrl+Down"
|
sequence: "Ctrl+Down"
|
||||||
|
|
||||||
onActivated: Rooms.nextRoom()
|
onActivated: Rooms.nextRoom()
|
||||||
}
|
}
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: "Ctrl+Up"
|
sequence: "Ctrl+Up"
|
||||||
|
|
||||||
onActivated: Rooms.previousRoom()
|
onActivated: Rooms.previousRoom()
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onOpenLogoutDialog() {
|
|
||||||
var component = Qt.createComponent("qrc:/qml/dialogs/LogoutDialog.qml")
|
|
||||||
if (component.status == Component.Ready) {
|
|
||||||
var dialog = component.createObject(timelineRoot);
|
|
||||||
dialog.open();
|
|
||||||
destroyOnClose(dialog);
|
|
||||||
} else {
|
|
||||||
console.error("Failed to create component: " + component.errorString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onOpenJoinRoomDialog() {
|
function onOpenJoinRoomDialog() {
|
||||||
var component = Qt.createComponent("qrc:/qml/dialogs/JoinRoomDialog.qml")
|
var component = Qt.createComponent("qrc:/qml/dialogs/JoinRoomDialog.qml");
|
||||||
if (component.status == Component.Ready) {
|
if (component.status == Component.Ready) {
|
||||||
var dialog = component.createObject(timelineRoot);
|
var dialog = component.createObject(timelineRoot);
|
||||||
dialog.show();
|
dialog.show();
|
||||||
|
@ -161,11 +153,22 @@ Pane {
|
||||||
console.error("Failed to create component: " + component.errorString());
|
console.error("Failed to create component: " + component.errorString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function onOpenLogoutDialog() {
|
||||||
function onShowRoomJoinPrompt(summary) {
|
var component = Qt.createComponent("qrc:/qml/dialogs/LogoutDialog.qml");
|
||||||
var component = Qt.createComponent("qrc:/qml/dialogs/ConfirmJoinRoomDialog.qml")
|
|
||||||
if (component.status == Component.Ready) {
|
if (component.status == Component.Ready) {
|
||||||
var dialog = component.createObject(timelineRoot, {"summary": summary});
|
var dialog = component.createObject(timelineRoot);
|
||||||
|
dialog.open();
|
||||||
|
destroyOnClose(dialog);
|
||||||
|
} else {
|
||||||
|
console.error("Failed to create component: " + component.errorString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onShowRoomJoinPrompt(summary) {
|
||||||
|
var component = Qt.createComponent("qrc:/qml/dialogs/ConfirmJoinRoomDialog.qml");
|
||||||
|
if (component.status == Component.Ready) {
|
||||||
|
var dialog = component.createObject(timelineRoot, {
|
||||||
|
"summary": summary
|
||||||
|
});
|
||||||
dialog.show();
|
dialog.show();
|
||||||
destroyOnClose(dialog);
|
destroyOnClose(dialog);
|
||||||
} else {
|
} else {
|
||||||
|
@ -175,12 +178,13 @@ Pane {
|
||||||
|
|
||||||
target: Nheko
|
target: Nheko
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onNewDeviceVerificationRequest(flow) {
|
function onNewDeviceVerificationRequest(flow) {
|
||||||
var component = Qt.createComponent("qrc:/qml/device-verification/DeviceVerification.qml")
|
var component = Qt.createComponent("qrc:/qml/device-verification/DeviceVerification.qml");
|
||||||
if (component.status == Component.Ready) {
|
if (component.status == Component.Ready) {
|
||||||
var dialog = component.createObject(timelineRoot, {"flow": flow});
|
var dialog = component.createObject(timelineRoot, {
|
||||||
|
"flow": flow
|
||||||
|
});
|
||||||
dialog.show();
|
dialog.show();
|
||||||
destroyOnClose(dialog);
|
destroyOnClose(dialog);
|
||||||
} else {
|
} else {
|
||||||
|
@ -190,101 +194,71 @@ Pane {
|
||||||
|
|
||||||
target: VerificationManager
|
target: VerificationManager
|
||||||
}
|
}
|
||||||
|
|
||||||
function destroyOnClose(obj) {
|
|
||||||
if (obj.closing != undefined) obj.closing.connect(() => obj.destroy(1000));
|
|
||||||
else if (obj.aboutToHide != undefined) obj.aboutToHide.connect(() => obj.destroy(1000));
|
|
||||||
}
|
|
||||||
|
|
||||||
function destroyOnClosed(obj) {
|
|
||||||
obj.aboutToHide.connect(() => obj.destroy(1000));
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onOpenProfile(profile) {
|
|
||||||
var component = Qt.createComponent("qrc:/qml/dialogs/UserProfile.qml")
|
|
||||||
if (component.status == Component.Ready) {
|
|
||||||
var userProfile = component.createObject(timelineRoot, {"profile": profile});
|
|
||||||
userProfile.show();
|
|
||||||
destroyOnClose(userProfile);
|
|
||||||
} else {
|
|
||||||
console.error("Failed to create component: " + component.errorString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onShowImagePackSettings(room, packlist) {
|
|
||||||
var component = Qt.createComponent("qrc:/qml/dialogs/ImagePackSettingsDialog.qml")
|
|
||||||
|
|
||||||
if (component.status == Component.Ready) {
|
|
||||||
var packSet = component.createObject(timelineRoot, {
|
|
||||||
"room": room,
|
|
||||||
"packlist": packlist
|
|
||||||
});
|
|
||||||
packSet.show();
|
|
||||||
destroyOnClose(packSet);
|
|
||||||
} else {
|
|
||||||
console.error("Failed to create component: " + component.errorString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onOpenRoomMembersDialog(members, room) {
|
|
||||||
var component = Qt.createComponent("qrc:/qml/dialogs/RoomMembers.qml")
|
|
||||||
if (component.status == Component.Ready) {
|
|
||||||
var membersDialog = component.createObject(timelineRoot, {
|
|
||||||
"members": members,
|
|
||||||
"room": room
|
|
||||||
});
|
|
||||||
membersDialog.show();
|
|
||||||
destroyOnClose(membersDialog);
|
|
||||||
} else {
|
|
||||||
console.error("Failed to create component: " + component.errorString());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function onOpenRoomSettingsDialog(settings) {
|
|
||||||
var component = Qt.createComponent("qrc:/qml/dialogs/RoomSettings.qml")
|
|
||||||
if (component.status == Component.Ready) {
|
|
||||||
var roomSettings = component.createObject(timelineRoot, {
|
|
||||||
"roomSettings": settings
|
|
||||||
});
|
|
||||||
roomSettings.show();
|
|
||||||
destroyOnClose(roomSettings);
|
|
||||||
} else {
|
|
||||||
console.error("Failed to create component: " + component.errorString());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function onOpenInviteUsersDialog(invitees) {
|
function onOpenInviteUsersDialog(invitees) {
|
||||||
var component = Qt.createComponent("qrc:/qml/dialogs/InviteDialog.qml")
|
var component = Qt.createComponent("qrc:/qml/dialogs/InviteDialog.qml");
|
||||||
if (component.status == Component.Ready) {
|
if (component.status == Component.Ready) {
|
||||||
var dialog = component.createObject(timelineRoot, {
|
var dialog = component.createObject(timelineRoot, {
|
||||||
"invitees": invitees
|
"invitees": invitees
|
||||||
});
|
});
|
||||||
dialog.show();
|
dialog.show();
|
||||||
destroyOnClose(dialog);
|
destroyOnClose(dialog);
|
||||||
} else {
|
} else {
|
||||||
console.error("Failed to create component: " + component.errorString());
|
console.error("Failed to create component: " + component.errorString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onOpenLeaveRoomDialog(roomid, reason) {
|
function onOpenLeaveRoomDialog(roomid, reason) {
|
||||||
var component = Qt.createComponent("qrc:/qml/dialogs/LeaveRoomDialog.qml")
|
var component = Qt.createComponent("qrc:/qml/dialogs/LeaveRoomDialog.qml");
|
||||||
if (component.status == Component.Ready) {
|
if (component.status == Component.Ready) {
|
||||||
var dialog = component.createObject(timelineRoot, {
|
var dialog = component.createObject(timelineRoot, {
|
||||||
"roomId": roomid,
|
"roomId": roomid,
|
||||||
"reason": reason
|
"reason": reason
|
||||||
});
|
});
|
||||||
dialog.open();
|
dialog.open();
|
||||||
destroyOnClose(dialog);
|
destroyOnClose(dialog);
|
||||||
} else {
|
} else {
|
||||||
console.error("Failed to create component: " + component.errorString());
|
console.error("Failed to create component: " + component.errorString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function onOpenProfile(profile) {
|
||||||
|
var component = Qt.createComponent("qrc:/qml/dialogs/UserProfile.qml");
|
||||||
|
if (component.status == Component.Ready) {
|
||||||
|
var userProfile = component.createObject(timelineRoot, {
|
||||||
|
"profile": profile
|
||||||
|
});
|
||||||
|
userProfile.show();
|
||||||
|
destroyOnClose(userProfile);
|
||||||
|
} else {
|
||||||
|
console.error("Failed to create component: " + component.errorString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onOpenRoomMembersDialog(members, room) {
|
||||||
|
var component = Qt.createComponent("qrc:/qml/dialogs/RoomMembers.qml");
|
||||||
|
if (component.status == Component.Ready) {
|
||||||
|
var membersDialog = component.createObject(timelineRoot, {
|
||||||
|
"members": members,
|
||||||
|
"room": room
|
||||||
|
});
|
||||||
|
membersDialog.show();
|
||||||
|
destroyOnClose(membersDialog);
|
||||||
|
} else {
|
||||||
|
console.error("Failed to create component: " + component.errorString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onOpenRoomSettingsDialog(settings) {
|
||||||
|
var component = Qt.createComponent("qrc:/qml/dialogs/RoomSettings.qml");
|
||||||
|
if (component.status == Component.Ready) {
|
||||||
|
var roomSettings = component.createObject(timelineRoot, {
|
||||||
|
"roomSettings": settings
|
||||||
|
});
|
||||||
|
roomSettings.show();
|
||||||
|
destroyOnClose(roomSettings);
|
||||||
|
} else {
|
||||||
|
console.error("Failed to create component: " + component.errorString());
|
||||||
|
}
|
||||||
|
}
|
||||||
function onShowImageOverlay(room, eventId, url, originalWidth, proportionalHeight) {
|
function onShowImageOverlay(room, eventId, url, originalWidth, proportionalHeight) {
|
||||||
var component = Qt.createComponent("qrc:/qml/dialogs/ImageOverlay.qml")
|
var component = Qt.createComponent("qrc:/qml/dialogs/ImageOverlay.qml");
|
||||||
if (component.status == Component.Ready) {
|
if (component.status == Component.Ready) {
|
||||||
var dialog = component.createObject(timelineRoot, {
|
var dialog = component.createObject(timelineRoot, {
|
||||||
"room": room,
|
"room": room,
|
||||||
|
@ -292,22 +266,33 @@ Pane {
|
||||||
"url": url,
|
"url": url,
|
||||||
"originalWidth": originalWidth ?? 0,
|
"originalWidth": originalWidth ?? 0,
|
||||||
"proportionalHeight": proportionalHeight ?? 0
|
"proportionalHeight": proportionalHeight ?? 0
|
||||||
}
|
});
|
||||||
);
|
|
||||||
dialog.showFullScreen();
|
dialog.showFullScreen();
|
||||||
destroyOnClose(dialog);
|
destroyOnClose(dialog);
|
||||||
} else {
|
} else {
|
||||||
console.error("Failed to create component: " + component.errorString());
|
console.error("Failed to create component: " + component.errorString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function onShowImagePackSettings(room, packlist) {
|
||||||
|
var component = Qt.createComponent("qrc:/qml/dialogs/ImagePackSettingsDialog.qml");
|
||||||
|
if (component.status == Component.Ready) {
|
||||||
|
var packSet = component.createObject(timelineRoot, {
|
||||||
|
"room": room,
|
||||||
|
"packlist": packlist
|
||||||
|
});
|
||||||
|
packSet.show();
|
||||||
|
destroyOnClose(packSet);
|
||||||
|
} else {
|
||||||
|
console.error("Failed to create component: " + component.errorString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
target: TimelineManager
|
target: TimelineManager
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onNewInviteState() {
|
function onNewInviteState() {
|
||||||
if (CallManager.haveCallInvite && Settings.mobileMode) {
|
if (CallManager.haveCallInvite && Settings.mobileMode) {
|
||||||
var component = Qt.createComponent("qrc:/qml/voip/CallInvite.qml")
|
var component = Qt.createComponent("qrc:/qml/voip/CallInvite.qml");
|
||||||
if (component.status == Component.Ready) {
|
if (component.status == Component.Ready) {
|
||||||
var dialog = component.createObject(timelineRoot);
|
var dialog = component.createObject(timelineRoot);
|
||||||
dialog.open();
|
dialog.open();
|
||||||
|
@ -320,141 +305,97 @@ Pane {
|
||||||
|
|
||||||
target: CallManager
|
target: CallManager
|
||||||
}
|
}
|
||||||
|
|
||||||
SelfVerificationCheck {
|
SelfVerificationCheck {
|
||||||
}
|
}
|
||||||
|
|
||||||
InputDialog {
|
InputDialog {
|
||||||
id: uiaPassPrompt
|
id: uiaPassPrompt
|
||||||
|
|
||||||
echoMode: TextInput.Password
|
echoMode: TextInput.Password
|
||||||
title: UIA.title
|
|
||||||
prompt: qsTr("Please enter your login password to continue:")
|
prompt: qsTr("Please enter your login password to continue:")
|
||||||
onAccepted: (t) => {
|
title: UIA.title
|
||||||
|
|
||||||
|
onAccepted: t => {
|
||||||
return UIA.continuePassword(t);
|
return UIA.continuePassword(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InputDialog {
|
InputDialog {
|
||||||
id: uiaEmailPrompt
|
id: uiaEmailPrompt
|
||||||
|
|
||||||
title: UIA.title
|
|
||||||
prompt: qsTr("Please enter a valid email address to continue:")
|
prompt: qsTr("Please enter a valid email address to continue:")
|
||||||
onAccepted: (t) => {
|
title: UIA.title
|
||||||
|
|
||||||
|
onAccepted: t => {
|
||||||
return UIA.continueEmail(t);
|
return UIA.continueEmail(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PhoneNumberInputDialog {
|
PhoneNumberInputDialog {
|
||||||
id: uiaPhoneNumberPrompt
|
id: uiaPhoneNumberPrompt
|
||||||
|
|
||||||
title: UIA.title
|
|
||||||
prompt: qsTr("Please enter a valid phone number to continue:")
|
prompt: qsTr("Please enter a valid phone number to continue:")
|
||||||
|
title: UIA.title
|
||||||
|
|
||||||
onAccepted: (p, t) => {
|
onAccepted: (p, t) => {
|
||||||
return UIA.continuePhoneNumber(p, t);
|
return UIA.continuePhoneNumber(p, t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InputDialog {
|
InputDialog {
|
||||||
id: uiaTokenPrompt
|
id: uiaTokenPrompt
|
||||||
|
|
||||||
title: UIA.title
|
|
||||||
prompt: qsTr("Please enter the token which has been sent to you:")
|
prompt: qsTr("Please enter the token which has been sent to you:")
|
||||||
onAccepted: (t) => {
|
title: UIA.title
|
||||||
|
|
||||||
|
onAccepted: t => {
|
||||||
return UIA.submit3pidToken(t);
|
return UIA.submit3pidToken(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Platform.MessageDialog {
|
Platform.MessageDialog {
|
||||||
id: uiaErrorDialog
|
id: uiaErrorDialog
|
||||||
|
|
||||||
buttons: Platform.MessageDialog.Ok
|
buttons: Platform.MessageDialog.Ok
|
||||||
}
|
}
|
||||||
|
|
||||||
Platform.MessageDialog {
|
Platform.MessageDialog {
|
||||||
id: uiaConfirmationLinkDialog
|
id: uiaConfirmationLinkDialog
|
||||||
|
|
||||||
buttons: Platform.MessageDialog.Ok
|
buttons: Platform.MessageDialog.Ok
|
||||||
text: qsTr("Wait for the confirmation link to arrive, then continue.")
|
text: qsTr("Wait for the confirmation link to arrive, then continue.")
|
||||||
|
|
||||||
onAccepted: UIA.continue3pidReceived()
|
onAccepted: UIA.continue3pidReceived()
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onPassword() {
|
|
||||||
console.log("UIA: password needed");
|
|
||||||
uiaPassPrompt.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onEmail() {
|
|
||||||
uiaEmailPrompt.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPhoneNumber() {
|
|
||||||
uiaPhoneNumberPrompt.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPrompt3pidToken() {
|
|
||||||
uiaTokenPrompt.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onConfirm3pidToken() {
|
function onConfirm3pidToken() {
|
||||||
uiaConfirmationLinkDialog.open();
|
uiaConfirmationLinkDialog.open();
|
||||||
}
|
}
|
||||||
|
function onEmail() {
|
||||||
|
uiaEmailPrompt.show();
|
||||||
|
}
|
||||||
function onError(msg) {
|
function onError(msg) {
|
||||||
uiaErrorDialog.text = msg;
|
uiaErrorDialog.text = msg;
|
||||||
uiaErrorDialog.open();
|
uiaErrorDialog.open();
|
||||||
}
|
}
|
||||||
|
function onPassword() {
|
||||||
|
console.log("UIA: password needed");
|
||||||
|
uiaPassPrompt.show();
|
||||||
|
}
|
||||||
|
function onPhoneNumber() {
|
||||||
|
uiaPhoneNumberPrompt.show();
|
||||||
|
}
|
||||||
|
function onPrompt3pidToken() {
|
||||||
|
uiaTokenPrompt.show();
|
||||||
|
}
|
||||||
|
|
||||||
target: UIA
|
target: UIA
|
||||||
}
|
}
|
||||||
|
|
||||||
StackView {
|
StackView {
|
||||||
id: mainWindow
|
id: mainWindow
|
||||||
|
|
||||||
anchors.fill: parent
|
property Transition popEnterOrg
|
||||||
initialItem: welcomePage
|
property Transition popExitOrg
|
||||||
|
|
||||||
Transition {
|
|
||||||
id: reducedMotionTransitionExit
|
|
||||||
PropertyAnimation {
|
|
||||||
property: "opacity"
|
|
||||||
from: 1
|
|
||||||
to:0
|
|
||||||
duration: 200
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Transition {
|
|
||||||
id: reducedMotionTransitionEnter
|
|
||||||
SequentialAnimation {
|
|
||||||
PropertyAction { property: "opacity"; value: 0 }
|
|
||||||
PauseAnimation { duration: 200 }
|
|
||||||
PropertyAnimation {
|
|
||||||
property: "opacity"
|
|
||||||
from: 0
|
|
||||||
to:1
|
|
||||||
duration: 200
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for some reason direct bindings to a hidden StackView don't work, so manually store and restore here.
|
// for some reason direct bindings to a hidden StackView don't work, so manually store and restore here.
|
||||||
property Transition pushEnterOrg
|
property Transition pushEnterOrg
|
||||||
property Transition pushExitOrg
|
property Transition pushExitOrg
|
||||||
property Transition popEnterOrg
|
|
||||||
property Transition popExitOrg
|
|
||||||
property Transition replaceEnterOrg
|
property Transition replaceEnterOrg
|
||||||
property Transition replaceExitOrg
|
property Transition replaceExitOrg
|
||||||
Component.onCompleted: {
|
|
||||||
pushEnterOrg = pushEnter;
|
|
||||||
popEnterOrg = popEnter;
|
|
||||||
replaceEnterOrg = replaceEnter;
|
|
||||||
pushExitOrg = pushExit;
|
|
||||||
popExitOrg = popExit;
|
|
||||||
replaceExitOrg = replaceExit;
|
|
||||||
|
|
||||||
updateTrans()
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateTrans() {
|
function updateTrans() {
|
||||||
pushEnter = Settings.reducedMotion ? reducedMotionTransitionEnter : pushEnterOrg;
|
pushEnter = Settings.reducedMotion ? reducedMotionTransitionEnter : pushEnterOrg;
|
||||||
|
@ -465,65 +406,104 @@ Pane {
|
||||||
replaceExit = Settings.reducedMotion ? reducedMotionTransitionExit : replaceExitOrg;
|
replaceExit = Settings.reducedMotion ? reducedMotionTransitionExit : replaceExitOrg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
initialItem: welcomePage
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
pushEnterOrg = pushEnter;
|
||||||
|
popEnterOrg = popEnter;
|
||||||
|
replaceEnterOrg = replaceEnter;
|
||||||
|
pushExitOrg = pushExit;
|
||||||
|
popExitOrg = popExit;
|
||||||
|
replaceExitOrg = replaceExit;
|
||||||
|
updateTrans();
|
||||||
|
}
|
||||||
|
|
||||||
|
Transition {
|
||||||
|
id: reducedMotionTransitionExit
|
||||||
|
|
||||||
|
PropertyAnimation {
|
||||||
|
duration: 200
|
||||||
|
from: 1
|
||||||
|
property: "opacity"
|
||||||
|
to: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Transition {
|
||||||
|
id: reducedMotionTransitionEnter
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
PropertyAction {
|
||||||
|
property: "opacity"
|
||||||
|
value: 0
|
||||||
|
}
|
||||||
|
PauseAnimation {
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
PropertyAnimation {
|
||||||
|
duration: 200
|
||||||
|
from: 0
|
||||||
|
property: "opacity"
|
||||||
|
to: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Connections {
|
Connections {
|
||||||
target: Settings
|
|
||||||
function onReducedMotionChanged() {
|
function onReducedMotionChanged() {
|
||||||
mainWindow.updateTrans();
|
mainWindow.updateTrans();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
target: Settings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: welcomePage
|
id: welcomePage
|
||||||
|
|
||||||
WelcomePage {
|
WelcomePage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: chatPage
|
id: chatPage
|
||||||
|
|
||||||
ChatPage {
|
ChatPage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: loginPage
|
id: loginPage
|
||||||
|
|
||||||
LoginPage {
|
LoginPage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: registerPage
|
id: registerPage
|
||||||
|
|
||||||
RegisterPage {
|
RegisterPage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: userSettingsPage
|
id: userSettingsPage
|
||||||
|
|
||||||
UserSettingsPage {
|
UserSettingsPage {
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
Snackbar {
|
||||||
|
id: snackbar
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Snackbar { id: snackbar }
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onSwitchToChatPage() {
|
|
||||||
mainWindow.replace(null, chatPage);
|
|
||||||
}
|
|
||||||
function onSwitchToLoginPage(error) {
|
|
||||||
mainWindow.replace(welcomePage, {}, loginPage, {"error": error}, StackView.PopTransition);
|
|
||||||
}
|
|
||||||
function onShowNotification(msg) {
|
function onShowNotification(msg) {
|
||||||
snackbar.showNotification(msg);
|
snackbar.showNotification(msg);
|
||||||
console.log("New snack: " + msg);
|
console.log("New snack: " + msg);
|
||||||
}
|
}
|
||||||
|
function onSwitchToChatPage() {
|
||||||
|
mainWindow.replace(null, chatPage);
|
||||||
|
}
|
||||||
|
function onSwitchToLoginPage(error) {
|
||||||
|
mainWindow.replace(welcomePage, {}, loginPage, {
|
||||||
|
"error": error
|
||||||
|
}, StackView.PopTransition);
|
||||||
|
}
|
||||||
|
|
||||||
target: MainWindow
|
target: MainWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,22 +10,29 @@ import QtQuick.Layouts 1.3
|
||||||
import im.nheko 1.0
|
import im.nheko 1.0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
visible: false
|
|
||||||
enabled: false
|
enabled: false
|
||||||
|
visible: false
|
||||||
|
|
||||||
Dialog {
|
Dialog {
|
||||||
id: showRecoverKeyDialog
|
id: showRecoverKeyDialog
|
||||||
|
|
||||||
property string recoveryKey: ""
|
property string recoveryKey: ""
|
||||||
|
|
||||||
parent: Overlay.overlay
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
height: content.height + implicitFooterHeight + implicitHeaderHeight
|
|
||||||
width: content.width
|
|
||||||
padding: 0
|
|
||||||
modal: true
|
|
||||||
standardButtons: Dialog.Ok
|
|
||||||
closePolicy: Popup.NoAutoClose
|
closePolicy: Popup.NoAutoClose
|
||||||
|
height: content.height + implicitFooterHeight + implicitHeaderHeight
|
||||||
|
modal: true
|
||||||
|
padding: 0
|
||||||
|
parent: Overlay.overlay
|
||||||
|
standardButtons: Dialog.Ok
|
||||||
|
width: content.width
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
border.color: Nheko.theme.separator
|
||||||
|
border.width: 1
|
||||||
|
color: palette.window
|
||||||
|
radius: Nheko.paddingSmall
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: content
|
id: content
|
||||||
|
@ -33,45 +40,33 @@ Item {
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
Layout.margins: Nheko.paddingMedium
|
Layout.margins: Nheko.paddingMedium
|
||||||
Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4
|
Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4
|
||||||
Layout.fillWidth: true
|
|
||||||
text: qsTr("This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200!")
|
|
||||||
color: palette.text
|
color: palette.text
|
||||||
|
text: qsTr("This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200!")
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
TextEdit {
|
TextEdit {
|
||||||
Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4
|
||||||
|
color: palette.text
|
||||||
|
font.bold: true
|
||||||
horizontalAlignment: TextEdit.AlignHCenter
|
horizontalAlignment: TextEdit.AlignHCenter
|
||||||
verticalAlignment: TextEdit.AlignVCenter
|
|
||||||
readOnly: true
|
readOnly: true
|
||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
text: showRecoverKeyDialog.recoveryKey
|
text: showRecoverKeyDialog.recoveryKey
|
||||||
color: palette.text
|
verticalAlignment: TextEdit.AlignVCenter
|
||||||
font.bold: true
|
|
||||||
wrapMode: TextEdit.Wrap
|
wrapMode: TextEdit.Wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
color: palette.window
|
|
||||||
border.color: Nheko.theme.separator
|
|
||||||
border.width: 1
|
|
||||||
radius: Nheko.paddingSmall
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
P.MessageDialog {
|
P.MessageDialog {
|
||||||
id: successDialog
|
id: successDialog
|
||||||
|
|
||||||
buttons: P.MessageDialog.Ok
|
buttons: P.MessageDialog.Ok
|
||||||
text: qsTr("Encryption setup successfully")
|
text: qsTr("Encryption setup successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
P.MessageDialog {
|
P.MessageDialog {
|
||||||
id: failureDialog
|
id: failureDialog
|
||||||
|
|
||||||
|
@ -80,85 +75,86 @@ Item {
|
||||||
buttons: P.MessageDialog.Ok
|
buttons: P.MessageDialog.Ok
|
||||||
text: qsTr("Failed to setup encryption: %1").arg(errorMessage)
|
text: qsTr("Failed to setup encryption: %1").arg(errorMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindowDialog {
|
MainWindowDialog {
|
||||||
id: bootstrapCrosssigning
|
id: bootstrapCrosssigning
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
border.color: Nheko.theme.separator
|
||||||
|
border.width: 1
|
||||||
|
color: palette.window
|
||||||
|
radius: Nheko.paddingSmall
|
||||||
|
}
|
||||||
|
|
||||||
onAccepted: SelfVerificationStatus.setupCrosssigning(storeSecretsOnline.checked, usePassword.checked ? passwordField.text : "", useOnlineKeyBackup.checked)
|
onAccepted: SelfVerificationStatus.setupCrosssigning(storeSecretsOnline.checked, usePassword.checked ? passwordField.text : "", useOnlineKeyBackup.checked)
|
||||||
|
|
||||||
GridLayout {
|
GridLayout {
|
||||||
id: grid
|
id: grid
|
||||||
|
|
||||||
width: bootstrapCrosssigning.useableWidth
|
columnSpacing: 0
|
||||||
columns: 2
|
columns: 2
|
||||||
rowSpacing: 0
|
rowSpacing: 0
|
||||||
columnSpacing: 0
|
width: bootstrapCrosssigning.useableWidth
|
||||||
z: 1
|
z: 1
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.margins: Nheko.paddingMedium
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.columnSpan: 2
|
Layout.columnSpan: 2
|
||||||
|
Layout.margins: Nheko.paddingMedium
|
||||||
|
color: palette.text
|
||||||
font.pointSize: fontMetrics.font.pointSize * 2
|
font.pointSize: fontMetrics.font.pointSize * 2
|
||||||
text: qsTr("Setup Encryption")
|
text: qsTr("Setup Encryption")
|
||||||
color: palette.text
|
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.margins: Nheko.paddingMedium
|
|
||||||
Layout.alignment: Qt.AlignLeft
|
Layout.alignment: Qt.AlignLeft
|
||||||
Layout.columnSpan: 2
|
Layout.columnSpan: 2
|
||||||
|
Layout.margins: Nheko.paddingMedium
|
||||||
Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2
|
Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2
|
||||||
text: qsTr("Hello and welcome to Matrix!\nIt seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful!")
|
|
||||||
color: palette.text
|
color: palette.text
|
||||||
|
text: qsTr("Hello and welcome to Matrix!\nIt seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful!")
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.margins: Nheko.paddingMedium
|
|
||||||
Layout.alignment: Qt.AlignLeft
|
Layout.alignment: Qt.AlignLeft
|
||||||
Layout.columnSpan: 1
|
Layout.columnSpan: 1
|
||||||
|
Layout.margins: Nheko.paddingMedium
|
||||||
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
|
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
|
||||||
text: "Store secrets online.\nYou have a few secrets to make all the encryption magic work. While you can keep them stored only locally, we recommend storing them encrypted on the server. Otherwise it will be painful to recover them. Only disable this if you are paranoid and like losing your data!"
|
|
||||||
color: palette.text
|
color: palette.text
|
||||||
|
text: "Store secrets online.\nYou have a few secrets to make all the encryption magic work. While you can keep them stored only locally, we recommend storing them encrypted on the server. Otherwise it will be painful to recover them. Only disable this if you are paranoid and like losing your data!"
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.margins: Nheko.paddingMedium
|
|
||||||
Layout.preferredHeight: storeSecretsOnline.height
|
|
||||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: Nheko.paddingMedium
|
||||||
|
Layout.preferredHeight: storeSecretsOnline.height
|
||||||
|
|
||||||
ToggleButton {
|
ToggleButton {
|
||||||
id: storeSecretsOnline
|
id: storeSecretsOnline
|
||||||
|
|
||||||
checked: true
|
checked: true
|
||||||
|
|
||||||
onClicked: console.log("Store secrets toggled: " + checked)
|
onClicked: console.log("Store secrets toggled: " + checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.margins: Nheko.paddingMedium
|
|
||||||
Layout.alignment: Qt.AlignLeft
|
Layout.alignment: Qt.AlignLeft
|
||||||
Layout.columnSpan: 1
|
Layout.columnSpan: 1
|
||||||
Layout.rowSpan: 2
|
Layout.margins: Nheko.paddingMedium
|
||||||
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
|
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
|
||||||
visible: storeSecretsOnline.checked
|
Layout.rowSpan: 2
|
||||||
text: "Set an online backup password.\nWe recommend you DON'T set a password and instead only rely on the recovery key. You will get a recovery key in any case when storing the cross-signing secrets online, but passwords are usually not very random, so they are easier to attack than a completely random recovery key. If you choose to use a password, DON'T make it the same as your login password, otherwise your server can read all your encrypted messages. (You don't want that.)"
|
|
||||||
color: palette.text
|
color: palette.text
|
||||||
|
text: "Set an online backup password.\nWe recommend you DON'T set a password and instead only rely on the recovery key. You will get a recovery key in any case when storing the cross-signing secrets online, but passwords are usually not very random, so they are easier to attack than a completely random recovery key. If you choose to use a password, DON'T make it the same as your login password, otherwise your server can read all your encrypted messages. (You don't want that.)"
|
||||||
|
visible: storeSecretsOnline.checked
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.margins: Nheko.paddingMedium
|
|
||||||
Layout.topMargin: Nheko.paddingLarge
|
|
||||||
Layout.preferredHeight: storeSecretsOnline.height
|
|
||||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||||
Layout.rowSpan: usePassword.checked ? 1 : 2
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: Nheko.paddingMedium
|
||||||
|
Layout.preferredHeight: storeSecretsOnline.height
|
||||||
|
Layout.rowSpan: usePassword.checked ? 1 : 2
|
||||||
|
Layout.topMargin: Nheko.paddingLarge
|
||||||
visible: storeSecretsOnline.checked
|
visible: storeSecretsOnline.checked
|
||||||
|
|
||||||
ToggleButton {
|
ToggleButton {
|
||||||
|
@ -166,57 +162,43 @@ Item {
|
||||||
|
|
||||||
checked: false
|
checked: false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixTextField {
|
MatrixTextField {
|
||||||
id: passwordField
|
id: passwordField
|
||||||
|
|
||||||
Layout.margins: Nheko.paddingMedium
|
|
||||||
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
|
|
||||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||||
Layout.columnSpan: 1
|
Layout.columnSpan: 1
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
visible: storeSecretsOnline.checked && usePassword.checked
|
|
||||||
echoMode: TextInput.Password
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.margins: Nheko.paddingMedium
|
Layout.margins: Nheko.paddingMedium
|
||||||
|
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
|
||||||
|
echoMode: TextInput.Password
|
||||||
|
visible: storeSecretsOnline.checked && usePassword.checked
|
||||||
|
}
|
||||||
|
Label {
|
||||||
Layout.alignment: Qt.AlignLeft
|
Layout.alignment: Qt.AlignLeft
|
||||||
Layout.columnSpan: 1
|
Layout.columnSpan: 1
|
||||||
|
Layout.margins: Nheko.paddingMedium
|
||||||
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
|
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
|
||||||
text: "Use online key backup.\nStore the keys for your messages securely encrypted online. In general you do want this, because it protects your messages from becoming unreadable, if you log out by accident. It does however carry a small security risk, if you ever share your recovery key by accident. Currently this also has some other weaknesses, that might allow the server to insert new keys into your backup. The server will however never be able to read your messages."
|
|
||||||
color: palette.text
|
color: palette.text
|
||||||
|
text: "Use online key backup.\nStore the keys for your messages securely encrypted online. In general you do want this, because it protects your messages from becoming unreadable, if you log out by accident. It does however carry a small security risk, if you ever share your recovery key by accident. Currently this also has some other weaknesses, that might allow the server to insert new keys into your backup. The server will however never be able to read your messages."
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.margins: Nheko.paddingMedium
|
|
||||||
Layout.preferredHeight: storeSecretsOnline.height
|
|
||||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: Nheko.paddingMedium
|
||||||
|
Layout.preferredHeight: storeSecretsOnline.height
|
||||||
|
|
||||||
ToggleButton {
|
ToggleButton {
|
||||||
id: useOnlineKeyBackup
|
id: useOnlineKeyBackup
|
||||||
|
|
||||||
checked: true
|
checked: true
|
||||||
|
|
||||||
onClicked: console.log("Online key backup toggled: " + checked)
|
onClicked: console.log("Online key backup toggled: " + checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
color: palette.window
|
|
||||||
border.color: Nheko.theme.separator
|
|
||||||
border.width: 1
|
|
||||||
radius: Nheko.paddingSmall
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindowDialog {
|
MainWindowDialog {
|
||||||
id: verifyMasterKey
|
id: verifyMasterKey
|
||||||
|
|
||||||
|
@ -225,54 +207,61 @@ Item {
|
||||||
GridLayout {
|
GridLayout {
|
||||||
id: masterGrid
|
id: masterGrid
|
||||||
|
|
||||||
width: verifyMasterKey.useableWidth
|
|
||||||
columns: 1
|
columns: 1
|
||||||
|
width: verifyMasterKey.useableWidth
|
||||||
z: 1
|
z: 1
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.margins: Nheko.paddingMedium
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.margins: Nheko.paddingMedium
|
||||||
|
color: palette.text
|
||||||
//Layout.columnSpan: 2
|
//Layout.columnSpan: 2
|
||||||
font.pointSize: fontMetrics.font.pointSize * 2
|
font.pointSize: fontMetrics.font.pointSize * 2
|
||||||
text: qsTr("Activate Encryption")
|
text: qsTr("Activate Encryption")
|
||||||
color: palette.text
|
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.margins: Nheko.paddingMedium
|
|
||||||
Layout.alignment: Qt.AlignLeft
|
Layout.alignment: Qt.AlignLeft
|
||||||
|
Layout.margins: Nheko.paddingMedium
|
||||||
//Layout.columnSpan: 2
|
//Layout.columnSpan: 2
|
||||||
Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2
|
Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2
|
||||||
text: qsTr("It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below.\nIf you choose verify, you need to have the other device available. If you choose \"enter passphrase\", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point.")
|
|
||||||
color: palette.text
|
color: palette.text
|
||||||
|
text: qsTr("It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below.\nIf you choose verify, you need to have the other device available. If you choose \"enter passphrase\", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point.")
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
FlatButton {
|
FlatButton {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: qsTr("verify")
|
text: qsTr("verify")
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
SelfVerificationStatus.verifyMasterKey();
|
SelfVerificationStatus.verifyMasterKey();
|
||||||
verifyMasterKey.close();
|
verifyMasterKey.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FlatButton {
|
FlatButton {
|
||||||
visible: SelfVerificationStatus.hasSSSS
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: qsTr("enter passphrase")
|
text: qsTr("enter passphrase")
|
||||||
|
visible: SelfVerificationStatus.hasSSSS
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
SelfVerificationStatus.verifyMasterKeyWithPassphrase();
|
SelfVerificationStatus.verifyMasterKeyWithPassphrase();
|
||||||
verifyMasterKey.close();
|
verifyMasterKey.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
|
function onSetupCompleted() {
|
||||||
|
successDialog.open();
|
||||||
|
}
|
||||||
|
function onSetupFailed(m) {
|
||||||
|
failureDialog.errorMessage = m;
|
||||||
|
failureDialog.open();
|
||||||
|
}
|
||||||
|
function onShowRecoveryKey(key) {
|
||||||
|
showRecoverKeyDialog.recoveryKey = key;
|
||||||
|
showRecoverKeyDialog.open();
|
||||||
|
}
|
||||||
function onStatusChanged() {
|
function onStatusChanged() {
|
||||||
console.log("STATUS CHANGED: " + SelfVerificationStatus.status);
|
console.log("STATUS CHANGED: " + SelfVerificationStatus.status);
|
||||||
if (SelfVerificationStatus.status == SelfVerificationStatus.NoMasterKey) {
|
if (SelfVerificationStatus.status == SelfVerificationStatus.NoMasterKey) {
|
||||||
|
@ -285,21 +274,6 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onShowRecoveryKey(key) {
|
|
||||||
showRecoverKeyDialog.recoveryKey = key;
|
|
||||||
showRecoverKeyDialog.open();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSetupCompleted() {
|
|
||||||
successDialog.open();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSetupFailed(m) {
|
|
||||||
failureDialog.errorMessage = m;
|
|
||||||
failureDialog.open();
|
|
||||||
}
|
|
||||||
|
|
||||||
target: SelfVerificationStatus
|
target: SelfVerificationStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,15 +9,9 @@ import im.nheko 1.0
|
||||||
ImageButton {
|
ImageButton {
|
||||||
id: indicator
|
id: indicator
|
||||||
|
|
||||||
required property int status
|
|
||||||
required property string eventId
|
required property string eventId
|
||||||
|
required property int status
|
||||||
|
|
||||||
width: 16
|
|
||||||
height: 16
|
|
||||||
hoverEnabled: true
|
|
||||||
changeColorOnHover: (status == MtxEvent.Read)
|
|
||||||
cursor: (status == MtxEvent.Read) ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
||||||
ToolTip.visible: hovered && status != MtxEvent.Empty
|
|
||||||
ToolTip.text: {
|
ToolTip.text: {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case MtxEvent.Failed:
|
case MtxEvent.Failed:
|
||||||
|
@ -32,11 +26,11 @@ ImageButton {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onClicked: {
|
ToolTip.visible: hovered && status != MtxEvent.Empty
|
||||||
if (status == MtxEvent.Read)
|
changeColorOnHover: (status == MtxEvent.Read)
|
||||||
room.showReadReceipts(eventId);
|
cursor: (status == MtxEvent.Read) ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
height: 16
|
||||||
}
|
hoverEnabled: true
|
||||||
image: {
|
image: {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case MtxEvent.Failed:
|
case MtxEvent.Failed:
|
||||||
|
@ -51,4 +45,10 @@ ImageButton {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
width: 16
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (status == MtxEvent.Read)
|
||||||
|
room.showReadReceipts(eventId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,72 +13,45 @@ import im.nheko 1.0
|
||||||
AbstractButton {
|
AbstractButton {
|
||||||
id: r
|
id: r
|
||||||
|
|
||||||
required property double proportionalHeight
|
|
||||||
required property int type
|
|
||||||
required property string typeString
|
|
||||||
required property int originalWidth
|
|
||||||
required property string blurhash
|
required property string blurhash
|
||||||
required property string body
|
required property string body
|
||||||
required property string formattedBody
|
required property string callType
|
||||||
|
required property int duration
|
||||||
|
required property int encryptionError
|
||||||
required property string eventId
|
required property string eventId
|
||||||
required property string filename
|
required property string filename
|
||||||
required property string filesize
|
required property string filesize
|
||||||
required property string url
|
required property string formattedBody
|
||||||
required property string thumbnailUrl
|
required property int index
|
||||||
required property bool isOnlyEmoji
|
|
||||||
required property bool isSender
|
|
||||||
required property bool isEncrypted
|
|
||||||
required property bool isEditable
|
required property bool isEditable
|
||||||
required property bool isEdited
|
required property bool isEdited
|
||||||
|
required property bool isEncrypted
|
||||||
|
required property bool isOnlyEmoji
|
||||||
|
required property bool isSender
|
||||||
required property bool isStateEvent
|
required property bool isStateEvent
|
||||||
|
required property int notificationlevel
|
||||||
|
required property int originalWidth
|
||||||
|
required property double proportionalHeight
|
||||||
|
required property var reactions
|
||||||
|
required property int relatedEventCacheBuster
|
||||||
required property string replyTo
|
required property string replyTo
|
||||||
|
required property string roomName
|
||||||
|
required property string roomTopic
|
||||||
|
required property int status
|
||||||
required property string threadId
|
required property string threadId
|
||||||
|
required property string thumbnailUrl
|
||||||
|
required property var timestamp
|
||||||
|
required property int trustlevel
|
||||||
|
required property int type
|
||||||
|
required property string typeString
|
||||||
|
required property string url
|
||||||
required property string userId
|
required property string userId
|
||||||
required property string userName
|
required property string userName
|
||||||
required property string roomTopic
|
|
||||||
required property string roomName
|
|
||||||
required property string callType
|
|
||||||
required property var reactions
|
|
||||||
required property int trustlevel
|
|
||||||
required property int notificationlevel
|
|
||||||
required property int encryptionError
|
|
||||||
required property int duration
|
|
||||||
required property var timestamp
|
|
||||||
required property int status
|
|
||||||
required property int index
|
|
||||||
required property int relatedEventCacheBuster
|
|
||||||
|
|
||||||
|
height: row.height + (reactionRow.height > 0 ? reactionRow.height - 2 : 0) + unreadRow.height
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: row.height+(reactionRow.height > 0 ? reactionRow.height-2 : 0 )+unreadRow.height
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
color: (Settings.messageHoverHighlight && hovered) ? palette.alternateBase : "transparent"
|
|
||||||
anchors.fill: parent
|
|
||||||
// this looks better without margins
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.RightButton
|
|
||||||
onSingleTapped: messageContextMenu.show(eventId, threadId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
|
|
||||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
|
||||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
onPressAndHold: messageContextMenu.show(eventId, threadId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
|
|
||||||
onDoubleClicked: room.reply = eventId
|
|
||||||
|
|
||||||
DragHandler {
|
|
||||||
id: draghandler
|
|
||||||
yAxis.enabled: false
|
|
||||||
xAxis.maximum: 100
|
|
||||||
xAxis.minimum: -100
|
|
||||||
onActiveChanged: {
|
|
||||||
if(!active && (x < -70 || x > 70))
|
|
||||||
room.reply = eventId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
states: State {
|
states: State {
|
||||||
name: "dragging"
|
name: "dragging"
|
||||||
when: draghandler.active
|
when: draghandler.active
|
||||||
|
@ -86,265 +59,292 @@ AbstractButton {
|
||||||
transitions: Transition {
|
transitions: Transition {
|
||||||
from: "dragging"
|
from: "dragging"
|
||||||
to: ""
|
to: ""
|
||||||
|
|
||||||
PropertyAnimation {
|
PropertyAnimation {
|
||||||
target: r
|
|
||||||
properties: "x"
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
to: 0
|
|
||||||
duration: 100
|
duration: 100
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
properties: "x"
|
||||||
|
target: r
|
||||||
|
to: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
let link = contentItem.child.linkAt != undefined && contentItem.child.linkAt(pressX-row.x-msg.x, pressY-row.y-msg.y-contentItem.y);
|
let link = contentItem.child.linkAt != undefined && contentItem.child.linkAt(pressX - row.x - msg.x, pressY - row.y - msg.y - contentItem.y);
|
||||||
if (link) {
|
if (link) {
|
||||||
Nheko.openLink(link)
|
Nheko.openLink(link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onDoubleClicked: room.reply = eventId
|
||||||
|
onPressAndHold: messageContextMenu.show(eventId, threadId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: (Settings.messageHoverHighlight && hovered) ? palette.alternateBase : "transparent"
|
||||||
|
|
||||||
|
// this looks better without margins
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
|
||||||
|
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||||
|
|
||||||
|
onSingleTapped: messageContextMenu.show(eventId, threadId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DragHandler {
|
||||||
|
id: draghandler
|
||||||
|
|
||||||
|
xAxis.maximum: 100
|
||||||
|
xAxis.minimum: -100
|
||||||
|
yAxis.enabled: false
|
||||||
|
|
||||||
|
onActiveChanged: {
|
||||||
|
if (!active && (x < -70 || x > 70))
|
||||||
|
room.reply = eventId;
|
||||||
|
}
|
||||||
|
}
|
||||||
AbstractButton {
|
AbstractButton {
|
||||||
anchors.leftMargin: Settings.smallAvatars? 0 : (Nheko.avatarSize + 8) // align bubble with section header
|
ToolTip.delay: Nheko.tooltipDelay
|
||||||
|
ToolTip.text: qsTr("Part of a thread")
|
||||||
|
ToolTip.visible: hovered
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8) // align bubble with section header
|
||||||
|
height: parent.height
|
||||||
visible: threadId
|
visible: threadId
|
||||||
width: 4
|
width: 4
|
||||||
height: parent.height
|
|
||||||
|
onClicked: room.thread = threadId
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: threadLine
|
id: threadLine
|
||||||
|
|
||||||
color: TimelineManager.userColor(threadId, palette.base)
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
color: TimelineManager.userColor(threadId, palette.base)
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: Nheko.tooltipDelay
|
|
||||||
ToolTip.text: qsTr("Part of a thread")
|
|
||||||
onClicked: room.thread = threadId
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: row
|
id: row
|
||||||
property bool bubbleOnRight : isSender && Settings.bubbles
|
|
||||||
anchors.leftMargin: (isStateEvent || Settings.smallAvatars? 0 : (Nheko.avatarSize + 8)) + (threadId ? 6 : 0) // align bubble with section header
|
|
||||||
anchors.left: (isStateEvent || bubbleOnRight) ? undefined : parent.left
|
|
||||||
anchors.right: (isStateEvent || !bubbleOnRight) ? undefined : parent.right
|
|
||||||
anchors.horizontalCenter: isStateEvent? parent.horizontalCenter : undefined
|
|
||||||
property int maxWidth: (parent.width-(Settings.smallAvatars || isStateEvent? 0 : Nheko.avatarSize+8))*(Settings.bubbles && !isStateEvent? 0.9 : 1)
|
|
||||||
width: Settings.bubbles? Math.min(maxWidth,Math.max(reply.implicitWidth+8,contentItem.implicitWidth+metadata.width+20)) : maxWidth
|
|
||||||
height: msg.height+msg.anchors.margins*2
|
|
||||||
|
|
||||||
property color userColor: TimelineManager.userColor(userId, palette.base)
|
|
||||||
property color bgColor: palette.base
|
property color bgColor: palette.base
|
||||||
color: (Settings.bubbles && !isStateEvent) ? Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.2)) : "#00000000"
|
property bool bubbleOnRight: isSender && Settings.bubbles
|
||||||
radius: 4
|
property int maxWidth: (parent.width - (Settings.smallAvatars || isStateEvent ? 0 : Nheko.avatarSize + 8)) * (Settings.bubbles && !isStateEvent ? 0.9 : 1)
|
||||||
border.width: r.notificationlevel == MtxEvent.Highlight ? 1 : 0
|
property color userColor: TimelineManager.userColor(userId, palette.base)
|
||||||
|
|
||||||
|
anchors.horizontalCenter: isStateEvent ? parent.horizontalCenter : undefined
|
||||||
|
anchors.left: (isStateEvent || bubbleOnRight) ? undefined : parent.left
|
||||||
|
anchors.leftMargin: (isStateEvent || Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8)) + (threadId ? 6 : 0) // align bubble with section header
|
||||||
|
anchors.right: (isStateEvent || !bubbleOnRight) ? undefined : parent.right
|
||||||
border.color: Nheko.theme.red
|
border.color: Nheko.theme.red
|
||||||
|
border.width: r.notificationlevel == MtxEvent.Highlight ? 1 : 0
|
||||||
|
color: (Settings.bubbles && !isStateEvent) ? Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.2)) : "#00000000"
|
||||||
|
height: msg.height + msg.anchors.margins * 2
|
||||||
|
radius: 4
|
||||||
|
width: Settings.bubbles ? Math.min(maxWidth, Math.max(reply.implicitWidth + 8, contentItem.implicitWidth + metadata.width + 20)) : maxWidth
|
||||||
|
|
||||||
GridLayout {
|
GridLayout {
|
||||||
|
id: msg
|
||||||
|
|
||||||
|
columnSpacing: 2
|
||||||
|
columns: Settings.bubbles ? 1 : 2
|
||||||
|
rowSpacing: 0
|
||||||
|
rows: Settings.bubbles ? 3 : 2
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
left: parent.left
|
left: parent.left
|
||||||
top: parent.top
|
|
||||||
right: parent.right
|
|
||||||
margins: (Settings.bubbles && ! isStateEvent)? 4 : 2
|
|
||||||
leftMargin: 4
|
leftMargin: 4
|
||||||
|
margins: (Settings.bubbles && !isStateEvent) ? 4 : 2
|
||||||
|
right: parent.right
|
||||||
rightMargin: 4
|
rightMargin: 4
|
||||||
|
top: parent.top
|
||||||
}
|
}
|
||||||
id: msg
|
|
||||||
rowSpacing: 0
|
|
||||||
columnSpacing: 2
|
|
||||||
columns: Settings.bubbles? 1 : 2
|
|
||||||
rows: Settings.bubbles? 3 : 2
|
|
||||||
|
|
||||||
// fancy reply, if this is a reply
|
// fancy reply, if this is a reply
|
||||||
Reply {
|
Reply {
|
||||||
Layout.row: 0
|
|
||||||
Layout.column: 0
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.maximumWidth: Settings.bubbles? Number.MAX_VALUE : implicitWidth
|
|
||||||
Layout.bottomMargin: visible? 2 : 0
|
|
||||||
Layout.preferredHeight: height
|
|
||||||
id: reply
|
id: reply
|
||||||
|
|
||||||
function fromModel(role) {
|
function fromModel(role) {
|
||||||
return replyTo != "" ? room.dataById(replyTo, role, r.eventId) : null;
|
return replyTo != "" ? room.dataById(replyTo, role, r.eventId) : null;
|
||||||
}
|
}
|
||||||
visible: replyTo
|
|
||||||
userColor: r.relatedEventCacheBuster, TimelineManager.userColor(userId, palette.base)
|
Layout.bottomMargin: visible ? 2 : 0
|
||||||
|
Layout.column: 0
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.maximumWidth: Settings.bubbles ? Number.MAX_VALUE : implicitWidth
|
||||||
|
Layout.preferredHeight: height
|
||||||
|
Layout.row: 0
|
||||||
blurhash: r.relatedEventCacheBuster, fromModel(Room.Blurhash) ?? ""
|
blurhash: r.relatedEventCacheBuster, fromModel(Room.Blurhash) ?? ""
|
||||||
body: r.relatedEventCacheBuster, fromModel(Room.Body) ?? ""
|
body: r.relatedEventCacheBuster, fromModel(Room.Body) ?? ""
|
||||||
formattedBody: r.relatedEventCacheBuster, fromModel(Room.FormattedBody) ?? ""
|
callType: r.relatedEventCacheBuster, fromModel(Room.CallType) ?? ""
|
||||||
|
duration: r.relatedEventCacheBuster, fromModel(Room.Duration) ?? 0
|
||||||
|
encryptionError: r.relatedEventCacheBuster, fromModel(Room.EncryptionError) ?? 0
|
||||||
eventId: fromModel(Room.EventId) ?? ""
|
eventId: fromModel(Room.EventId) ?? ""
|
||||||
filename: r.relatedEventCacheBuster, fromModel(Room.Filename) ?? ""
|
filename: r.relatedEventCacheBuster, fromModel(Room.Filename) ?? ""
|
||||||
filesize: r.relatedEventCacheBuster, fromModel(Room.Filesize) ?? ""
|
filesize: r.relatedEventCacheBuster, fromModel(Room.Filesize) ?? ""
|
||||||
|
formattedBody: r.relatedEventCacheBuster, fromModel(Room.FormattedBody) ?? ""
|
||||||
|
isOnlyEmoji: r.relatedEventCacheBuster, fromModel(Room.IsOnlyEmoji) ?? false
|
||||||
|
isStateEvent: r.relatedEventCacheBuster, fromModel(Room.IsStateEvent) ?? false
|
||||||
|
originalWidth: r.relatedEventCacheBuster, fromModel(Room.OriginalWidth) ?? 0
|
||||||
proportionalHeight: r.relatedEventCacheBuster, fromModel(Room.ProportionalHeight) ?? 1
|
proportionalHeight: r.relatedEventCacheBuster, fromModel(Room.ProportionalHeight) ?? 1
|
||||||
|
relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0
|
||||||
|
roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? ""
|
||||||
|
roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? ""
|
||||||
|
thumbnailUrl: r.relatedEventCacheBuster, fromModel(Room.ThumbnailUrl) ?? ""
|
||||||
type: r.relatedEventCacheBuster, fromModel(Room.Type) ?? MtxEvent.UnknownMessage
|
type: r.relatedEventCacheBuster, fromModel(Room.Type) ?? MtxEvent.UnknownMessage
|
||||||
typeString: r.relatedEventCacheBuster, fromModel(Room.TypeString) ?? ""
|
typeString: r.relatedEventCacheBuster, fromModel(Room.TypeString) ?? ""
|
||||||
url: r.relatedEventCacheBuster, fromModel(Room.Url) ?? ""
|
url: r.relatedEventCacheBuster, fromModel(Room.Url) ?? ""
|
||||||
originalWidth: r.relatedEventCacheBuster, fromModel(Room.OriginalWidth) ?? 0
|
userColor: r.relatedEventCacheBuster, TimelineManager.userColor(userId, palette.base)
|
||||||
isOnlyEmoji: r.relatedEventCacheBuster, fromModel(Room.IsOnlyEmoji) ?? false
|
|
||||||
isStateEvent: r.relatedEventCacheBuster, fromModel(Room.IsStateEvent) ?? false
|
|
||||||
userId: r.relatedEventCacheBuster, fromModel(Room.UserId) ?? ""
|
userId: r.relatedEventCacheBuster, fromModel(Room.UserId) ?? ""
|
||||||
userName: r.relatedEventCacheBuster, fromModel(Room.UserName) ?? ""
|
userName: r.relatedEventCacheBuster, fromModel(Room.UserName) ?? ""
|
||||||
thumbnailUrl: r.relatedEventCacheBuster, fromModel(Room.ThumbnailUrl) ?? ""
|
visible: replyTo
|
||||||
duration: r.relatedEventCacheBuster, fromModel(Room.Duration) ?? 0
|
|
||||||
roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? ""
|
|
||||||
roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? ""
|
|
||||||
callType: r.relatedEventCacheBuster, fromModel(Room.CallType) ?? ""
|
|
||||||
encryptionError: r.relatedEventCacheBuster, fromModel(Room.EncryptionError) ?? 0
|
|
||||||
relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// actual message content
|
// actual message content
|
||||||
MessageDelegate {
|
MessageDelegate {
|
||||||
Layout.row: 1
|
id: contentItem
|
||||||
|
|
||||||
Layout.column: 0
|
Layout.column: 0
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: height
|
Layout.preferredHeight: height
|
||||||
id: contentItem
|
Layout.row: 1
|
||||||
|
|
||||||
blurhash: r.blurhash
|
blurhash: r.blurhash
|
||||||
body: r.body
|
body: r.body
|
||||||
formattedBody: r.formattedBody
|
callType: r.callType
|
||||||
|
duration: r.duration
|
||||||
|
encryptionError: r.encryptionError
|
||||||
eventId: r.eventId
|
eventId: r.eventId
|
||||||
filename: r.filename
|
filename: r.filename
|
||||||
filesize: r.filesize
|
filesize: r.filesize
|
||||||
|
formattedBody: r.formattedBody
|
||||||
|
isOnlyEmoji: r.isOnlyEmoji
|
||||||
|
isReply: false
|
||||||
|
isStateEvent: r.isStateEvent
|
||||||
|
metadataWidth: metadata.width
|
||||||
|
originalWidth: r.originalWidth
|
||||||
proportionalHeight: r.proportionalHeight
|
proportionalHeight: r.proportionalHeight
|
||||||
|
relatedEventCacheBuster: r.relatedEventCacheBuster
|
||||||
|
roomName: r.roomName
|
||||||
|
roomTopic: r.roomTopic
|
||||||
|
thumbnailUrl: r.thumbnailUrl
|
||||||
type: r.type
|
type: r.type
|
||||||
typeString: r.typeString ?? ""
|
typeString: r.typeString ?? ""
|
||||||
url: r.url
|
url: r.url
|
||||||
thumbnailUrl: r.thumbnailUrl
|
|
||||||
duration: r.duration
|
|
||||||
originalWidth: r.originalWidth
|
|
||||||
isOnlyEmoji: r.isOnlyEmoji
|
|
||||||
isStateEvent: r.isStateEvent
|
|
||||||
userId: r.userId
|
userId: r.userId
|
||||||
userName: r.userName
|
userName: r.userName
|
||||||
roomTopic: r.roomTopic
|
|
||||||
roomName: r.roomName
|
|
||||||
callType: r.callType
|
|
||||||
encryptionError: r.encryptionError
|
|
||||||
relatedEventCacheBuster: r.relatedEventCacheBuster
|
|
||||||
isReply: false
|
|
||||||
metadataWidth: metadata.width
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: metadata
|
id: metadata
|
||||||
Layout.column: Settings.bubbles? 0 : 1
|
|
||||||
Layout.row: Settings.bubbles? 2 : 0
|
property int iconSize: Math.floor(fontMetrics.ascent * scaling)
|
||||||
Layout.rowSpan: Settings.bubbles? 1 : 2
|
property double scaling: Settings.bubbles ? 0.75 : 1
|
||||||
Layout.bottomMargin: -2
|
|
||||||
Layout.topMargin: (contentItem.fitsMetadata && Settings.bubbles)? -height-Layout.bottomMargin : 0
|
|
||||||
Layout.alignment: Qt.AlignTop | Qt.AlignRight
|
Layout.alignment: Qt.AlignTop | Qt.AlignRight
|
||||||
|
Layout.bottomMargin: -2
|
||||||
|
Layout.column: Settings.bubbles ? 0 : 1
|
||||||
Layout.preferredWidth: implicitWidth
|
Layout.preferredWidth: implicitWidth
|
||||||
visible: !isStateEvent
|
Layout.row: Settings.bubbles ? 2 : 0
|
||||||
|
Layout.rowSpan: Settings.bubbles ? 1 : 2
|
||||||
|
Layout.topMargin: (contentItem.fitsMetadata && Settings.bubbles) ? -height - Layout.bottomMargin : 0
|
||||||
spacing: 2
|
spacing: 2
|
||||||
|
visible: !isStateEvent
|
||||||
property double scaling: Settings.bubbles? 0.75 : 1
|
|
||||||
|
|
||||||
property int iconSize: Math.floor(fontMetrics.ascent*scaling)
|
|
||||||
|
|
||||||
StatusIndicator {
|
StatusIndicator {
|
||||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||||
height: parent.iconSize
|
|
||||||
width: parent.iconSize
|
|
||||||
status: r.status
|
|
||||||
eventId: r.eventId
|
|
||||||
anchors.verticalCenter: ts.verticalCenter
|
anchors.verticalCenter: ts.verticalCenter
|
||||||
}
|
eventId: r.eventId
|
||||||
|
|
||||||
Image {
|
|
||||||
visible: isEdited || eventId == room.edit
|
|
||||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
|
||||||
height: parent.iconSize
|
height: parent.iconSize
|
||||||
|
status: r.status
|
||||||
width: parent.iconSize
|
width: parent.iconSize
|
||||||
sourceSize.width: parent.iconSize * Screen.devicePixelRatio
|
}
|
||||||
sourceSize.height: parent.iconSize * Screen.devicePixelRatio
|
Image {
|
||||||
source: "image://colorimage/:/icons/icons/ui/edit.svg?" + ((eventId == room.edit) ? palette.highlight : palette.buttonText)
|
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||||
ToolTip.visible: editHovered.hovered
|
|
||||||
ToolTip.delay: Nheko.tooltipDelay
|
ToolTip.delay: Nheko.tooltipDelay
|
||||||
ToolTip.text: qsTr("Edited")
|
ToolTip.text: qsTr("Edited")
|
||||||
|
ToolTip.visible: editHovered.hovered
|
||||||
anchors.verticalCenter: ts.verticalCenter
|
anchors.verticalCenter: ts.verticalCenter
|
||||||
|
height: parent.iconSize
|
||||||
|
source: "image://colorimage/:/icons/icons/ui/edit.svg?" + ((eventId == room.edit) ? palette.highlight : palette.buttonText)
|
||||||
|
sourceSize.height: parent.iconSize * Screen.devicePixelRatio
|
||||||
|
sourceSize.width: parent.iconSize * Screen.devicePixelRatio
|
||||||
|
visible: isEdited || eventId == room.edit
|
||||||
|
width: parent.iconSize
|
||||||
|
|
||||||
HoverHandler {
|
HoverHandler {
|
||||||
id: editHovered
|
id: editHovered
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
visible: threadId
|
|
||||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||||
height: parent.iconSize
|
|
||||||
width: parent.iconSize
|
|
||||||
image: ":/icons/icons/ui/thread.svg"
|
|
||||||
buttonTextColor: TimelineManager.userColor(threadId, palette.base)
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: Nheko.tooltipDelay
|
ToolTip.delay: Nheko.tooltipDelay
|
||||||
ToolTip.text: qsTr("Part of a thread")
|
ToolTip.text: qsTr("Part of a thread")
|
||||||
|
ToolTip.visible: hovered
|
||||||
anchors.verticalCenter: ts.verticalCenter
|
anchors.verticalCenter: ts.verticalCenter
|
||||||
|
buttonTextColor: TimelineManager.userColor(threadId, palette.base)
|
||||||
|
height: parent.iconSize
|
||||||
|
image: ":/icons/icons/ui/thread.svg"
|
||||||
|
visible: threadId
|
||||||
|
width: parent.iconSize
|
||||||
|
|
||||||
onClicked: room.thread = threadId
|
onClicked: room.thread = threadId
|
||||||
}
|
}
|
||||||
|
|
||||||
EncryptionIndicator {
|
EncryptionIndicator {
|
||||||
visible: room.isEncrypted
|
|
||||||
encrypted: isEncrypted
|
|
||||||
trust: trustlevel
|
|
||||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||||
height: parent.iconSize
|
|
||||||
width: parent.iconSize
|
|
||||||
sourceSize.width: parent.iconSize * Screen.devicePixelRatio
|
|
||||||
sourceSize.height: parent.iconSize * Screen.devicePixelRatio
|
|
||||||
anchors.verticalCenter: ts.verticalCenter
|
anchors.verticalCenter: ts.verticalCenter
|
||||||
|
encrypted: isEncrypted
|
||||||
|
height: parent.iconSize
|
||||||
|
sourceSize.height: parent.iconSize * Screen.devicePixelRatio
|
||||||
|
sourceSize.width: parent.iconSize * Screen.devicePixelRatio
|
||||||
|
trust: trustlevel
|
||||||
|
visible: room.isEncrypted
|
||||||
|
width: parent.iconSize
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: ts
|
id: ts
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||||
Layout.preferredWidth: implicitWidth
|
Layout.preferredWidth: implicitWidth
|
||||||
text: timestamp.toLocaleTimeString(Locale.ShortFormat)
|
|
||||||
color: palette.inactive.text
|
|
||||||
ToolTip.visible: ma.hovered
|
|
||||||
ToolTip.delay: Nheko.tooltipDelay
|
ToolTip.delay: Nheko.tooltipDelay
|
||||||
ToolTip.text: Qt.formatDateTime(timestamp, Qt.DefaultLocaleLongDate)
|
ToolTip.text: Qt.formatDateTime(timestamp, Qt.DefaultLocaleLongDate)
|
||||||
font.pointSize: fontMetrics.font.pointSize*parent.scaling
|
ToolTip.visible: ma.hovered
|
||||||
|
color: palette.inactive.text
|
||||||
|
font.pointSize: fontMetrics.font.pointSize * parent.scaling
|
||||||
|
text: timestamp.toLocaleTimeString(Locale.ShortFormat)
|
||||||
|
|
||||||
HoverHandler {
|
HoverHandler {
|
||||||
id: ma
|
id: ma
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Reactions {
|
Reactions {
|
||||||
anchors {
|
|
||||||
top: row.bottom
|
|
||||||
topMargin: -4
|
|
||||||
left: row.bubbleOnRight? undefined : row.left
|
|
||||||
right: row.bubbleOnRight? row.right : undefined
|
|
||||||
}
|
|
||||||
width: row.maxWidth
|
|
||||||
layoutDirection: row.bubbleOnRight? Qt.RightToLeft : Qt.LeftToRight
|
|
||||||
|
|
||||||
id: reactionRow
|
id: reactionRow
|
||||||
|
|
||||||
reactions: r.reactions
|
|
||||||
eventId: r.eventId
|
eventId: r.eventId
|
||||||
}
|
layoutDirection: row.bubbleOnRight ? Qt.RightToLeft : Qt.LeftToRight
|
||||||
|
reactions: r.reactions
|
||||||
|
width: row.maxWidth
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
left: row.bubbleOnRight ? undefined : row.left
|
||||||
|
right: row.bubbleOnRight ? row.right : undefined
|
||||||
|
top: row.bottom
|
||||||
|
topMargin: -4
|
||||||
|
}
|
||||||
|
}
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: unreadRow
|
id: unreadRow
|
||||||
|
|
||||||
|
color: palette.highlight
|
||||||
|
height: visible ? 3 : 0
|
||||||
|
visible: (r.index > 0 && (room.fullyReadEventId == r.eventId))
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
top: reactionRow.bottom
|
|
||||||
topMargin: 5
|
|
||||||
left: parent.left
|
left: parent.left
|
||||||
right: parent.right
|
right: parent.right
|
||||||
|
top: reactionRow.bottom
|
||||||
|
topMargin: 5
|
||||||
}
|
}
|
||||||
color: palette.highlight
|
|
||||||
|
|
||||||
visible: (r.index > 0 && (room.fullyReadEventId == r.eventId))
|
|
||||||
height: visible ? 3 : 0
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,86 +20,85 @@ import im.nheko.EmojiModel 1.0
|
||||||
Item {
|
Item {
|
||||||
id: timelineView
|
id: timelineView
|
||||||
|
|
||||||
|
required property PrivacyScreen privacyScreen
|
||||||
property var room: null
|
property var room: null
|
||||||
property var roomPreview: null
|
property var roomPreview: null
|
||||||
property bool showBackButton: false
|
|
||||||
property bool shouldEffectsRun: false
|
property bool shouldEffectsRun: false
|
||||||
required property PrivacyScreen privacyScreen
|
property bool showBackButton: false
|
||||||
|
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
onRoomChanged: if (room != null) room.triggerSpecialEffects()
|
// focus message input on key press, but not on Ctrl-C and such.
|
||||||
|
Keys.onPressed: event => {
|
||||||
|
if (event.text && event.key !== Qt.Key_Enter && event.key !== Qt.Key_Return && !topBar.searchHasFocus) {
|
||||||
|
TimelineManager.focusMessageInput();
|
||||||
|
room.input.setText(room.input.text + event.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onRoomChanged: if (room != null)
|
||||||
|
room.triggerSpecialEffects()
|
||||||
|
|
||||||
StickerPicker {
|
StickerPicker {
|
||||||
id: emojiPopup
|
id: emojiPopup
|
||||||
|
|
||||||
emoji: true
|
emoji: true
|
||||||
}
|
}
|
||||||
|
|
||||||
// focus message input on key press, but not on Ctrl-C and such.
|
|
||||||
Keys.onPressed: (event) => {
|
|
||||||
if (event.text && event.key !== Qt.Key_Enter && event.key !== Qt.Key_Return && !topBar.searchHasFocus) {
|
|
||||||
TimelineManager.focusMessageInput();
|
|
||||||
room.input.setText(room.input.text + event.text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: StandardKey.Close
|
sequence: StandardKey.Close
|
||||||
|
|
||||||
onActivated: Rooms.resetCurrentRoom()
|
onActivated: Rooms.resetCurrentRoom()
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
visible: !room && !TimelineManager.isInitialSync && (!roomPreview || !roomPreview.roomid)
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: qsTr("No room open")
|
|
||||||
font.pointSize: 24
|
font.pointSize: 24
|
||||||
|
text: qsTr("No room open")
|
||||||
|
visible: !room && !TimelineManager.isInitialSync && (!roomPreview || !roomPreview.roomid)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spinner {
|
Spinner {
|
||||||
visible: TimelineManager.isInitialSync
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
foreground: palette.mid
|
foreground: palette.mid
|
||||||
running: TimelineManager.isInitialSync
|
|
||||||
// height is somewhat arbitrary here... don't set width because width scales w/ height
|
// height is somewhat arbitrary here... don't set width because width scales w/ height
|
||||||
height: parent.height / 16
|
height: parent.height / 16
|
||||||
z: 3
|
|
||||||
opacity: hh.hovered ? 0.3 : 1
|
opacity: hh.hovered ? 0.3 : 1
|
||||||
|
running: TimelineManager.isInitialSync
|
||||||
|
visible: TimelineManager.isInitialSync
|
||||||
|
z: 3
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation { duration: 100; }
|
NumberAnimation {
|
||||||
|
duration: 100
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HoverHandler {
|
HoverHandler {
|
||||||
id: hh
|
id: hh
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: timelineLayout
|
id: timelineLayout
|
||||||
|
|
||||||
visible: room != null && !room.isSpace
|
|
||||||
enabled: visible
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
enabled: visible
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
visible: room != null && !room.isSpace
|
||||||
|
|
||||||
TopBar {
|
TopBar {
|
||||||
id: topBar
|
id: topBar
|
||||||
|
|
||||||
showBackButton: timelineView.showBackButton
|
showBackButton: timelineView.showBackButton
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
color: Nheko.theme.separator
|
||||||
height: 1
|
height: 1
|
||||||
z: 3
|
z: 3
|
||||||
color: Nheko.theme.separator
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: msgView
|
id: msgView
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
color: palette.base
|
color: palette.base
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
@ -118,143 +117,121 @@ Item {
|
||||||
|
|
||||||
target: timelineView
|
target: timelineView
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageView {
|
MessageView {
|
||||||
|
Layout.fillWidth: true
|
||||||
implicitHeight: msgView.height - typingIndicator.height
|
implicitHeight: msgView.height - typingIndicator.height
|
||||||
searchString: topBar.searchString
|
searchString: topBar.searchString
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
source: CallManager.isOnCall && CallManager.callType != CallType.VOICE ? "voip/VideoCall.qml" : ""
|
source: CallManager.isOnCall && CallManager.callType != CallType.VOICE ? "voip/VideoCall.qml" : ""
|
||||||
|
|
||||||
onLoaded: TimelineManager.setVideoCallItem()
|
onLoaded: TimelineManager.setVideoCallItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TypingIndicator {
|
TypingIndicator {
|
||||||
id: typingIndicator
|
id: typingIndicator
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CallInviteBar {
|
CallInviteBar {
|
||||||
id: callInviteBar
|
id: callInviteBar
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
z: 3
|
z: 3
|
||||||
}
|
}
|
||||||
|
|
||||||
ActiveCallBar {
|
ActiveCallBar {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
z: 3
|
z: 3
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
z: 3
|
|
||||||
height: 1
|
|
||||||
color: Nheko.theme.separator
|
color: Nheko.theme.separator
|
||||||
|
height: 1
|
||||||
|
z: 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
UploadBox {
|
UploadBox {
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageInputWarning {
|
MessageInputWarning {
|
||||||
text: qsTr("You are about to notify the whole room")
|
text: qsTr("You are about to notify the whole room")
|
||||||
visible: (room && room.permissions.canPingRoom() && room.input.containsAtRoom)
|
visible: (room && room.permissions.canPingRoom() && room.input.containsAtRoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageInputWarning {
|
MessageInputWarning {
|
||||||
text: qsTr("The command /%1 is not recognized and will be sent as part of your message").arg(room ? room.input.currentCommand : "")
|
text: qsTr("The command /%1 is not recognized and will be sent as part of your message").arg(room ? room.input.currentCommand : "")
|
||||||
visible: room ? room.input.containsInvalidCommand && !room.input.containsIncompleteCommand : false
|
visible: room ? room.input.containsInvalidCommand && !room.input.containsIncompleteCommand : false
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageInputWarning {
|
MessageInputWarning {
|
||||||
|
bubbleColor: Nheko.theme.orange
|
||||||
text: qsTr("/%1 looks like an incomplete command. To send it anyway, add a space to the end of your message.").arg(room ? room.input.currentCommand : "")
|
text: qsTr("/%1 looks like an incomplete command. To send it anyway, add a space to the end of your message.").arg(room ? room.input.currentCommand : "")
|
||||||
visible: room ? room.input.containsIncompleteCommand : false
|
visible: room ? room.input.containsIncompleteCommand : false
|
||||||
bubbleColor: Nheko.theme.orange
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ReplyPopup {
|
ReplyPopup {
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageInput {
|
MessageInput {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: preview
|
id: preview
|
||||||
|
|
||||||
|
property string avatarUrl: room ? room.roomAvatarUrl : (roomPreview ? roomPreview.roomAvatarUrl : "")
|
||||||
|
property string reason: roomPreview ? roomPreview.reason : ""
|
||||||
property string roomId: room ? room.roomId : (roomPreview ? roomPreview.roomid : "")
|
property string roomId: room ? room.roomId : (roomPreview ? roomPreview.roomid : "")
|
||||||
property string roomName: room ? room.roomName : (roomPreview ? roomPreview.roomName : "")
|
property string roomName: room ? room.roomName : (roomPreview ? roomPreview.roomName : "")
|
||||||
property string roomTopic: room ? room.roomTopic : (roomPreview ? roomPreview.roomTopic : "")
|
property string roomTopic: room ? room.roomTopic : (roomPreview ? roomPreview.roomTopic : "")
|
||||||
property string avatarUrl: room ? room.roomAvatarUrl : (roomPreview ? roomPreview.roomAvatarUrl : "")
|
|
||||||
property string reason: roomPreview ? roomPreview.reason : ""
|
|
||||||
|
|
||||||
visible: room != null && room.isSpace || roomPreview != null
|
|
||||||
enabled: visible
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Nheko.paddingLarge
|
anchors.margins: Nheko.paddingLarge
|
||||||
|
enabled: visible
|
||||||
spacing: Nheko.paddingLarge
|
spacing: Nheko.paddingLarge
|
||||||
|
visible: room != null && room.isSpace || roomPreview != null
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
}
|
}
|
||||||
|
|
||||||
Avatar {
|
Avatar {
|
||||||
url: parent.avatarUrl.replace("mxc://", "image://MxcImage/")
|
Layout.alignment: Qt.AlignHCenter
|
||||||
roomid: parent.roomId
|
|
||||||
displayName: parent.roomName
|
displayName: parent.roomName
|
||||||
height: 130
|
|
||||||
width: 130
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
enabled: false
|
enabled: false
|
||||||
|
height: 130
|
||||||
|
roomid: parent.roomId
|
||||||
|
url: parent.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||||
|
width: 130
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
spacing: Nheko.paddingMedium
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
spacing: Nheko.paddingMedium
|
||||||
|
|
||||||
MatrixText {
|
MatrixText {
|
||||||
text: !(roomPreview?.isFetched ?? false) ? qsTr("No preview available") : preview.roomName
|
|
||||||
font.pixelSize: 24
|
font.pixelSize: 24
|
||||||
|
text: !(roomPreview?.isFetched ?? false) ? qsTr("No preview available") : preview.roomName
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
|
ToolTip.text: qsTr("Settings")
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
hoverEnabled: true
|
||||||
image: ":/icons/icons/ui/settings.svg"
|
image: ":/icons/icons/ui/settings.svg"
|
||||||
visible: !!room
|
visible: !!room
|
||||||
hoverEnabled: true
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.text: qsTr("Settings")
|
|
||||||
onClicked: TimelineManager.openRoomSettings(room.roomId)
|
onClicked: TimelineManager.openRoomSettings(room.roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
visible: !!room
|
|
||||||
spacing: Nheko.paddingMedium
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
spacing: Nheko.paddingMedium
|
||||||
|
visible: !!room
|
||||||
|
|
||||||
MatrixText {
|
MatrixText {
|
||||||
text: qsTr("%n member(s)", "", room ? room.roomMemberCount : 0)
|
text: qsTr("%n member(s)", "", room ? room.roomMemberCount : 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
image: ":/icons/icons/ui/people.svg"
|
|
||||||
hoverEnabled: true
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.text: qsTr("View members of %1").arg(room ? room.roomName : "")
|
ToolTip.text: qsTr("View members of %1").arg(room ? room.roomName : "")
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
hoverEnabled: true
|
||||||
|
image: ":/icons/icons/ui/people.svg"
|
||||||
|
|
||||||
onClicked: TimelineManager.openRoomMembers(room)
|
onClicked: TimelineManager.openRoomMembers(room)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
@ -262,54 +239,53 @@ Item {
|
||||||
Layout.rightMargin: Nheko.paddingLarge
|
Layout.rightMargin: Nheko.paddingLarge
|
||||||
|
|
||||||
TextArea {
|
TextArea {
|
||||||
text: (roomPreview?.isFetched ?? false) ? TimelineManager.escapeEmoji(preview.roomTopic) : qsTr("This room is possibly inaccessible. If this room is private, you should remove it from this community.")
|
|
||||||
wrapMode: TextEdit.WordWrap
|
|
||||||
textFormat: TextEdit.RichText
|
|
||||||
readOnly: true
|
|
||||||
background: null
|
background: null
|
||||||
selectByMouse: true
|
|
||||||
horizontalAlignment: TextEdit.AlignHCenter
|
horizontalAlignment: TextEdit.AlignHCenter
|
||||||
|
readOnly: true
|
||||||
|
selectByMouse: true
|
||||||
|
text: (roomPreview?.isFetched ?? false) ? TimelineManager.escapeEmoji(preview.roomTopic) : qsTr("This room is possibly inaccessible. If this room is private, you should remove it from this community.")
|
||||||
|
textFormat: TextEdit.RichText
|
||||||
|
wrapMode: TextEdit.WordWrap
|
||||||
|
|
||||||
onLinkActivated: Nheko.openLink(link)
|
onLinkActivated: Nheko.openLink(link)
|
||||||
|
|
||||||
CursorShape {
|
CursorShape {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FlatButton {
|
FlatButton {
|
||||||
visible: roomPreview && !roomPreview.isInvite
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: qsTr("join the conversation")
|
text: qsTr("join the conversation")
|
||||||
|
visible: roomPreview && !roomPreview.isInvite
|
||||||
|
|
||||||
onClicked: Rooms.joinPreview(roomPreview.roomid)
|
onClicked: Rooms.joinPreview(roomPreview.roomid)
|
||||||
}
|
}
|
||||||
|
|
||||||
FlatButton {
|
FlatButton {
|
||||||
visible: roomPreview && roomPreview.isInvite
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: qsTr("accept invite")
|
text: qsTr("accept invite")
|
||||||
|
visible: roomPreview && roomPreview.isInvite
|
||||||
|
|
||||||
onClicked: Rooms.acceptInvite(roomPreview.roomid)
|
onClicked: Rooms.acceptInvite(roomPreview.roomid)
|
||||||
}
|
}
|
||||||
|
|
||||||
FlatButton {
|
FlatButton {
|
||||||
visible: roomPreview && roomPreview.isInvite
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: qsTr("decline invite")
|
text: qsTr("decline invite")
|
||||||
|
visible: roomPreview && roomPreview.isInvite
|
||||||
|
|
||||||
onClicked: Rooms.declineInvite(roomPreview.roomid)
|
onClicked: Rooms.declineInvite(roomPreview.roomid)
|
||||||
}
|
}
|
||||||
|
|
||||||
FlatButton {
|
FlatButton {
|
||||||
visible: !!room
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
text: qsTr("leave")
|
text: qsTr("leave")
|
||||||
|
visible: !!room
|
||||||
|
|
||||||
onClicked: TimelineManager.openLeaveRoomDialog(room.roomId)
|
onClicked: TimelineManager.openLeaveRoomDialog(room.roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
id: reasonField
|
id: reasonField
|
||||||
|
|
||||||
property bool showReason: false
|
property bool showReason: false
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
@ -319,17 +295,15 @@ Item {
|
||||||
visible: preview.reason !== "" && showReason
|
visible: preview.reason !== "" && showReason
|
||||||
|
|
||||||
TextArea {
|
TextArea {
|
||||||
text: TimelineManager.escapeEmoji(preview.reason)
|
|
||||||
wrapMode: TextEdit.WordWrap
|
|
||||||
textFormat: TextEdit.RichText
|
|
||||||
readOnly: true
|
|
||||||
background: null
|
background: null
|
||||||
selectByMouse: true
|
|
||||||
horizontalAlignment: TextEdit.AlignHCenter
|
horizontalAlignment: TextEdit.AlignHCenter
|
||||||
|
readOnly: true
|
||||||
|
selectByMouse: true
|
||||||
|
text: TimelineManager.escapeEmoji(preview.reason)
|
||||||
|
textFormat: TextEdit.RichText
|
||||||
|
wrapMode: TextEdit.WordWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
id: showReasonButton
|
id: showReasonButton
|
||||||
|
|
||||||
|
@ -337,76 +311,94 @@ Item {
|
||||||
//Layout.fillWidth: true
|
//Layout.fillWidth: true
|
||||||
Layout.leftMargin: Nheko.paddingLarge
|
Layout.leftMargin: Nheko.paddingLarge
|
||||||
Layout.rightMargin: Nheko.paddingLarge
|
Layout.rightMargin: Nheko.paddingLarge
|
||||||
|
|
||||||
visible: preview.reason !== ""
|
|
||||||
text: reasonField.showReason ? qsTr("Hide invite reason") : qsTr("Show invite reason")
|
text: reasonField.showReason ? qsTr("Hide invite reason") : qsTr("Show invite reason")
|
||||||
|
visible: preview.reason !== ""
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
reasonField.showReason = !reasonField.showReason;
|
reasonField.showReason = !reasonField.showReason;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
visible: room != null
|
|
||||||
Layout.preferredHeight: Math.ceil(fontMetrics.lineSpacing * 2)
|
Layout.preferredHeight: Math.ceil(fontMetrics.lineSpacing * 2)
|
||||||
|
visible: room != null
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
id: backToRoomsButton
|
id: backToRoomsButton
|
||||||
|
|
||||||
anchors.top: parent.top
|
ToolTip.text: qsTr("Back to room list")
|
||||||
|
ToolTip.visible: hovered
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.margins: Nheko.paddingMedium
|
anchors.margins: Nheko.paddingMedium
|
||||||
width: Nheko.avatarSize
|
anchors.top: parent.top
|
||||||
height: Nheko.avatarSize
|
|
||||||
visible: (room == null || room.isSpace) && showBackButton
|
|
||||||
enabled: visible
|
enabled: visible
|
||||||
|
height: Nheko.avatarSize
|
||||||
image: ":/icons/icons/ui/angle-arrow-left.svg"
|
image: ":/icons/icons/ui/angle-arrow-left.svg"
|
||||||
ToolTip.visible: hovered
|
visible: (room == null || room.isSpace) && showBackButton
|
||||||
ToolTip.text: qsTr("Back to room list")
|
width: Nheko.avatarSize
|
||||||
|
|
||||||
onClicked: Rooms.resetCurrentRoom()
|
onClicked: Rooms.resetCurrentRoom()
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelineEffects {
|
TimelineEffects {
|
||||||
id: timelineEffects
|
id: timelineEffects
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
NhekoDropArea {
|
NhekoDropArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
roomid: room ? room.roomId : ""
|
roomid: room ? room.roomId : ""
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: effectsTimer
|
id: effectsTimer
|
||||||
onTriggered: shouldEffectsRun = false;
|
|
||||||
interval: timelineEffects.maxLifespan
|
interval: timelineEffects.maxLifespan
|
||||||
repeat: false
|
repeat: false
|
||||||
running: false
|
running: false
|
||||||
}
|
|
||||||
|
|
||||||
|
onTriggered: shouldEffectsRun = false
|
||||||
|
}
|
||||||
Connections {
|
Connections {
|
||||||
|
function onConfetti() {
|
||||||
|
if (!Settings.fancyEffects)
|
||||||
|
return;
|
||||||
|
shouldEffectsRun = true;
|
||||||
|
timelineEffects.pulseConfetti();
|
||||||
|
room.markSpecialEffectsDone();
|
||||||
|
}
|
||||||
|
function onConfettiDone() {
|
||||||
|
if (!Settings.fancyEffects)
|
||||||
|
return;
|
||||||
|
effectsTimer.restart();
|
||||||
|
}
|
||||||
function onOpenReadReceiptsDialog(rr) {
|
function onOpenReadReceiptsDialog(rr) {
|
||||||
var dialog = readReceiptsDialog.createObject(timelineRoot, {
|
var dialog = readReceiptsDialog.createObject(timelineRoot, {
|
||||||
"readReceipts": rr,
|
"readReceipts": rr,
|
||||||
"room": room
|
"room": room
|
||||||
});
|
});
|
||||||
dialog.show();
|
dialog.show();
|
||||||
timelineRoot.destroyOnClose(dialog);
|
timelineRoot.destroyOnClose(dialog);
|
||||||
}
|
}
|
||||||
|
function onRainfall() {
|
||||||
|
if (!Settings.fancyEffects)
|
||||||
|
return;
|
||||||
|
shouldEffectsRun = true;
|
||||||
|
timelineEffects.pulseRainfall();
|
||||||
|
room.markSpecialEffectsDone();
|
||||||
|
}
|
||||||
|
function onRainfallDone() {
|
||||||
|
if (!Settings.fancyEffects)
|
||||||
|
return;
|
||||||
|
effectsTimer.restart();
|
||||||
|
}
|
||||||
function onShowRawMessageDialog(rawMessage) {
|
function onShowRawMessageDialog(rawMessage) {
|
||||||
var component = Qt.createComponent("qrc:/qml/dialogs/RawMessageDialog.qml")
|
var component = Qt.createComponent("qrc:/qml/dialogs/RawMessageDialog.qml");
|
||||||
if (component.status == Component.Ready) {
|
if (component.status == Component.Ready) {
|
||||||
var dialog = component.createObject(timelineRoot, {
|
var dialog = component.createObject(timelineRoot, {
|
||||||
"rawMessage": rawMessage
|
"rawMessage": rawMessage
|
||||||
});
|
});
|
||||||
dialog.show();
|
dialog.show();
|
||||||
timelineRoot.destroyOnClose(dialog);
|
timelineRoot.destroyOnClose(dialog);
|
||||||
} else {
|
} else {
|
||||||
|
@ -414,43 +406,6 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onConfetti()
|
|
||||||
{
|
|
||||||
if (!Settings.fancyEffects)
|
|
||||||
return
|
|
||||||
|
|
||||||
shouldEffectsRun = true;
|
|
||||||
timelineEffects.pulseConfetti()
|
|
||||||
room.markSpecialEffectsDone()
|
|
||||||
}
|
|
||||||
|
|
||||||
function onConfettiDone()
|
|
||||||
{
|
|
||||||
if (!Settings.fancyEffects)
|
|
||||||
return
|
|
||||||
|
|
||||||
effectsTimer.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onRainfall()
|
|
||||||
{
|
|
||||||
if (!Settings.fancyEffects)
|
|
||||||
return
|
|
||||||
|
|
||||||
shouldEffectsRun = true;
|
|
||||||
timelineEffects.pulseRainfall()
|
|
||||||
room.markSpecialEffectsDone()
|
|
||||||
}
|
|
||||||
|
|
||||||
function onRainfallDone()
|
|
||||||
{
|
|
||||||
if (!Settings.fancyEffects)
|
|
||||||
return
|
|
||||||
|
|
||||||
effectsTimer.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
target: room
|
target: room
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,17 +11,44 @@ Switch {
|
||||||
id: toggleButton
|
id: toggleButton
|
||||||
|
|
||||||
implicitWidth: indicatorItem.width
|
implicitWidth: indicatorItem.width
|
||||||
|
|
||||||
state: checked ? "on" : "off"
|
state: checked ? "on" : "off"
|
||||||
|
|
||||||
|
indicator: Item {
|
||||||
|
id: indicatorItem
|
||||||
|
|
||||||
|
implicitHeight: 24
|
||||||
|
implicitWidth: 48
|
||||||
|
y: parent.height / 2 - height / 2
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: track
|
||||||
|
|
||||||
|
color: Qt.rgba(border.color.r, border.color.g, border.color.b, 0.6)
|
||||||
|
height: parent.height * 0.6
|
||||||
|
radius: height / 2
|
||||||
|
width: parent.width - height
|
||||||
|
x: radius
|
||||||
|
y: parent.height / 2 - height / 2
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
id: handle
|
||||||
|
|
||||||
|
border.color: "#767676"
|
||||||
|
color: palette.button
|
||||||
|
height: width
|
||||||
|
radius: width / 2
|
||||||
|
width: parent.height * 0.9
|
||||||
|
y: parent.height / 2 - height / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
states: [
|
states: [
|
||||||
State {
|
State {
|
||||||
name: "off"
|
name: "off"
|
||||||
|
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: track
|
|
||||||
border.color: "#767676"
|
border.color: "#767676"
|
||||||
|
target: track
|
||||||
}
|
}
|
||||||
|
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: handle
|
target: handle
|
||||||
x: 0
|
x: 0
|
||||||
|
@ -31,10 +58,9 @@ Switch {
|
||||||
name: "on"
|
name: "on"
|
||||||
|
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: track
|
|
||||||
border.color: palette.highlight
|
border.color: palette.highlight
|
||||||
|
target: track
|
||||||
}
|
}
|
||||||
|
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: handle
|
target: handle
|
||||||
x: indicatorItem.width - handle.width
|
x: indicatorItem.width - handle.width
|
||||||
|
@ -43,55 +69,22 @@ Switch {
|
||||||
]
|
]
|
||||||
transitions: [
|
transitions: [
|
||||||
Transition {
|
Transition {
|
||||||
to: "off"
|
|
||||||
reversible: true
|
reversible: true
|
||||||
|
to: "off"
|
||||||
|
|
||||||
ParallelAnimation {
|
ParallelAnimation {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: handle
|
|
||||||
property: "x"
|
|
||||||
duration: 200
|
duration: 200
|
||||||
easing.type: Easing.InOutQuad
|
easing.type: Easing.InOutQuad
|
||||||
|
property: "x"
|
||||||
|
target: handle
|
||||||
}
|
}
|
||||||
|
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
target: track
|
|
||||||
properties: "color,border.color"
|
|
||||||
duration: 200
|
duration: 200
|
||||||
|
properties: "color,border.color"
|
||||||
|
target: track
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
indicator: Item {
|
|
||||||
id: indicatorItem
|
|
||||||
|
|
||||||
implicitWidth: 48
|
|
||||||
implicitHeight: 24
|
|
||||||
y: parent.height / 2 - height / 2
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: track
|
|
||||||
|
|
||||||
height: parent.height * 0.6
|
|
||||||
radius: height / 2
|
|
||||||
width: parent.width - height
|
|
||||||
x: radius
|
|
||||||
y: parent.height / 2 - height / 2
|
|
||||||
color: Qt.rgba(border.color.r, border.color.g, border.color.b, 0.6)
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: handle
|
|
||||||
|
|
||||||
y: parent.height / 2 - height / 2
|
|
||||||
width: parent.height * 0.9
|
|
||||||
height: width
|
|
||||||
radius: width / 2
|
|
||||||
color: palette.button
|
|
||||||
border.color: "#767676"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,212 +8,142 @@ import QtQuick.Controls 2.15
|
||||||
import QtQuick.Layouts 1.2
|
import QtQuick.Layouts 1.2
|
||||||
import QtQuick.Window 2.15
|
import QtQuick.Window 2.15
|
||||||
import im.nheko 1.0
|
import im.nheko 1.0
|
||||||
|
|
||||||
import "./delegates"
|
import "./delegates"
|
||||||
|
|
||||||
Pane {
|
Pane {
|
||||||
id: topBar
|
id: topBar
|
||||||
|
|
||||||
property bool showBackButton: false
|
|
||||||
property string roomName: room ? room.roomName : qsTr("No room selected")
|
|
||||||
property string roomId: room ? room.roomId : ""
|
|
||||||
property string avatarUrl: room ? room.roomAvatarUrl : ""
|
property string avatarUrl: room ? room.roomAvatarUrl : ""
|
||||||
property string roomTopic: room ? room.roomTopic : ""
|
|
||||||
property bool isEncrypted: room ? room.isEncrypted : false
|
|
||||||
property int trustlevel: room ? room.trustlevel : Crypto.Unverified
|
|
||||||
property bool isDirect: room ? room.isDirect : false
|
|
||||||
property string directChatOtherUserId: room ? room.directChatOtherUserId : ""
|
property string directChatOtherUserId: room ? room.directChatOtherUserId : ""
|
||||||
|
property bool isDirect: room ? room.isDirect : false
|
||||||
|
property bool isEncrypted: room ? room.isEncrypted : false
|
||||||
|
property string roomId: room ? room.roomId : ""
|
||||||
|
property string roomName: room ? room.roomName : qsTr("No room selected")
|
||||||
|
property string roomTopic: room ? room.roomTopic : ""
|
||||||
property bool searchHasFocus: searchField.focus && searchField.enabled
|
property bool searchHasFocus: searchField.focus && searchField.enabled
|
||||||
|
|
||||||
property string searchString: ""
|
property string searchString: ""
|
||||||
|
property bool showBackButton: false
|
||||||
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
|
property int trustlevel: room ? room.trustlevel : Crypto.Unverified
|
||||||
Connections {
|
|
||||||
function onHideMenu() {
|
|
||||||
roomOptionsMenu.close()
|
|
||||||
}
|
|
||||||
target: MainWindow
|
|
||||||
}
|
|
||||||
|
|
||||||
onRoomIdChanged: {
|
|
||||||
searchString = "";
|
|
||||||
searchButton.searchActive = false;
|
|
||||||
searchField.text = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: StandardKey.Find
|
|
||||||
onActivated: searchButton.searchActive = !searchButton.searchActive
|
|
||||||
}
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
implicitHeight: topLayout.height + Nheko.paddingMedium * 2
|
implicitHeight: topLayout.height + Nheko.paddingMedium * 2
|
||||||
|
padding: 0
|
||||||
z: 3
|
z: 3
|
||||||
|
|
||||||
padding: 0
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: palette.window
|
color: palette.window
|
||||||
}
|
}
|
||||||
|
|
||||||
TapHandler {
|
|
||||||
onSingleTapped: {
|
|
||||||
if (eventPoint.position.y > topBar.height - (pinnedMessages.visible ? pinnedMessages.height : 0) - (widgets.visible ? widgets.height : 0)) {
|
|
||||||
eventPoint.accepted = true
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (showBackButton && eventPoint.position.x < Nheko.paddingMedium + backToRoomsButton.width) {
|
|
||||||
eventPoint.accepted = true
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (eventPoint.position.x > topBar.width - Nheko.paddingMedium - roomOptionsButton.width) {
|
|
||||||
eventPoint.accepted = true
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (communityLabel.visible && eventPoint.position.y < communityAvatar.height + Nheko.paddingMedium + Nheko.paddingSmall/2) {
|
|
||||||
if (!Communities.trySwitchToSpace(room.parentSpace.roomid))
|
|
||||||
room.parentSpace.promptJoin();
|
|
||||||
eventPoint.accepted = true
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (room) {
|
|
||||||
let p = topBar.mapToItem(roomTopicC, eventPoint.position.x, eventPoint.position.y);
|
|
||||||
let link = roomTopicC.linkAt(p.x, p.y);
|
|
||||||
|
|
||||||
if (link) {
|
|
||||||
Nheko.openLink(link);
|
|
||||||
} else {
|
|
||||||
TimelineManager.openRoomSettings(room.roomId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eventPoint.accepted = true;
|
|
||||||
}
|
|
||||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
|
||||||
}
|
|
||||||
|
|
||||||
HoverHandler {
|
|
||||||
grabPermissions: PointerHandler.TakeOverForbidden | PointerHandler.CanTakeOverFromAnything
|
|
||||||
}
|
|
||||||
|
|
||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
GridLayout {
|
GridLayout {
|
||||||
id: topLayout
|
id: topLayout
|
||||||
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.margins: Nheko.paddingMedium
|
anchors.margins: Nheko.paddingMedium
|
||||||
|
anchors.right: parent.right
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
columnSpacing: Nheko.paddingSmall
|
columnSpacing: Nheko.paddingSmall
|
||||||
rowSpacing: Nheko.paddingSmall
|
rowSpacing: Nheko.paddingSmall
|
||||||
|
|
||||||
|
|
||||||
Avatar {
|
Avatar {
|
||||||
id: communityAvatar
|
id: communityAvatar
|
||||||
|
|
||||||
visible: roomid && room.parentSpace.isLoaded && ("space:"+room.parentSpace.roomid != Communities.currentTagId)
|
|
||||||
|
|
||||||
property string avatarUrl: (Settings.groupView && room && room.parentSpace && room.parentSpace.roomAvatarUrl) || ""
|
property string avatarUrl: (Settings.groupView && room && room.parentSpace && room.parentSpace.roomAvatarUrl) || ""
|
||||||
property string communityId: (Settings.groupView && room && room.parentSpace && room.parentSpace.roomid) || ""
|
property string communityId: (Settings.groupView && room && room.parentSpace && room.parentSpace.roomid) || ""
|
||||||
property string communityName: (Settings.groupView && room && room.parentSpace && room.parentSpace.roomName) || ""
|
property string communityName: (Settings.groupView && room && room.parentSpace && room.parentSpace.roomName) || ""
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
Layout.column: 1
|
Layout.column: 1
|
||||||
Layout.row: 0
|
Layout.row: 0
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
width: fontMetrics.lineSpacing
|
|
||||||
height: fontMetrics.lineSpacing
|
|
||||||
url: avatarUrl.replace("mxc://", "image://MxcImage/")
|
|
||||||
roomid: communityId
|
|
||||||
displayName: communityName
|
displayName: communityName
|
||||||
enabled: false
|
enabled: false
|
||||||
|
height: fontMetrics.lineSpacing
|
||||||
|
roomid: communityId
|
||||||
|
url: avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||||
|
visible: roomid && room.parentSpace.isLoaded && ("space:" + room.parentSpace.roomid != Communities.currentTagId)
|
||||||
|
width: fontMetrics.lineSpacing
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: communityLabel
|
id: communityLabel
|
||||||
visible: communityAvatar.visible
|
|
||||||
|
|
||||||
Layout.column: 2
|
Layout.column: 2
|
||||||
Layout.row: 0
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.row: 0
|
||||||
color: palette.text
|
color: palette.text
|
||||||
text: qsTr("In %1").arg(communityAvatar.displayName)
|
|
||||||
maximumLineCount: 1
|
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
text: qsTr("In %1").arg(communityAvatar.displayName)
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
|
visible: communityAvatar.visible
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
id: backToRoomsButton
|
id: backToRoomsButton
|
||||||
|
|
||||||
Layout.column: 0
|
|
||||||
Layout.row: 1
|
|
||||||
Layout.rowSpan: 2
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.column: 0
|
||||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||||
visible: showBackButton
|
Layout.row: 1
|
||||||
image: ":/icons/icons/ui/angle-arrow-left.svg"
|
Layout.rowSpan: 2
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.text: qsTr("Back to room list")
|
ToolTip.text: qsTr("Back to room list")
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
image: ":/icons/icons/ui/angle-arrow-left.svg"
|
||||||
|
visible: showBackButton
|
||||||
|
|
||||||
onClicked: Rooms.resetCurrentRoom()
|
onClicked: Rooms.resetCurrentRoom()
|
||||||
}
|
}
|
||||||
|
|
||||||
Avatar {
|
Avatar {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
Layout.column: 1
|
Layout.column: 1
|
||||||
Layout.row: 1
|
Layout.row: 1
|
||||||
Layout.rowSpan: 2
|
Layout.rowSpan: 2
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
width: Nheko.avatarSize
|
|
||||||
height: Nheko.avatarSize
|
|
||||||
url: avatarUrl.replace("mxc://", "image://MxcImage/")
|
|
||||||
roomid: roomId
|
|
||||||
userid: isDirect ? directChatOtherUserId : ""
|
|
||||||
displayName: roomName
|
displayName: roomName
|
||||||
enabled: false
|
enabled: false
|
||||||
|
height: Nheko.avatarSize
|
||||||
|
roomid: roomId
|
||||||
|
url: avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||||
|
userid: isDirect ? directChatOtherUserId : ""
|
||||||
|
width: Nheko.avatarSize
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.column: 2
|
Layout.column: 2
|
||||||
|
Layout.fillWidth: true
|
||||||
Layout.row: 1
|
Layout.row: 1
|
||||||
color: palette.text
|
color: palette.text
|
||||||
font.pointSize: fontMetrics.font.pointSize * 1.1
|
|
||||||
font.bold: true
|
|
||||||
text: roomName
|
|
||||||
maximumLineCount: 1
|
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
|
font.bold: true
|
||||||
|
font.pointSize: fontMetrics.font.pointSize * 1.1
|
||||||
|
maximumLineCount: 1
|
||||||
|
text: roomName
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixText {
|
MatrixText {
|
||||||
id: roomTopicC
|
id: roomTopicC
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.column: 2
|
Layout.column: 2
|
||||||
Layout.row: 2
|
Layout.fillWidth: true
|
||||||
Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
|
Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
|
||||||
selectByMouse: false
|
Layout.row: 2
|
||||||
enabled: false
|
|
||||||
clip: true
|
clip: true
|
||||||
|
enabled: false
|
||||||
|
selectByMouse: false
|
||||||
text: roomTopic
|
text: roomTopic
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
id: pinButton
|
id: pinButton
|
||||||
|
|
||||||
property bool pinsShown: !Settings.hiddenPins.includes(roomId)
|
property bool pinsShown: !Settings.hiddenPins.includes(roomId)
|
||||||
|
|
||||||
visible: !!room && room.pinnedMessages.length > 0
|
|
||||||
Layout.column: 3
|
|
||||||
Layout.row: 1
|
|
||||||
Layout.rowSpan: 2
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.column: 3
|
||||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||||
image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg"
|
Layout.row: 1
|
||||||
ToolTip.visible: hovered
|
Layout.rowSpan: 2
|
||||||
ToolTip.text: qsTr("Show or hide pinned messages")
|
ToolTip.text: qsTr("Show or hide pinned messages")
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg"
|
||||||
|
visible: !!room && room.pinnedMessages.length > 0
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
var ps = Settings.hiddenPins;
|
var ps = Settings.hiddenPins;
|
||||||
if (pinsShown) {
|
if (pinsShown) {
|
||||||
|
@ -226,242 +156,280 @@ Pane {
|
||||||
}
|
}
|
||||||
Settings.hiddenPins = ps;
|
Settings.hiddenPins = ps;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AbstractButton {
|
AbstractButton {
|
||||||
Layout.column: 4
|
Layout.column: 4
|
||||||
Layout.row: 1
|
|
||||||
Layout.rowSpan: 2
|
|
||||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||||
|
Layout.row: 1
|
||||||
|
Layout.rowSpan: 2
|
||||||
|
background: null
|
||||||
|
|
||||||
contentItem: EncryptionIndicator {
|
contentItem: EncryptionIndicator {
|
||||||
encrypted: isEncrypted
|
|
||||||
trust: trustlevel
|
|
||||||
enabled: false
|
|
||||||
unencryptedIcon: ":/icons/icons/ui/people.svg"
|
|
||||||
unencryptedColor: palette.buttonText
|
|
||||||
unencryptedHoverColor: palette.highlight
|
|
||||||
hovered: parent.hovered
|
|
||||||
|
|
||||||
ToolTip.delay: Nheko.tooltipDelay
|
ToolTip.delay: Nheko.tooltipDelay
|
||||||
ToolTip.text: {
|
ToolTip.text: {
|
||||||
if (!isEncrypted)
|
if (!isEncrypted)
|
||||||
return qsTr("Show room members.");
|
return qsTr("Show room members.");
|
||||||
|
|
||||||
switch (trustlevel) {
|
switch (trustlevel) {
|
||||||
case Crypto.Verified:
|
case Crypto.Verified:
|
||||||
return qsTr("This room contains only verified devices.");
|
return qsTr("This room contains only verified devices.");
|
||||||
case Crypto.TOFU:
|
case Crypto.TOFU:
|
||||||
return qsTr("This room contains verified devices and devices which have never changed their master key.");
|
return qsTr("This room contains verified devices and devices which have never changed their master key.");
|
||||||
default:
|
default:
|
||||||
return qsTr("This room contains unverified devices!");
|
return qsTr("This room contains unverified devices!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
enabled: false
|
||||||
|
encrypted: isEncrypted
|
||||||
|
hovered: parent.hovered
|
||||||
|
trust: trustlevel
|
||||||
|
unencryptedColor: palette.buttonText
|
||||||
|
unencryptedHoverColor: palette.highlight
|
||||||
|
unencryptedIcon: ":/icons/icons/ui/people.svg"
|
||||||
}
|
}
|
||||||
|
|
||||||
background: null
|
|
||||||
onClicked: TimelineManager.openRoomMembers(room)
|
onClicked: TimelineManager.openRoomMembers(room)
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
id: searchButton
|
id: searchButton
|
||||||
|
|
||||||
property bool searchActive: false
|
property bool searchActive: false
|
||||||
|
|
||||||
visible: !!room
|
|
||||||
Layout.column: 5
|
|
||||||
Layout.row: 1
|
|
||||||
Layout.rowSpan: 2
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.column: 5
|
||||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||||
image: ":/icons/icons/ui/search.svg"
|
Layout.row: 1
|
||||||
ToolTip.visible: hovered
|
Layout.rowSpan: 2
|
||||||
ToolTip.text: qsTr("Search this room")
|
ToolTip.text: qsTr("Search this room")
|
||||||
onClicked: searchActive = !searchActive
|
ToolTip.visible: hovered
|
||||||
|
image: ":/icons/icons/ui/search.svg"
|
||||||
|
visible: !!room
|
||||||
|
|
||||||
|
onClicked: searchActive = !searchActive
|
||||||
onSearchActiveChanged: {
|
onSearchActiveChanged: {
|
||||||
if (searchActive) {
|
if (searchActive) {
|
||||||
searchField.forceActiveFocus();
|
searchField.forceActiveFocus();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
searchField.clear();
|
searchField.clear();
|
||||||
topBar.searchString = "";
|
topBar.searchString = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
id: roomOptionsButton
|
id: roomOptionsButton
|
||||||
|
|
||||||
visible: !!room
|
|
||||||
Layout.column: 6
|
|
||||||
Layout.row: 1
|
|
||||||
Layout.rowSpan: 2
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.column: 6
|
||||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||||
image: ":/icons/icons/ui/options.svg"
|
Layout.row: 1
|
||||||
ToolTip.visible: hovered
|
Layout.rowSpan: 2
|
||||||
ToolTip.text: qsTr("Room options")
|
ToolTip.text: qsTr("Room options")
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
image: ":/icons/icons/ui/options.svg"
|
||||||
|
visible: !!room
|
||||||
|
|
||||||
onClicked: roomOptionsMenu.open(roomOptionsButton)
|
onClicked: roomOptionsMenu.open(roomOptionsButton)
|
||||||
|
|
||||||
Platform.Menu {
|
Platform.Menu {
|
||||||
id: roomOptionsMenu
|
id: roomOptionsMenu
|
||||||
|
|
||||||
Platform.MenuItem {
|
Platform.MenuItem {
|
||||||
visible: room ? room.permissions.canInvite() : false
|
|
||||||
text: qsTr("Invite users")
|
text: qsTr("Invite users")
|
||||||
|
visible: room ? room.permissions.canInvite() : false
|
||||||
|
|
||||||
onTriggered: TimelineManager.openInviteUsers(roomId)
|
onTriggered: TimelineManager.openInviteUsers(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
Platform.MenuItem {
|
Platform.MenuItem {
|
||||||
text: qsTr("Members")
|
text: qsTr("Members")
|
||||||
|
|
||||||
onTriggered: TimelineManager.openRoomMembers(room)
|
onTriggered: TimelineManager.openRoomMembers(room)
|
||||||
}
|
}
|
||||||
|
|
||||||
Platform.MenuItem {
|
Platform.MenuItem {
|
||||||
text: qsTr("Leave room")
|
text: qsTr("Leave room")
|
||||||
|
|
||||||
onTriggered: TimelineManager.openLeaveRoomDialog(roomId)
|
onTriggered: TimelineManager.openLeaveRoomDialog(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
Platform.MenuItem {
|
Platform.MenuItem {
|
||||||
text: qsTr("Settings")
|
text: qsTr("Settings")
|
||||||
|
|
||||||
onTriggered: TimelineManager.openRoomSettings(roomId)
|
onTriggered: TimelineManager.openRoomSettings(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
id: pinnedMessages
|
id: pinnedMessages
|
||||||
|
|
||||||
Layout.row: 3
|
|
||||||
Layout.column: 2
|
Layout.column: 2
|
||||||
Layout.columnSpan: 4
|
Layout.columnSpan: 4
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4)
|
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4)
|
||||||
|
Layout.row: 3
|
||||||
visible: !!room && room.pinnedMessages.length > 0 && !Settings.hiddenPins.includes(roomId)
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
ScrollBar.horizontal.visible: false
|
ScrollBar.horizontal.visible: false
|
||||||
|
clip: true
|
||||||
|
visible: !!room && room.pinnedMessages.length > 0 && !Settings.hiddenPins.includes(roomId)
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
|
|
||||||
spacing: Nheko.paddingSmall
|
|
||||||
model: room ? room.pinnedMessages : undefined
|
model: room ? room.pinnedMessages : undefined
|
||||||
|
spacing: Nheko.paddingSmall
|
||||||
|
|
||||||
delegate: RowLayout {
|
delegate: RowLayout {
|
||||||
required property string modelData
|
required property string modelData
|
||||||
|
|
||||||
width: ListView.view.width
|
|
||||||
height: implicitHeight
|
height: implicitHeight
|
||||||
|
width: ListView.view.width
|
||||||
|
|
||||||
Reply {
|
Reply {
|
||||||
id: reply
|
id: reply
|
||||||
|
|
||||||
property var e: room ? room.getDump(modelData, "pins") : {}
|
property var e: room ? room.getDump(modelData, "pins") : {}
|
||||||
Connections {
|
|
||||||
function onPinnedMessagesChanged() { reply.e = room.getDump(modelData, "pins") }
|
|
||||||
target: room
|
|
||||||
}
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: height
|
Layout.preferredHeight: height
|
||||||
|
|
||||||
userColor: TimelineManager.userColor(e.userId, palette.window)
|
|
||||||
blurhash: e.blurhash ?? ""
|
blurhash: e.blurhash ?? ""
|
||||||
body: e.body ?? ""
|
body: e.body ?? ""
|
||||||
formattedBody: e.formattedBody ?? ""
|
encryptionError: e.encryptionError ?? 0
|
||||||
eventId: e.eventId ?? ""
|
eventId: e.eventId ?? ""
|
||||||
filename: e.filename ?? ""
|
filename: e.filename ?? ""
|
||||||
filesize: e.filesize ?? ""
|
filesize: e.filesize ?? ""
|
||||||
|
formattedBody: e.formattedBody ?? ""
|
||||||
|
isOnlyEmoji: e.isOnlyEmoji ?? false
|
||||||
|
keepFullText: true
|
||||||
|
originalWidth: e.originalWidth ?? 0
|
||||||
proportionalHeight: e.proportionalHeight ?? 1
|
proportionalHeight: e.proportionalHeight ?? 1
|
||||||
type: e.type ?? MtxEvent.UnknownMessage
|
type: e.type ?? MtxEvent.UnknownMessage
|
||||||
typeString: e.typeString ?? ""
|
typeString: e.typeString ?? ""
|
||||||
url: e.url ?? ""
|
url: e.url ?? ""
|
||||||
originalWidth: e.originalWidth ?? 0
|
userColor: TimelineManager.userColor(e.userId, palette.window)
|
||||||
isOnlyEmoji: e.isOnlyEmoji ?? false
|
|
||||||
userId: e.userId ?? ""
|
userId: e.userId ?? ""
|
||||||
userName: e.userName ?? ""
|
userName: e.userName ?? ""
|
||||||
encryptionError: e.encryptionError ?? 0
|
|
||||||
keepFullText: true
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onPinnedMessagesChanged() {
|
||||||
|
reply.e = room.getDump(modelData, "pins");
|
||||||
|
}
|
||||||
|
|
||||||
|
target: room
|
||||||
|
}
|
||||||
|
}
|
||||||
ImageButton {
|
ImageButton {
|
||||||
id: deletePinButton
|
id: deletePinButton
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
|
||||||
Layout.preferredHeight: 16
|
Layout.preferredHeight: 16
|
||||||
Layout.preferredWidth: 16
|
Layout.preferredWidth: 16
|
||||||
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
|
ToolTip.text: qsTr("Unpin")
|
||||||
visible: room.permissions.canChange(MtxEvent.PinnedEvents)
|
ToolTip.visible: hovered
|
||||||
|
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
image: ":/icons/icons/ui/dismiss.svg"
|
image: ":/icons/icons/ui/dismiss.svg"
|
||||||
ToolTip.visible: hovered
|
visible: room.permissions.canChange(MtxEvent.PinnedEvents)
|
||||||
ToolTip.text: qsTr("Unpin")
|
|
||||||
|
|
||||||
onClicked: room.unpin(modelData)
|
onClicked: room.unpin(modelData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
id: widgets
|
id: widgets
|
||||||
|
|
||||||
Layout.row: 4
|
|
||||||
Layout.column: 2
|
Layout.column: 2
|
||||||
Layout.columnSpan: 4
|
Layout.columnSpan: 4
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 1.5)
|
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 1.5)
|
||||||
|
Layout.row: 4
|
||||||
visible: !!room && room.widgetLinks.length > 0 && !Settings.hiddenWidgets.includes(roomId)
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
ScrollBar.horizontal.visible: false
|
ScrollBar.horizontal.visible: false
|
||||||
|
clip: true
|
||||||
|
visible: !!room && room.widgetLinks.length > 0 && !Settings.hiddenWidgets.includes(roomId)
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
|
|
||||||
spacing: Nheko.paddingSmall
|
|
||||||
model: room ? room.widgetLinks : undefined
|
model: room ? room.widgetLinks : undefined
|
||||||
|
spacing: Nheko.paddingSmall
|
||||||
|
|
||||||
delegate: MatrixText {
|
delegate: MatrixText {
|
||||||
required property var modelData
|
required property var modelData
|
||||||
|
|
||||||
color: palette.text
|
color: palette.text
|
||||||
text: modelData
|
text: modelData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixTextField {
|
MatrixTextField {
|
||||||
id: searchField
|
id: searchField
|
||||||
visible: searchButton.searchActive
|
|
||||||
enabled: visible
|
|
||||||
hasClear: true
|
|
||||||
|
|
||||||
Layout.row: 5
|
|
||||||
Layout.column: 2
|
Layout.column: 2
|
||||||
Layout.columnSpan: 4
|
Layout.columnSpan: 4
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.row: 5
|
||||||
|
enabled: visible
|
||||||
|
hasClear: true
|
||||||
placeholderText: qsTr("Enter search query")
|
placeholderText: qsTr("Enter search query")
|
||||||
|
visible: searchButton.searchActive
|
||||||
|
|
||||||
onAccepted: topBar.searchString = text
|
onAccepted: topBar.searchString = text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CursorShape {
|
CursorShape {
|
||||||
anchors.fill: parent
|
|
||||||
anchors.bottomMargin: (pinnedMessages.visible ? pinnedMessages.height : 0) + (widgets.visible ? widgets.height : 0)
|
anchors.bottomMargin: (pinnedMessages.visible ? pinnedMessages.height : 0) + (widgets.visible ? widgets.height : 0)
|
||||||
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onRoomIdChanged: {
|
||||||
|
searchString = "";
|
||||||
|
searchButton.searchActive = false;
|
||||||
|
searchField.text = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
|
||||||
|
Connections {
|
||||||
|
function onHideMenu() {
|
||||||
|
roomOptionsMenu.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
target: MainWindow
|
||||||
|
}
|
||||||
|
Shortcut {
|
||||||
|
sequence: StandardKey.Find
|
||||||
|
|
||||||
|
onActivated: searchButton.searchActive = !searchButton.searchActive
|
||||||
|
}
|
||||||
|
TapHandler {
|
||||||
|
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||||
|
|
||||||
|
onSingleTapped: {
|
||||||
|
if (eventPoint.position.y > topBar.height - (pinnedMessages.visible ? pinnedMessages.height : 0) - (widgets.visible ? widgets.height : 0)) {
|
||||||
|
eventPoint.accepted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (showBackButton && eventPoint.position.x < Nheko.paddingMedium + backToRoomsButton.width) {
|
||||||
|
eventPoint.accepted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (eventPoint.position.x > topBar.width - Nheko.paddingMedium - roomOptionsButton.width) {
|
||||||
|
eventPoint.accepted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (communityLabel.visible && eventPoint.position.y < communityAvatar.height + Nheko.paddingMedium + Nheko.paddingSmall / 2) {
|
||||||
|
if (!Communities.trySwitchToSpace(room.parentSpace.roomid))
|
||||||
|
room.parentSpace.promptJoin();
|
||||||
|
eventPoint.accepted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (room) {
|
||||||
|
let p = topBar.mapToItem(roomTopicC, eventPoint.position.x, eventPoint.position.y);
|
||||||
|
let link = roomTopicC.linkAt(p.x, p.y);
|
||||||
|
if (link) {
|
||||||
|
Nheko.openLink(link);
|
||||||
|
} else {
|
||||||
|
TimelineManager.openRoomSettings(room.roomId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eventPoint.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HoverHandler {
|
||||||
|
grabPermissions: PointerHandler.TakeOverForbidden | PointerHandler.CanTakeOverFromAnything
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,30 +8,28 @@ import QtQuick.Layouts 1.2
|
||||||
import im.nheko 1.0
|
import im.nheko 1.0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
implicitHeight: Math.max(fontMetrics.height * 1.2, typingDisplay.height)
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: Math.max(fontMetrics.height * 1.2, typingDisplay.height)
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: typingRect
|
id: typingRect
|
||||||
|
|
||||||
visible: (room && room.typingUsers.length > 0)
|
|
||||||
color: palette.base
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
color: palette.base
|
||||||
|
visible: (room && room.typingUsers.length > 0)
|
||||||
z: 3
|
z: 3
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: typingDisplay
|
id: typingDisplay
|
||||||
|
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: 10
|
anchors.leftMargin: 10
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 10
|
anchors.rightMargin: 10
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
color: palette.text
|
color: palette.text
|
||||||
text: room ? room.formatTypingUsers(room.typingUsers, palette.base) : ""
|
text: room ? room.formatTypingUsers(room.typingUsers, palette.base) : ""
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
import "./components"
|
import "./components"
|
||||||
import "./ui"
|
import "./ui"
|
||||||
|
|
||||||
import QtQuick 2.9
|
import QtQuick 2.9
|
||||||
import QtQuick.Controls 2.5
|
import QtQuick.Controls 2.5
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
|
@ -12,31 +11,33 @@ import im.nheko 1.0
|
||||||
|
|
||||||
Page {
|
Page {
|
||||||
id: uploadPopup
|
id: uploadPopup
|
||||||
visible: room && room.input.uploads.length > 0
|
|
||||||
Layout.preferredHeight: 200
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 200
|
||||||
|
clip: true
|
||||||
padding: Nheko.paddingMedium
|
padding: Nheko.paddingMedium
|
||||||
|
visible: room && room.input.uploads.length > 0
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: palette.base
|
||||||
|
}
|
||||||
contentItem: ListView {
|
contentItem: ListView {
|
||||||
id: uploadsList
|
id: uploadsList
|
||||||
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
model: room ? room.input.uploads : undefined
|
||||||
|
orientation: ListView.Horizontal
|
||||||
|
spacing: Nheko.paddingMedium
|
||||||
|
width: Math.min(contentWidth, parent.availableWidth)
|
||||||
|
|
||||||
ScrollBar.horizontal: ScrollBar {
|
ScrollBar.horizontal: ScrollBar {
|
||||||
id: scr
|
id: scr
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
orientation: ListView.Horizontal
|
|
||||||
width: Math.min(contentWidth, parent.availableWidth)
|
|
||||||
model: room ? room.input.uploads : undefined
|
|
||||||
spacing: Nheko.paddingMedium
|
|
||||||
|
|
||||||
delegate: Pane {
|
delegate: Pane {
|
||||||
|
height: uploadPopup.availableHeight - buttons.height - (scr.visible ? scr.height : 0)
|
||||||
padding: Nheko.paddingSmall
|
padding: Nheko.paddingSmall
|
||||||
height: uploadPopup.availableHeight - buttons.height - (scr.visible? scr.height : 0)
|
|
||||||
width: uploadPopup.availableHeight - buttons.height
|
width: uploadPopup.availableHeight - buttons.height
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
|
@ -45,46 +46,48 @@ Page {
|
||||||
}
|
}
|
||||||
contentItem: ColumnLayout {
|
contentItem: ColumnLayout {
|
||||||
Image {
|
Image {
|
||||||
|
property string typeStr: switch (modelData.mediaType) {
|
||||||
|
case MediaUpload.Video:
|
||||||
|
return "video-file";
|
||||||
|
case MediaUpload.Audio:
|
||||||
|
return "music";
|
||||||
|
case MediaUpload.Image:
|
||||||
|
return "image";
|
||||||
|
default:
|
||||||
|
return "zip";
|
||||||
|
}
|
||||||
|
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
mipmap: true
|
||||||
|
smooth: true
|
||||||
|
source: (modelData.thumbnail != "") ? modelData.thumbnail : ("image://colorimage/:/icons/icons/ui/" + typeStr + ".svg?" + palette.buttonText)
|
||||||
sourceSize.height: parent.availableHeight - namefield.height
|
sourceSize.height: parent.availableHeight - namefield.height
|
||||||
sourceSize.width: parent.availableWidth
|
sourceSize.width: parent.availableWidth
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
smooth: true
|
|
||||||
mipmap: true
|
|
||||||
|
|
||||||
property string typeStr: switch(modelData.mediaType) {
|
|
||||||
case MediaUpload.Video: return "video-file";
|
|
||||||
case MediaUpload.Audio: return "music";
|
|
||||||
case MediaUpload.Image: return "image";
|
|
||||||
default: return "zip";
|
|
||||||
}
|
|
||||||
source: (modelData.thumbnail != "") ? modelData.thumbnail : ("image://colorimage/:/icons/icons/ui/"+typeStr+".svg?" + palette.buttonText)
|
|
||||||
}
|
}
|
||||||
MatrixTextField {
|
MatrixTextField {
|
||||||
id: namefield
|
id: namefield
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: modelData.filename
|
text: modelData.filename
|
||||||
|
|
||||||
onTextEdited: modelData.filename = text
|
onTextEdited: modelData.filename = text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
footer: DialogButtonBox {
|
footer: DialogButtonBox {
|
||||||
id: buttons
|
id: buttons
|
||||||
|
|
||||||
standardButtons: DialogButtonBox.Cancel
|
standardButtons: DialogButtonBox.Cancel
|
||||||
Button {
|
|
||||||
text: qsTr("Upload %n file(s)", "", (room ? room.input.uploads.length : 0))
|
|
||||||
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
|
|
||||||
}
|
|
||||||
onAccepted: room.input.acceptUploads()
|
onAccepted: room.input.acceptUploads()
|
||||||
onRejected: room.input.declineUploads()
|
onRejected: room.input.declineUploads()
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
Button {
|
||||||
color: palette.base
|
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
|
||||||
|
text: qsTr("Upload %n file(s)", "", (room ? room.input.uploads.length : 0))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue