mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-21 10:40:47 +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 {
|
||||
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 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
|
||||
width: 48
|
||||
|
||||
background: Rectangle {
|
||||
id: bg
|
||||
radius: Settings.avatarCircles ? height / 2 : height / 8
|
||||
|
||||
color: palette.alternateBase
|
||||
radius: Settings.avatarCircles ? height / 2 : height / 8
|
||||
}
|
||||
|
||||
Label {
|
||||
id: label
|
||||
|
||||
enabled: false
|
||||
|
||||
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)) : "")
|
||||
textFormat: Text.RichText
|
||||
font.pixelSize: avatar.height / 2
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
visible: img.status != Image.Ready && !Settings.useIdenticon
|
||||
color: palette.text
|
||||
}
|
||||
|
||||
Image {
|
||||
id: identicon
|
||||
|
||||
anchors.fill: parent
|
||||
visible: Settings.useIdenticon && img.status != Image.Ready
|
||||
source: Settings.useIdenticon ? ("image://jdenticon/" + (userid !== "" ? userid : roomid) + "?radius=" + (Settings.avatarCircles ? 100 : 25)) : ""
|
||||
visible: Settings.useIdenticon && img.status != Image.Ready
|
||||
}
|
||||
|
||||
Image {
|
||||
id: img
|
||||
|
||||
|
@ -58,8 +57,6 @@ AbstractButton {
|
|||
fillMode: avatar.crop ? Image.PreserveAspectCrop : Image.PreserveAspectFit
|
||||
mipmap: true
|
||||
smooth: true
|
||||
sourceSize.width: avatar.width * Screen.devicePixelRatio
|
||||
sourceSize.height: avatar.height * Screen.devicePixelRatio
|
||||
source: if (avatar.url.startsWith('image://')) {
|
||||
return avatar.url + "?radius=" + (Settings.avatarCircles ? 100 : 25) + ((avatar.crop) ? "" : "&scale");
|
||||
} else if (avatar.url.startsWith(':/')) {
|
||||
|
@ -67,20 +64,12 @@ AbstractButton {
|
|||
} else {
|
||||
return "";
|
||||
}
|
||||
|
||||
sourceSize.height: avatar.height * Screen.devicePixelRatio
|
||||
sourceSize.width: avatar.width * Screen.devicePixelRatio
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
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() {
|
||||
switch (Presence.userPresence(userid)) {
|
||||
case "online":
|
||||
|
@ -94,22 +83,28 @@ AbstractButton {
|
|||
}
|
||||
}
|
||||
|
||||
anchors.bottom: avatar.bottom
|
||||
anchors.right: avatar.right
|
||||
color: updatePresence()
|
||||
height: avatar.height / 6
|
||||
radius: Settings.avatarCircles ? height / 2 : height / 8
|
||||
visible: !!userid
|
||||
width: height
|
||||
|
||||
Connections {
|
||||
target: Presence
|
||||
|
||||
function onPresenceChanged(id) {
|
||||
if (id == userid) onlineIndicator.color = onlineIndicator.updatePresence();
|
||||
}
|
||||
}
|
||||
if (id == userid)
|
||||
onlineIndicator.color = onlineIndicator.updatePresence();
|
||||
}
|
||||
|
||||
target: Presence
|
||||
}
|
||||
}
|
||||
CursorShape {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
Ripple {
|
||||
color: Qt.rgba(palette.alternateBase.r, palette.alternateBase.g, palette.alternateBase.b, 0.5)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,16 +17,16 @@ Rectangle {
|
|||
color: palette.window
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Rectangle {
|
||||
id: offlineIndicator
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: offlineLabel.height + Nheko.paddingMedium
|
||||
color: Nheko.theme.error
|
||||
visible: !TimelineManager.isConnected
|
||||
Layout.preferredHeight: offlineLabel.height + Nheko.paddingMedium
|
||||
Layout.fillWidth: true
|
||||
z: 1
|
||||
|
||||
Label {
|
||||
|
@ -36,18 +36,9 @@ Rectangle {
|
|||
text: qsTr("No network connection")
|
||||
}
|
||||
}
|
||||
|
||||
AdaptiveLayout {
|
||||
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() {
|
||||
if (!singlePageMode)
|
||||
adaptiveView.pageIndex = 0;
|
||||
|
@ -57,67 +48,67 @@ Rectangle {
|
|||
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 {
|
||||
target: Rooms
|
||||
function onCurrentRoomChanged() {
|
||||
adaptiveView.initializePageIndex();
|
||||
}
|
||||
}
|
||||
|
||||
target: Rooms
|
||||
}
|
||||
AdaptiveLayoutElement {
|
||||
id: communityListC
|
||||
|
||||
visible: Settings.groupView
|
||||
minimumWidth: communitiesList.avatarSize * 4 + Nheko.paddingMedium * 2
|
||||
collapsedWidth: communitiesList.avatarSize + 2 * Nheko.paddingMedium
|
||||
preferredWidth: Settings.communityListWidth >= minimumWidth ? Settings.communityListWidth : collapsedWidth
|
||||
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 {
|
||||
id: communitiesList
|
||||
|
||||
collapsed: parent.collapsed
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: Settings
|
||||
delayed: true
|
||||
property: 'communityListWidth'
|
||||
restoreMode: Binding.RestoreBindingOrValue
|
||||
target: Settings
|
||||
value: communityListC.preferredWidth
|
||||
when: !adaptiveView.singlePageMode
|
||||
delayed: true
|
||||
restoreMode: Binding.RestoreBindingOrValue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AdaptiveLayoutElement {
|
||||
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
|
||||
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 {
|
||||
id: roomlist
|
||||
|
||||
height: adaptiveView.height
|
||||
collapsed: parent.collapsed
|
||||
height: adaptiveView.height
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: Settings
|
||||
delayed: true
|
||||
property: 'roomListWidth'
|
||||
restoreMode: Binding.RestoreBindingOrValue
|
||||
target: Settings
|
||||
value: roomListC.preferredWidth
|
||||
when: !adaptiveView.singlePageMode
|
||||
delayed: true
|
||||
restoreMode: Binding.RestoreBindingOrValue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AdaptiveLayoutElement {
|
||||
id: timlineViewC
|
||||
|
||||
|
@ -127,25 +118,20 @@ Rectangle {
|
|||
id: timeline
|
||||
|
||||
privacyScreen: privacyScreen
|
||||
showBackButton: adaptiveView.singlePageMode
|
||||
room: Rooms.currentRoom
|
||||
roomPreview: Rooms.currentRoomPreview.roomid ? Rooms.currentRoomPreview : null
|
||||
showBackButton: adaptiveView.singlePageMode
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PrivacyScreen {
|
||||
id: privacyScreen
|
||||
|
||||
anchors.fill: parent
|
||||
visible: Settings.privacyScreen
|
||||
screenTimeout: Settings.privacyScreenTimeout
|
||||
timelineRoot: adaptiveView
|
||||
visible: Settings.privacyScreen
|
||||
windowTarget: MainWindow
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,19 +13,24 @@ import im.nheko 1.0
|
|||
|
||||
Page {
|
||||
id: communitySidebar
|
||||
|
||||
//leftPadding: Nheko.paddingSmall
|
||||
//rightPadding: Nheko.paddingSmall
|
||||
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 1.6)
|
||||
property bool collapsed: false
|
||||
|
||||
background: Rectangle {
|
||||
color: Nheko.theme.sidebarBackground
|
||||
}
|
||||
|
||||
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
|
||||
Connections {
|
||||
function onHideMenu() {
|
||||
communityContextMenu.close()
|
||||
}
|
||||
target: MainWindow
|
||||
communityContextMenu.close();
|
||||
}
|
||||
|
||||
target: MainWindow
|
||||
}
|
||||
ListView {
|
||||
id: communitiesList
|
||||
|
||||
|
@ -36,15 +41,158 @@ Page {
|
|||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
id: scrollbar
|
||||
|
||||
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 {
|
||||
id: communityContextMenu
|
||||
|
||||
property string tagId
|
||||
property bool hidden
|
||||
property bool muted
|
||||
property string tagId
|
||||
|
||||
function show(id_, hidden_, muted_) {
|
||||
tagId = id_;
|
||||
|
@ -54,177 +202,19 @@ Page {
|
|||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Do not show notification counts for this community or tag.")
|
||||
checkable: true
|
||||
checked: communityContextMenu.muted
|
||||
text: qsTr("Do not show notification counts for this community or tag.")
|
||||
|
||||
onTriggered: Communities.toggleTagMute(communityContextMenu.tagId)
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Hide rooms with this tag or from this community by default.")
|
||||
checkable: true
|
||||
checked: communityContextMenu.hidden
|
||||
text: qsTr("Hide rooms with this tag or from this community by default.")
|
||||
|
||||
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,65 +11,22 @@ import im.nheko 1.0
|
|||
Control {
|
||||
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 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 rowSpacing: Nheko.paddingSmall
|
||||
property alias count: listView.count
|
||||
|
||||
signal completionClicked(string completion)
|
||||
signal completionSelected(string id)
|
||||
|
||||
function up() {
|
||||
if (bottomToTop)
|
||||
down_();
|
||||
else
|
||||
up_();
|
||||
}
|
||||
|
||||
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() {
|
||||
if (currentIndex > -1 && currentIndex < listView.count)
|
||||
return completer.completionAt(currentIndex);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
function finishCompletion() {
|
||||
if (popup.completerName == "room")
|
||||
popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.roomid);
|
||||
else if (popup.completerName == "user")
|
||||
popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.userid);
|
||||
|
||||
}
|
||||
|
||||
function changeCompleter() {
|
||||
if (completerName) {
|
||||
completer = TimelineManager.completerFor(completerName, completerName == "room" ? "" : (popup.roomId != "" ? popup.roomId : room.roomId));
|
||||
|
@ -77,51 +34,79 @@ Control {
|
|||
} else {
|
||||
completer = undefined;
|
||||
}
|
||||
currentIndex = -1
|
||||
currentIndex = -1;
|
||||
}
|
||||
function currentCompletion() {
|
||||
if (currentIndex > -1 && currentIndex < listView.count)
|
||||
return completer.completionAt(currentIndex);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
function down() {
|
||||
if (bottomToTop)
|
||||
up_();
|
||||
else
|
||||
down_();
|
||||
}
|
||||
function down_() {
|
||||
currentIndex = currentIndex + 1;
|
||||
if (currentIndex >= listView.count)
|
||||
currentIndex = -1;
|
||||
}
|
||||
function finishCompletion() {
|
||||
if (popup.completerName == "room")
|
||||
popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.roomid);
|
||||
else if (popup.completerName == "user")
|
||||
popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.userid);
|
||||
}
|
||||
function up() {
|
||||
if (bottomToTop)
|
||||
down_();
|
||||
else
|
||||
up_();
|
||||
}
|
||||
function up_() {
|
||||
currentIndex = currentIndex - 1;
|
||||
if (currentIndex == -2)
|
||||
currentIndex = listView.count - 1;
|
||||
}
|
||||
onCompleterNameChanged: changeCompleter()
|
||||
onRoomIdChanged: changeCompleter()
|
||||
|
||||
bottomPadding: 1
|
||||
leftPadding: 1
|
||||
topPadding: 1
|
||||
rightPadding: 1
|
||||
topPadding: 1
|
||||
|
||||
background: Rectangle {
|
||||
border.color: palette.mid
|
||||
color: palette.base
|
||||
}
|
||||
contentItem: ListView {
|
||||
id: listView
|
||||
|
||||
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
|
||||
// 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
|
||||
// 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))
|
||||
clip: true
|
||||
|
||||
Timer {
|
||||
id: deadTimer
|
||||
interval: 50
|
||||
}
|
||||
|
||||
onContentYChanged: deadTimer.restart()
|
||||
|
||||
// Broken, see https://bugreports.qt.io/browse/QTBUG-102811
|
||||
//reuseItems: true
|
||||
implicitWidth: listView.contentItem.childrenRect.width
|
||||
model: completer
|
||||
verticalLayoutDirection: popup.bottomToTop ? ListView.BottomToTop : ListView.TopToBottom
|
||||
spacing: rowSpacing
|
||||
pixelAligned: true
|
||||
highlightFollowsCurrentItem: true
|
||||
|
||||
displayMarginBeginning: height / 2
|
||||
displayMarginEnd: height / 2
|
||||
spacing: rowSpacing
|
||||
verticalLayoutDirection: popup.bottomToTop ? ListView.BottomToTop : ListView.TopToBottom
|
||||
|
||||
delegate: Rectangle {
|
||||
property variant modelData: model
|
||||
|
||||
ListView.delayRemove: true
|
||||
|
||||
color: model.index == popup.currentIndex ? palette.highlight : palette.base
|
||||
height: (chooser.child?.implicitHeight ?? 0) + 2 * popup.rowMargin
|
||||
implicitWidth: fullWidth ? ListView.view.width : chooser.child.implicitWidth + 4
|
||||
|
@ -131,7 +116,7 @@ Control {
|
|||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onPositionChanged: if (!listView.moving && !deadTimer.running) popup.currentIndex = model.index
|
||||
|
||||
onClicked: {
|
||||
popup.completionClicked(completer.completionAt(model.index));
|
||||
if (popup.completerName == "room")
|
||||
|
@ -139,18 +124,19 @@ Control {
|
|||
else if (popup.completerName == "user")
|
||||
popup.completionSelected(model.userid);
|
||||
}
|
||||
onPositionChanged: if (!listView.moving && !deadTimer.running)
|
||||
popup.currentIndex = model.index
|
||||
}
|
||||
Ripple {
|
||||
color: Qt.rgba(palette.base.r, palette.base.g, palette.base.b, 0.5)
|
||||
}
|
||||
|
||||
DelegateChooser {
|
||||
id: chooser
|
||||
|
||||
roleValue: popup.completerName
|
||||
anchors.fill: parent
|
||||
anchors.margins: popup.rowMargin
|
||||
enabled: false
|
||||
roleValue: popup.completerName
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "user"
|
||||
|
@ -162,28 +148,23 @@ Control {
|
|||
spacing: rowSpacing
|
||||
|
||||
Avatar {
|
||||
height: popup.avatarHeight
|
||||
width: popup.avatarWidth
|
||||
displayName: model.displayName
|
||||
userid: model.userid
|
||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
enabled: false
|
||||
height: popup.avatarHeight
|
||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
userid: model.userid
|
||||
width: popup.avatarWidth
|
||||
}
|
||||
|
||||
Label {
|
||||
text: model.displayName
|
||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
||||
text: model.displayName
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "(" + model.userid + ")"
|
||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
|
||||
text: "(" + model.userid + ")"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "emoji"
|
||||
|
||||
|
@ -194,39 +175,33 @@ Control {
|
|||
spacing: rowSpacing
|
||||
|
||||
Label {
|
||||
visible: !!model.unicode
|
||||
text: model.unicode
|
||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
||||
font: Settings.emojiFont
|
||||
text: model.unicode
|
||||
visible: !!model.unicode
|
||||
}
|
||||
|
||||
Avatar {
|
||||
visible: !model.unicode
|
||||
height: popup.avatarHeight
|
||||
width: popup.avatarWidth
|
||||
crop: false
|
||||
displayName: model.shortcode
|
||||
enabled: false
|
||||
height: popup.avatarHeight
|
||||
//userid: model.shortcode
|
||||
url: (model.url ? model.url : "").replace("mxc://", "image://MxcImage/")
|
||||
enabled: false
|
||||
crop: false
|
||||
visible: !model.unicode
|
||||
width: popup.avatarWidth
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.leftMargin: Nheko.paddingSmall
|
||||
Layout.rightMargin: Nheko.paddingSmall
|
||||
text: model.shortcode
|
||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
||||
text: model.shortcode
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "(" + model.packname + ")"
|
||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
|
||||
text: "(" + model.packname + ")"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "command"
|
||||
|
||||
|
@ -237,20 +212,16 @@ Control {
|
|||
spacing: rowSpacing
|
||||
|
||||
Label {
|
||||
text: model.name
|
||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
||||
font.bold: true
|
||||
text: model.name
|
||||
}
|
||||
|
||||
Label {
|
||||
text: model.description
|
||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
|
||||
text: model.description
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "room"
|
||||
|
||||
|
@ -261,26 +232,22 @@ Control {
|
|||
spacing: rowSpacing
|
||||
|
||||
Avatar {
|
||||
height: popup.avatarHeight
|
||||
width: popup.avatarWidth
|
||||
displayName: model.roomName
|
||||
enabled: false
|
||||
height: popup.avatarHeight
|
||||
roomid: model.roomid
|
||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
enabled: false
|
||||
width: popup.avatarWidth
|
||||
}
|
||||
|
||||
Label {
|
||||
text: model.roomName
|
||||
font.pixelSize: popup.avatarHeight * 0.5
|
||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
||||
font.italic: model.isTombstoned
|
||||
font.pixelSize: popup.avatarHeight * 0.5
|
||||
text: model.roomName
|
||||
textFormat: Text.RichText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "roomAliases"
|
||||
|
||||
|
@ -291,41 +258,38 @@ Control {
|
|||
spacing: rowSpacing
|
||||
|
||||
Avatar {
|
||||
height: popup.avatarHeight
|
||||
width: popup.avatarWidth
|
||||
displayName: model.roomName
|
||||
enabled: false
|
||||
height: popup.avatarHeight
|
||||
roomid: model.roomid
|
||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
enabled: false
|
||||
width: popup.avatarWidth
|
||||
}
|
||||
|
||||
Label {
|
||||
text: model.roomName
|
||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
||||
font.italic: model.isTombstoned
|
||||
text: model.roomName
|
||||
textFormat: Text.RichText
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "(" + model.roomAlias + ")"
|
||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
|
||||
text: "(" + model.roomAlias + ")"
|
||||
textFormat: Text.RichText
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onContentYChanged: deadTimer.restart()
|
||||
|
||||
Timer {
|
||||
id: deadTimer
|
||||
|
||||
interval: 50
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
background: Rectangle {
|
||||
color: palette.base
|
||||
border.color: palette.mid
|
||||
}
|
||||
|
||||
onCompleterNameChanged: changeCompleter()
|
||||
onRoomIdChanged: changeCompleter()
|
||||
}
|
||||
|
|
|
@ -9,21 +9,20 @@ import im.nheko 1.0
|
|||
Label {
|
||||
id: root
|
||||
|
||||
property alias fullText: metrics.text
|
||||
property alias elideWidth: metrics.elideWidth
|
||||
property alias fullText: metrics.text
|
||||
property int fullTextWidth: Math.ceil(metrics.advanceWidth)
|
||||
|
||||
color: palette.text
|
||||
text: (textFormat == Text.PlainText) ? metrics.elidedText : TimelineManager.escapeEmoji(metrics.elidedText)
|
||||
maximumLineCount: 1
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
text: (textFormat == Text.PlainText) ? metrics.elidedText : TimelineManager.escapeEmoji(metrics.elidedText)
|
||||
textFormat: Text.PlainText
|
||||
|
||||
TextMetrics {
|
||||
id: metrics
|
||||
|
||||
font.pointSize: root.font.pointSize
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: root.font.pointSize
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,16 +11,10 @@ Image {
|
|||
id: stateImg
|
||||
|
||||
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 string sourceUrl: {
|
||||
if (!encrypted)
|
||||
return "image://colorimage/" + unencryptedIcon + "?";
|
||||
|
||||
switch (trust) {
|
||||
case Crypto.Verified:
|
||||
return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?";
|
||||
|
@ -32,11 +26,25 @@ Image {
|
|||
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
|
||||
sourceSize.height: height
|
||||
sourceSize.width: width
|
||||
source: {
|
||||
if (encrypted) {
|
||||
switch (trust) {
|
||||
|
@ -51,23 +59,12 @@ Image {
|
|||
return sourceUrl + (stateImg.hovered ? unencryptedHoverColor : unencryptedColor);
|
||||
}
|
||||
}
|
||||
ToolTip.visible: stateImg.hovered
|
||||
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.");
|
||||
}
|
||||
}
|
||||
sourceSize.height: height
|
||||
sourceSize.width: width
|
||||
width: 16
|
||||
|
||||
HoverHandler {
|
||||
id: ma
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,13 +16,21 @@ Popup {
|
|||
mid = mid_in;
|
||||
}
|
||||
|
||||
x: Math.round(parent.width / 2 - width / 2)
|
||||
y: Math.round(parent.height / 4)
|
||||
leftPadding: 10
|
||||
modal: true
|
||||
parent: Overlay.overlay
|
||||
width: timelineRoot.width * 0.8
|
||||
leftPadding: 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: {
|
||||
roomTextInput.forceActiveFocus();
|
||||
}
|
||||
|
@ -35,46 +43,40 @@ Popup {
|
|||
Label {
|
||||
id: titleLabel
|
||||
|
||||
text: qsTr("Forward Message")
|
||||
font.bold: true
|
||||
bottomPadding: 10
|
||||
color: palette.text
|
||||
font.bold: true
|
||||
text: qsTr("Forward Message")
|
||||
}
|
||||
|
||||
Reply {
|
||||
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 ?? ""
|
||||
body: modelData.body ?? ""
|
||||
formattedBody: modelData.formattedBody ?? ""
|
||||
encryptionError: modelData.encryptionError ?? ""
|
||||
eventId: modelData.eventId ?? ""
|
||||
filename: modelData.filename ?? ""
|
||||
filesize: modelData.filesize ?? ""
|
||||
formattedBody: modelData.formattedBody ?? ""
|
||||
isOnlyEmoji: modelData.isOnlyEmoji ?? false
|
||||
originalWidth: modelData.originalWidth ?? 0
|
||||
proportionalHeight: modelData.proportionalHeight ?? 1
|
||||
type: modelData.type ?? MtxEvent.UnknownMessage
|
||||
typeString: modelData.typeString ?? ""
|
||||
url: modelData.url ?? ""
|
||||
originalWidth: modelData.originalWidth ?? 0
|
||||
isOnlyEmoji: modelData.isOnlyEmoji ?? false
|
||||
userColor: TimelineManager.userColor(modelData.userId, palette.window)
|
||||
userId: modelData.userId ?? ""
|
||||
userName: modelData.userName ?? ""
|
||||
encryptionError: modelData.encryptionError ?? ""
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
id: roomTextInput
|
||||
|
||||
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
|
||||
color: palette.text
|
||||
onTextEdited: {
|
||||
completerPopup.completer.searchString = text;
|
||||
}
|
||||
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
|
||||
event.accepted = true;
|
||||
|
@ -90,43 +92,32 @@ Popup {
|
|||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
onTextEdited: {
|
||||
completerPopup.completer.searchString = text;
|
||||
}
|
||||
}
|
||||
|
||||
Completer {
|
||||
id: completerPopup
|
||||
|
||||
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
|
||||
completerName: "room"
|
||||
fullWidth: true
|
||||
centerRowContent: false
|
||||
avatarHeight: 24
|
||||
avatarWidth: 24
|
||||
bottomToTop: false
|
||||
centerRowContent: false
|
||||
completerName: "room"
|
||||
fullWidth: true
|
||||
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onCompletionSelected(id) {
|
||||
room.forwardMessage(messageContextMenu.eventId, id);
|
||||
forwardMessagePopup.close();
|
||||
}
|
||||
|
||||
function onCountChanged() {
|
||||
if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count))
|
||||
completerPopup.currentIndex = 0;
|
||||
|
||||
}
|
||||
|
||||
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 {
|
||||
id: button
|
||||
|
||||
property alias cursor: mouseArea.cursorShape
|
||||
property string image: undefined
|
||||
property color highlightColor: palette.highlight
|
||||
property color buttonTextColor: palette.buttonText
|
||||
property bool changeColorOnHover: true
|
||||
property alias cursor: mouseArea.cursorShape
|
||||
property color highlightColor: palette.highlight
|
||||
property string image: undefined
|
||||
property bool ripple: true
|
||||
|
||||
focusPolicy: Qt.NoFocus
|
||||
width: 16
|
||||
height: 16
|
||||
width: 16
|
||||
|
||||
Image {
|
||||
id: buttonImg
|
||||
|
||||
// Workaround, can't get icon.source working for now...
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: image != "" ? ("image://colorimage/" + image + "?" + ((button.hovered && changeColorOnHover) ? highlightColor : buttonTextColor)) : ""
|
||||
sourceSize.height: button.height
|
||||
sourceSize.width: button.width
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
CursorShape {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
Ripple {
|
||||
enabled: button.ripple
|
||||
color: Qt.rgba(buttonTextColor.r, buttonTextColor.g, buttonTextColor.b, 0.5)
|
||||
enabled: button.ripple
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,22 +11,23 @@ TextEdit {
|
|||
|
||||
property alias cursorShape: cs.cursorShape
|
||||
|
||||
textFormat: TextEdit.RichText
|
||||
readOnly: true
|
||||
focus: false
|
||||
wrapMode: Text.Wrap
|
||||
selectByMouse: !Settings.mobileMode
|
||||
ToolTip.text: hoveredLink
|
||||
ToolTip.visible: hoveredLink || false
|
||||
// this always has to be enabled, otherwise you can't click links anymore!
|
||||
//enabled: selectByMouse
|
||||
color: palette.text
|
||||
onLinkActivated: Nheko.openLink(link)
|
||||
ToolTip.visible: hoveredLink || false
|
||||
ToolTip.text: hoveredLink
|
||||
focus: false
|
||||
readOnly: true
|
||||
selectByMouse: !Settings.mobileMode
|
||||
textFormat: TextEdit.RichText
|
||||
wrapMode: Text.Wrap
|
||||
|
||||
// Setting a tooltip delay makes the hover text empty .-.
|
||||
//ToolTip.delay: Nheko.tooltipDelay
|
||||
Component.onCompleted: {
|
||||
TimelineManager.fixImageRendering(r.textDocument, r);
|
||||
}
|
||||
onLinkActivated: Nheko.openLink(link)
|
||||
|
||||
CursorShape {
|
||||
id: cs
|
||||
|
@ -34,5 +35,4 @@ TextEdit {
|
|||
anchors.fill: parent
|
||||
cursorShape: hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,67 +7,63 @@ import QtQuick.Controls 2.12
|
|||
import QtQuick.Layouts 1.12
|
||||
import im.nheko 1.0
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
id: c
|
||||
|
||||
property color backgroundColor: palette.base
|
||||
property alias color: labelC.color
|
||||
property alias textPadding: input.padding
|
||||
property alias text: input.text
|
||||
property alias echoMode: input.echoMode
|
||||
property alias font: input.font
|
||||
property var hasClear: false
|
||||
property alias label: labelC.text
|
||||
property alias placeholderText: input.placeholderText
|
||||
property alias font: input.font
|
||||
property alias echoMode: input.echoMode
|
||||
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 editingFinished
|
||||
|
||||
function forceActiveFocus() {
|
||||
input.forceActiveFocus();
|
||||
}
|
||||
signal textEdited
|
||||
|
||||
function clear() {
|
||||
input.clear();
|
||||
}
|
||||
function forceActiveFocus() {
|
||||
input.forceActiveFocus();
|
||||
}
|
||||
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
ToolTip.visible: hover.hovered
|
||||
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: labelC.contentHeight
|
||||
Layout.margins: input.padding
|
||||
Layout.bottomMargin: Nheko.paddingSmall
|
||||
visible: labelC.text
|
||||
onTextChanged: timer.restart()
|
||||
|
||||
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
|
||||
|
||||
Label {
|
||||
id: labelC
|
||||
|
||||
y: contentHeight + input.padding + Nheko.paddingSmall
|
||||
enabled: false
|
||||
|
||||
color: palette.text
|
||||
enabled: false
|
||||
font.letterSpacing: input.font.pixelSize * 0.02
|
||||
font.pixelSize: input.font.pixelSize
|
||||
font.weight: Font.DemiBold
|
||||
font.letterSpacing: input.font.pixelSize * 0.02
|
||||
width: parent.width
|
||||
|
||||
state: labelC.text && (input.activeFocus == true || input.text) ? "focused" : ""
|
||||
width: parent.width
|
||||
y: contentHeight + input.padding + Nheko.paddingSmall
|
||||
|
||||
states: State {
|
||||
name: "focused"
|
||||
|
@ -76,50 +72,40 @@ ColumnLayout {
|
|||
target: labelC
|
||||
y: 0
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: input
|
||||
opacity: 1
|
||||
target: input
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
transitions: Transition {
|
||||
from: ""
|
||||
to: "focused"
|
||||
reversible: true
|
||||
to: "focused"
|
||||
|
||||
NumberAnimation {
|
||||
target: labelC
|
||||
alwaysRunToEnd: true
|
||||
duration: 210
|
||||
easing.type: Easing.InCubic
|
||||
properties: "y"
|
||||
duration: 210
|
||||
easing.type: Easing.InCubic
|
||||
alwaysRunToEnd: true
|
||||
target: labelC
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: input
|
||||
properties: "opacity"
|
||||
alwaysRunToEnd: true
|
||||
duration: 210
|
||||
easing.type: Easing.InCubic
|
||||
alwaysRunToEnd: true
|
||||
}
|
||||
|
||||
properties: "opacity"
|
||||
target: input
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: input
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
color: labelC.color
|
||||
opacity: labelC.text ? 0 : 1
|
||||
focus: true
|
||||
|
||||
onTextEdited: c.textEdited()
|
||||
onAccepted: c.accepted()
|
||||
onEditingFinished: c.editingFinished()
|
||||
opacity: labelC.text ? 0 : 1
|
||||
|
||||
background: Rectangle {
|
||||
id: backgroundRect
|
||||
|
@ -127,44 +113,46 @@ ColumnLayout {
|
|||
color: labelC.text ? "transparent" : backgroundColor
|
||||
}
|
||||
|
||||
onAccepted: c.accepted()
|
||||
onEditingFinished: c.editingFinished()
|
||||
onTextEdited: c.textEdited()
|
||||
|
||||
ImageButton {
|
||||
id: clearText
|
||||
|
||||
focusPolicy: Qt.NoFocus
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/round-remove-button.svg"
|
||||
visible: c.hasClear && searchField.text !== ''
|
||||
|
||||
image: ":/icons/icons/ui/round-remove-button.svg"
|
||||
focusPolicy: Qt.NoFocus
|
||||
onClicked: {
|
||||
searchField.clear()
|
||||
searchField.clear();
|
||||
topBar.searchString = "";
|
||||
}
|
||||
hoverEnabled: true
|
||||
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
right: parent.right
|
||||
rightMargin: Nheko.paddingSmall
|
||||
top: parent.top
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: blueBar
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
color: palette.highlight
|
||||
height: 1
|
||||
|
||||
Rectangle {
|
||||
id: blackBar
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
color: palette.text
|
||||
height: parent.height * 2
|
||||
width: 0
|
||||
color: palette.text
|
||||
|
||||
states: State {
|
||||
name: "focused"
|
||||
|
@ -174,31 +162,25 @@ ColumnLayout {
|
|||
target: blackBar
|
||||
width: blueBar.width
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
transitions: Transition {
|
||||
from: ""
|
||||
to: "focused"
|
||||
reversible: true
|
||||
|
||||
to: "focused"
|
||||
|
||||
NumberAnimation {
|
||||
target: blackBar
|
||||
properties: "width"
|
||||
alwaysRunToEnd: true
|
||||
duration: 310
|
||||
easing.type: Easing.InCubic
|
||||
alwaysRunToEnd: true
|
||||
properties: "width"
|
||||
target: blackBar
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: hover
|
||||
|
||||
enabled: c.ToolTip.text
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,60 +14,54 @@ import im.nheko 1.0
|
|||
Rectangle {
|
||||
id: inputBar
|
||||
|
||||
property bool showAllButtons: width > 450 || (messageInput.length == 0 && !messageInput.inputMethodComposing)
|
||||
readonly property string text: messageInput.text
|
||||
|
||||
color: palette.window
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: row.implicitHeight
|
||||
Layout.minimumHeight: 40
|
||||
property bool showAllButtons: width > 450 || (messageInput.length == 0 && !messageInput.inputMethodComposing)
|
||||
|
||||
Layout.preferredHeight: row.implicitHeight
|
||||
color: palette.window
|
||||
|
||||
Component {
|
||||
id: placeCallDialog
|
||||
|
||||
PlaceCall {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: screenShareDialog
|
||||
|
||||
ScreenShare {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
|
||||
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
|
||||
|
||||
ImageButton {
|
||||
visible: CallManager.callsSupported && showAllButtons
|
||||
opacity: (CallManager.haveCallInvite || CallManager.isOnCallOnOtherDevice) ? 0.3 : 1
|
||||
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
|
||||
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: {
|
||||
if (room) {
|
||||
if (CallManager.haveCallInvite) {
|
||||
return;
|
||||
} else if (CallManager.isOnCall) {
|
||||
CallManager.hangUp();
|
||||
}
|
||||
else if(CallManager.isOnCallOnOtherDevice) {
|
||||
} else if (CallManager.isOnCallOnOtherDevice) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
var dialog = placeCallDialog.createObject(timelineRoot);
|
||||
dialog.open();
|
||||
timelineRoot.destroyOnClose(dialog);
|
||||
|
@ -75,18 +69,18 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
visible: showAllButtons
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
hoverEnabled: true
|
||||
width: 22
|
||||
height: 22
|
||||
image: ":/icons/icons/ui/attach.svg"
|
||||
Layout.margins: 8
|
||||
onClicked: room.input.openFileSelection()
|
||||
ToolTip.visible: hovered
|
||||
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 {
|
||||
anchors.fill: parent
|
||||
|
@ -98,112 +92,67 @@ Rectangle {
|
|||
height: parent.height / 2
|
||||
running: parent.visible
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: textInput
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumHeight: Window.height / 4
|
||||
Layout.minimumHeight: fontMetrics.lineSpacing
|
||||
Layout.preferredHeight: contentHeight
|
||||
Layout.fillWidth: true
|
||||
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
contentWidth: availableWidth
|
||||
|
||||
TextArea {
|
||||
id: messageInput
|
||||
|
||||
property int completerTriggeredAt: 0
|
||||
property string lastChar
|
||||
|
||||
function insertCompletion(completion) {
|
||||
messageInput.remove(completerTriggeredAt, cursorPosition);
|
||||
messageInput.insert(cursorPosition, completion);
|
||||
}
|
||||
|
||||
function openCompleter(pos, type) {
|
||||
if (popup.opened) return;
|
||||
if (popup.opened)
|
||||
return;
|
||||
completerTriggeredAt = pos;
|
||||
completer.completerName = type;
|
||||
popup.open();
|
||||
completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition) + messageInput.preeditText);
|
||||
}
|
||||
|
||||
function positionCursorAtEnd() {
|
||||
cursorPosition = messageInput.length;
|
||||
}
|
||||
|
||||
function positionCursorAtStart() {
|
||||
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...")
|
||||
placeholderTextColor: palette.buttonText
|
||||
color: palette.text
|
||||
width: textInput.width
|
||||
verticalAlignment: TextEdit.AlignVCenter
|
||||
wrapMode: TextEdit.Wrap
|
||||
padding: 0
|
||||
selectByMouse: true
|
||||
topPadding: 8
|
||||
bottomPadding: 8
|
||||
leftPadding: inputBar.showAllButtons? 0 : 8
|
||||
focus: true
|
||||
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 ;
|
||||
verticalAlignment: TextEdit.AlignVCenter
|
||||
width: textInput.width
|
||||
wrapMode: TextEdit.Wrap
|
||||
|
||||
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);
|
||||
}
|
||||
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) => {
|
||||
Keys.onPressed: event => {
|
||||
if (event.matches(StandardKey.Paste)) {
|
||||
event.accepted = room.input.tryPasteAttachment(false);
|
||||
} else if (event.key == Qt.Key_Space) {
|
||||
// close popup if user enters space after colon
|
||||
if (cursorPosition == completerTriggeredAt + 1)
|
||||
popup.close();
|
||||
|
||||
if (popup.opened && completer.count <= 0)
|
||||
popup.close();
|
||||
|
||||
} else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_U) {
|
||||
messageInput.clear();
|
||||
} else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_P) {
|
||||
|
@ -218,8 +167,8 @@ Rectangle {
|
|||
completer.completerName = "";
|
||||
popup.close();
|
||||
} 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")) {
|
||||
room.input.send();
|
||||
event.accepted = true;
|
||||
|
@ -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 {
|
||||
function onRoomChanged() {
|
||||
messageInput.clear();
|
||||
if (room)
|
||||
messageInput.append(room.input.text);
|
||||
|
||||
completer.completerName = "";
|
||||
messageInput.forceActiveFocus();
|
||||
}
|
||||
|
||||
target: timelineView
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onCompletionClicked(completion) {
|
||||
messageInput.insertCompletion(completion);
|
||||
|
@ -334,43 +315,39 @@ Rectangle {
|
|||
|
||||
target: completer
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: popup
|
||||
|
||||
background: null
|
||||
padding: 0
|
||||
x: messageInput.positionToRectangle(messageInput.completerTriggeredAt).x
|
||||
y: messageInput.positionToRectangle(messageInput.completerTriggeredAt).y - height
|
||||
|
||||
background: null
|
||||
padding: 0
|
||||
enter: Transition {
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
from: 0
|
||||
property: "opacity"
|
||||
to: 1
|
||||
}
|
||||
}
|
||||
exit: Transition {
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
from: 1
|
||||
property: "opacity"
|
||||
to: 0
|
||||
}
|
||||
}
|
||||
|
||||
Completer {
|
||||
anchors.fill: parent
|
||||
id: completer
|
||||
|
||||
anchors.fill: parent
|
||||
rowMargin: 2
|
||||
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 {
|
||||
function onTextChanged(newText) {
|
||||
messageInput.text = newText;
|
||||
|
@ -380,16 +357,13 @@ Rectangle {
|
|||
ignoreUnknownSignals: true
|
||||
target: room ? room.input : null
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onReplyChanged() {
|
||||
messageInput.forceActiveFocus();
|
||||
}
|
||||
|
||||
function onEditChanged() {
|
||||
messageInput.forceActiveFocus();
|
||||
}
|
||||
|
||||
function onReplyChanged() {
|
||||
messageInput.forceActiveFocus();
|
||||
}
|
||||
function onThreadChanged() {
|
||||
messageInput.forceActiveFocus();
|
||||
}
|
||||
|
@ -397,7 +371,6 @@ Rectangle {
|
|||
ignoreUnknownSignals: true
|
||||
target: room
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onFocusInput() {
|
||||
messageInput.forceActiveFocus();
|
||||
|
@ -405,31 +378,29 @@ Rectangle {
|
|||
|
||||
target: TimelineManager
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
acceptedButtons: Qt.MiddleButton
|
||||
// workaround for wrong cursor shape on some platforms
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.MiddleButton
|
||||
cursorShape: Qt.IBeamCursor
|
||||
onPressed: (mouse) => mouse.accepted = room.input.tryPasteAttachment(true)
|
||||
}
|
||||
|
||||
onPressed: mouse => mouse.accepted = room.input.tryPasteAttachment(true)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: stickerButton
|
||||
visible: showAllButtons
|
||||
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||
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.visible: hovered
|
||||
height: 22
|
||||
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();
|
||||
|
@ -440,20 +411,19 @@ Rectangle {
|
|||
|
||||
emoji: false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: emojiButton
|
||||
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||
Layout.margins: 8
|
||||
hoverEnabled: true
|
||||
width: 22
|
||||
height: 22
|
||||
image: ":/icons/icons/ui/smile.svg"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Emoji")
|
||||
ToolTip.visible: hovered
|
||||
height: 22
|
||||
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();
|
||||
|
@ -465,28 +435,25 @@ Rectangle {
|
|||
emoji: true
|
||||
}
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||
Layout.margins: 8
|
||||
hoverEnabled: true
|
||||
width: 22
|
||||
height: 22
|
||||
image: ":/icons/icons/ui/send.svg"
|
||||
Layout.rightMargin: 8
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Send")
|
||||
ToolTip.visible: hovered
|
||||
height: 22
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/send.svg"
|
||||
width: 22
|
||||
|
||||
onClicked: {
|
||||
room.input.send();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: room ? (!room.permissions.canSend(MtxEvent.TextMessage)) : false
|
||||
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 {
|
||||
id: warningRoot
|
||||
|
||||
required property string text
|
||||
property color bubbleColor: Nheko.theme.error
|
||||
required property string text
|
||||
|
||||
implicitHeight: visible ? warningDisplay.implicitHeight + 4 * Nheko.paddingSmall : 0
|
||||
height: implicitHeight
|
||||
Layout.fillWidth: true
|
||||
color: palette.window // required to hide the timeline behind this warning
|
||||
height: implicitHeight
|
||||
implicitHeight: visible ? warningDisplay.implicitHeight + 4 * Nheko.paddingSmall : 0
|
||||
|
||||
Rectangle {
|
||||
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.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
|
||||
|
||||
Label {
|
||||
id: warningDisplay
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Nheko.paddingSmall
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: warningRoot.text
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,9 +11,8 @@ Item {
|
|||
id: privacyScreen
|
||||
|
||||
readonly property bool active: Settings.privacyScreen && screenSaver.state === "Visible"
|
||||
property var timelineRoot
|
||||
property int screenTimeout
|
||||
|
||||
property var timelineRoot
|
||||
required property var windowTarget
|
||||
|
||||
Connections {
|
||||
|
@ -24,29 +23,28 @@ Item {
|
|||
} else {
|
||||
if (timelineRoot.visible)
|
||||
screenSaverTimer.start();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
target: windowTarget
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: screenSaverTimer
|
||||
|
||||
interval: screenTimeout * 1000
|
||||
running: !windowTarget.active
|
||||
|
||||
onTriggered: {
|
||||
screenSaver.state = "Visible";
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: screenSaver
|
||||
|
||||
state: "Invisible"
|
||||
anchors.fill: parent
|
||||
state: "Invisible"
|
||||
visible: false
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "Visible"
|
||||
|
@ -55,20 +53,18 @@ Item {
|
|||
target: screenSaver
|
||||
visible: true
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: screenSaver
|
||||
opacity: 1
|
||||
target: screenSaver
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "Invisible"
|
||||
|
||||
PropertyChanges {
|
||||
target: screenSaver
|
||||
opacity: 0
|
||||
target: screenSaver
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: screenSaver
|
||||
visible: false
|
||||
|
@ -78,39 +74,33 @@ Item {
|
|||
transitions: [
|
||||
Transition {
|
||||
from: "Invisible"
|
||||
to: "Visible"
|
||||
reversible: true
|
||||
to: "Visible"
|
||||
|
||||
SequentialAnimation {
|
||||
NumberAnimation {
|
||||
target: screenSaver
|
||||
property: "visible"
|
||||
duration: 0
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
property: "visible"
|
||||
target: screenSaver
|
||||
property: "opacity"
|
||||
}
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.Linear
|
||||
property: "opacity"
|
||||
target: screenSaver
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
MultiEffect {
|
||||
id: blur
|
||||
|
||||
blurEnabled: true
|
||||
|
||||
anchors.fill: parent
|
||||
source: timelineRoot
|
||||
blur: 1.0
|
||||
blurEnabled: true
|
||||
blurMax: 32
|
||||
source: timelineRoot
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,19 +11,24 @@ Popup {
|
|||
id: quickSwitcher
|
||||
|
||||
property int textHeight: Math.round(Qt.application.font.pixelSize * 2.4)
|
||||
property int textMargin: Nheko.paddingSmall
|
||||
|
||||
background: null
|
||||
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)
|
||||
y: Math.round(parent.height / 4)
|
||||
modal: true
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
parent: Overlay.overlay
|
||||
|
||||
Overlay.modal: Rectangle {
|
||||
color: "#aa1E1E1E"
|
||||
}
|
||||
|
||||
onClosed: TimelineManager.focusMessageInput()
|
||||
onOpened: {
|
||||
roomTextInput.forceActiveFocus();
|
||||
}
|
||||
onClosed: TimelineManager.focusMessageInput()
|
||||
property int textMargin: Nheko.paddingSmall
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
|
@ -32,12 +37,10 @@ Popup {
|
|||
MatrixTextField {
|
||||
id: roomTextInput
|
||||
|
||||
width: parent.width
|
||||
font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
|
||||
color: palette.text
|
||||
onTextEdited: {
|
||||
completerPopup.completer.searchString = text;
|
||||
}
|
||||
font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
|
||||
width: parent.width
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
|
||||
event.accepted = true;
|
||||
|
@ -53,41 +56,35 @@ Popup {
|
|||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
onTextEdited: {
|
||||
completerPopup.completer.searchString = text;
|
||||
}
|
||||
}
|
||||
|
||||
Completer {
|
||||
id: completerPopup
|
||||
|
||||
visible: roomTextInput.text.length > 0
|
||||
width: parent.width
|
||||
completerName: "room"
|
||||
bottomToTop: false
|
||||
fullWidth: true
|
||||
avatarHeight: quickSwitcher.textHeight
|
||||
avatarWidth: quickSwitcher.textHeight
|
||||
bottomToTop: false
|
||||
centerRowContent: false
|
||||
completerName: "room"
|
||||
fullWidth: true
|
||||
rowMargin: Math.round(quickSwitcher.textMargin / 2)
|
||||
rowSpacing: quickSwitcher.textMargin
|
||||
visible: roomTextInput.text.length > 0
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onCompletionSelected(id) {
|
||||
Rooms.setCurrentRoom(id);
|
||||
quickSwitcher.close();
|
||||
}
|
||||
|
||||
function onCountChanged() {
|
||||
if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count))
|
||||
completerPopup.currentIndex = 0;
|
||||
|
||||
}
|
||||
|
||||
target: completerPopup
|
||||
}
|
||||
|
||||
Overlay.modal: Rectangle {
|
||||
color: "#aa1E1E1E"
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,10 +11,11 @@ import im.nheko 1.0
|
|||
Flow {
|
||||
id: reactionFlow
|
||||
|
||||
property string eventId
|
||||
|
||||
// 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 gentleText: Qt.hsla(palette.text.hslHue, palette.text.hslSaturation, palette.text.hslLightness, 0.6)
|
||||
property string eventId
|
||||
property alias reactions: repeater.model
|
||||
|
||||
spacing: 4
|
||||
|
@ -25,40 +26,39 @@ Flow {
|
|||
delegate: AbstractButton {
|
||||
id: reaction
|
||||
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
onClicked: {
|
||||
console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + ". selfReactedEvent: " + modelData.selfReactedEvent);
|
||||
room.input.reaction(reactionFlow.eventId, modelData.key);
|
||||
}
|
||||
Component.onCompleted: {
|
||||
ToolTip.text = Qt.binding(function() {
|
||||
if (textMetrics.elidedText === textMetrics.text) {
|
||||
return modelData.users;
|
||||
}
|
||||
return modelData.displayKey + "\n" + modelData.users;
|
||||
})
|
||||
}
|
||||
ToolTip.visible: hovered
|
||||
hoverEnabled: true
|
||||
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 {
|
||||
spacing: textMetrics.height / 4
|
||||
|
||||
TextMetrics {
|
||||
id: textMetrics
|
||||
|
||||
font.family: Settings.emojiFont
|
||||
elide: Text.ElideRight
|
||||
elideWidth: 150
|
||||
font.family: Settings.emojiFont
|
||||
text: modelData.displayKey
|
||||
}
|
||||
|
||||
Text {
|
||||
id: reactionText
|
||||
|
||||
anchors.baseline: reactionCounter.baseline
|
||||
color: (reaction.hovered || modelData.selfReactedEvent !== '') ? palette.highlightedText : palette.text
|
||||
font.family: Settings.emojiFont
|
||||
maximumLineCount: 1
|
||||
text: {
|
||||
// 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) {
|
||||
|
@ -68,51 +68,45 @@ Flow {
|
|||
}
|
||||
return textMetrics.elidedText;
|
||||
}
|
||||
font.family: Settings.emojiFont
|
||||
color: (reaction.hovered || modelData.selfReactedEvent !== '') ? palette.highlightedText: palette.text
|
||||
maximumLineCount: 1
|
||||
visible: !modelData.key.startsWith("mxc://")
|
||||
}
|
||||
Image {
|
||||
anchors.verticalCenter: divider.verticalCenter
|
||||
fillMode: Image.PreserveAspectFit
|
||||
height: textMetrics.height
|
||||
width: textMetrics.height
|
||||
source: modelData.key.startsWith("mxc://") ? (modelData.key.replace("mxc://", "image://MxcImage/") + "?scale") : ""
|
||||
visible: modelData.key.startsWith("mxc://")
|
||||
fillMode: Image.PreserveAspectFit
|
||||
width: textMetrics.height
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: divider
|
||||
|
||||
color: reaction.hovered ? palette.text : gentleText
|
||||
height: Math.floor(reactionCounter.implicitHeight * 1.4)
|
||||
width: 1
|
||||
color: reaction.hovered ? palette.text: gentleText
|
||||
}
|
||||
|
||||
Text {
|
||||
id: reactionCounter
|
||||
|
||||
anchors.verticalCenter: divider.verticalCenter
|
||||
text: modelData.count
|
||||
font: reaction.font
|
||||
color: (reaction.hovered || modelData.selfReactedEvent !== '') ? palette.highlightedText : palette.windowText
|
||||
font: reaction.font
|
||||
text: modelData.count
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
ToolTip.text = Qt.binding(function () {
|
||||
if (textMetrics.elidedText === textMetrics.text) {
|
||||
return modelData.users;
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
anchors.centerIn: parent
|
||||
implicitWidth: reaction.implicitWidth
|
||||
implicitHeight: reaction.implicitHeight
|
||||
border.color: reaction.hovered ? palette.text: gentleText
|
||||
color: reaction.hovered ? palette.highlight : (modelData.selfReactedEvent !== '' ? gentleHighlight : palette.window)
|
||||
border.width: 1
|
||||
radius: reaction.height / 2
|
||||
return modelData.displayKey + "\n" + modelData.users;
|
||||
});
|
||||
}
|
||||
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
|
||||
|
||||
Layout.fillWidth: true
|
||||
visible: room && (room.reply || room.edit || room.thread)
|
||||
color: palette.window
|
||||
// Height of child, plus margins, plus border
|
||||
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
|
||||
|
||||
Reply {
|
||||
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.leftMargin: replyPopup.width < 450 ? Nheko.paddingSmall : (CallManager.callsSupported ? 2 * (22 + 16) : 1 * (22 + 16))
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: replyPopup.width < 450 ? 2 * (22 + 16) : 3 * (22 + 16)
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Nheko.paddingSmall
|
||||
userColor: TimelineManager.userColor(modelData.userId, palette.window)
|
||||
blurhash: modelData.blurhash ?? ""
|
||||
body: modelData.body ?? ""
|
||||
formattedBody: modelData.formattedBody ?? ""
|
||||
encryptionError: modelData.encryptionError ?? 0
|
||||
eventId: modelData.eventId ?? ""
|
||||
filename: modelData.filename ?? ""
|
||||
filesize: modelData.filesize ?? ""
|
||||
formattedBody: modelData.formattedBody ?? ""
|
||||
isOnlyEmoji: modelData.isOnlyEmoji ?? false
|
||||
originalWidth: modelData.originalWidth ?? 0
|
||||
proportionalHeight: modelData.proportionalHeight ?? 1
|
||||
type: modelData.type ?? MtxEvent.UnknownMessage
|
||||
typeString: modelData.typeString ?? ""
|
||||
url: modelData.url ?? ""
|
||||
originalWidth: modelData.originalWidth ?? 0
|
||||
isOnlyEmoji: modelData.isOnlyEmoji ?? false
|
||||
userColor: TimelineManager.userColor(modelData.userId, palette.window)
|
||||
userId: modelData.userId ?? ""
|
||||
userName: modelData.userName ?? ""
|
||||
encryptionError: modelData.encryptionError ?? 0
|
||||
visible: room && room.reply
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: closeReplyButton
|
||||
|
||||
visible: room && room.reply
|
||||
ToolTip.text: qsTr("Close")
|
||||
ToolTip.visible: closeReplyButton.hovered
|
||||
anchors.margins: Nheko.paddingSmall
|
||||
anchors.right: replyPreview.right
|
||||
anchors.top: replyPreview.top
|
||||
anchors.margins: Nheko.paddingSmall
|
||||
hoverEnabled: true
|
||||
width: 16
|
||||
height: 16
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/dismiss.svg"
|
||||
ToolTip.visible: closeReplyButton.hovered
|
||||
ToolTip.text: qsTr("Close")
|
||||
visible: room && room.reply
|
||||
width: 16
|
||||
|
||||
onClicked: room.reply = undefined
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: closeEditButton
|
||||
|
||||
visible: room && room.edit
|
||||
anchors.right: closeThreadButton.left
|
||||
ToolTip.text: qsTr("Cancel Edit")
|
||||
ToolTip.visible: closeEditButton.hovered
|
||||
anchors.margins: 8
|
||||
anchors.right: closeThreadButton.left
|
||||
anchors.top: parent.top
|
||||
height: 22
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/dismiss_edit.svg"
|
||||
visible: room && room.edit
|
||||
width: 22
|
||||
height: 22
|
||||
ToolTip.visible: closeEditButton.hovered
|
||||
ToolTip.text: qsTr("Cancel Edit")
|
||||
|
||||
onClicked: room.edit = undefined
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
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.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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -20,19 +20,14 @@ import im.nheko.EmojiModel 1.0
|
|||
Pane {
|
||||
id: timelineRoot
|
||||
|
||||
background: null
|
||||
padding: 0
|
||||
|
||||
FontMetrics {
|
||||
id: fontMetrics
|
||||
function destroyOnClose(obj) {
|
||||
if (obj.closing != undefined)
|
||||
obj.closing.connect(() => obj.destroy(1000));
|
||||
else if (obj.aboutToHide != undefined)
|
||||
obj.aboutToHide.connect(() => obj.destroy(1000));
|
||||
}
|
||||
|
||||
RoomDirectoryModel {
|
||||
id: publicRooms
|
||||
}
|
||||
|
||||
UserDirectoryModel {
|
||||
id: userDirectory
|
||||
function destroyOnClosed(obj) {
|
||||
obj.aboutToHide.connect(() => obj.destroy(1000));
|
||||
}
|
||||
|
||||
//Timer {
|
||||
|
@ -41,9 +36,20 @@ Pane {
|
|||
// running: true
|
||||
// repeat: true
|
||||
//}
|
||||
|
||||
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) {
|
||||
var dialog = component.createObject(timelineRoot, {
|
||||
"roomSettings": settings
|
||||
});
|
||||
dialog.show();
|
||||
destroyOnClose(dialog);
|
||||
} else {
|
||||
console.error("Failed to create component: " + component.errorString());
|
||||
}
|
||||
}
|
||||
function showAllowedRoomsEditor(settings) {
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/AllowedRoomsSettingsDialog.qml");
|
||||
if (component.status == Component.Ready) {
|
||||
var dialog = component.createObject(timelineRoot, {
|
||||
"roomSettings": settings
|
||||
|
@ -53,11 +59,9 @@ Pane {
|
|||
} else {
|
||||
console.error("Failed to create component: " + component.errorString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function showPLEditor(settings) {
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/PowerLevelEditor.qml")
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/PowerLevelEditor.qml");
|
||||
if (component.status == Component.Ready) {
|
||||
var dialog = component.createObject(timelineRoot, {
|
||||
"roomSettings": settings
|
||||
|
@ -68,9 +72,8 @@ Pane {
|
|||
console.error("Failed to create component: " + component.errorString());
|
||||
}
|
||||
}
|
||||
|
||||
function showSpacePLApplyPrompt(settings, editingModel) {
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/PowerLevelSpacesApplyDialog.qml")
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/PowerLevelSpacesApplyDialog.qml");
|
||||
if (component.status == Component.Ready) {
|
||||
var dialog = component.createObject(timelineRoot, {
|
||||
"roomSettings": settings,
|
||||
|
@ -83,36 +86,37 @@ Pane {
|
|||
}
|
||||
}
|
||||
|
||||
function showAllowedRoomsEditor(settings) {
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/AllowedRoomsSettingsDialog.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());
|
||||
}
|
||||
}
|
||||
background: null
|
||||
padding: 0
|
||||
|
||||
FontMetrics {
|
||||
id: fontMetrics
|
||||
|
||||
}
|
||||
RoomDirectoryModel {
|
||||
id: publicRooms
|
||||
|
||||
}
|
||||
UserDirectoryModel {
|
||||
id: userDirectory
|
||||
|
||||
}
|
||||
Component {
|
||||
id: readReceiptsDialog
|
||||
|
||||
ReadReceipts {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Quit
|
||||
|
||||
onActivated: Qt.quit()
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Ctrl+K"
|
||||
|
||||
onActivated: {
|
||||
var component = Qt.createComponent("qrc:/qml/QuickSwitcher.qml")
|
||||
var component = Qt.createComponent("qrc:/qml/QuickSwitcher.qml");
|
||||
if (component.status == Component.Ready) {
|
||||
var quickSwitch = component.createObject(timelineRoot);
|
||||
quickSwitch.open();
|
||||
|
@ -122,37 +126,25 @@ Pane {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
// Add alternative shortcut, because sometimes Alt+A is stolen by the TextEdit
|
||||
sequences: ["Alt+A", "Ctrl+Shift+A"]
|
||||
|
||||
onActivated: Rooms.nextRoomWithActivity()
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Ctrl+Down"
|
||||
|
||||
onActivated: Rooms.nextRoom()
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Ctrl+Up"
|
||||
|
||||
onActivated: Rooms.previousRoom()
|
||||
}
|
||||
|
||||
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() {
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/JoinRoomDialog.qml")
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/JoinRoomDialog.qml");
|
||||
if (component.status == Component.Ready) {
|
||||
var dialog = component.createObject(timelineRoot);
|
||||
dialog.show();
|
||||
|
@ -161,11 +153,22 @@ Pane {
|
|||
console.error("Failed to create component: " + component.errorString());
|
||||
}
|
||||
}
|
||||
|
||||
function onShowRoomJoinPrompt(summary) {
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/ConfirmJoinRoomDialog.qml")
|
||||
function onOpenLogoutDialog() {
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/LogoutDialog.qml");
|
||||
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();
|
||||
destroyOnClose(dialog);
|
||||
} else {
|
||||
|
@ -175,12 +178,13 @@ Pane {
|
|||
|
||||
target: Nheko
|
||||
}
|
||||
|
||||
Connections {
|
||||
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) {
|
||||
var dialog = component.createObject(timelineRoot, {"flow": flow});
|
||||
var dialog = component.createObject(timelineRoot, {
|
||||
"flow": flow
|
||||
});
|
||||
dialog.show();
|
||||
destroyOnClose(dialog);
|
||||
} else {
|
||||
|
@ -190,31 +194,87 @@ Pane {
|
|||
|
||||
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 {
|
||||
function onOpenProfile(profile) {
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/UserProfile.qml")
|
||||
function onOpenInviteUsersDialog(invitees) {
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/InviteDialog.qml");
|
||||
if (component.status == Component.Ready) {
|
||||
var userProfile = component.createObject(timelineRoot, {"profile": profile});
|
||||
var dialog = component.createObject(timelineRoot, {
|
||||
"invitees": invitees
|
||||
});
|
||||
dialog.show();
|
||||
destroyOnClose(dialog);
|
||||
} else {
|
||||
console.error("Failed to create component: " + component.errorString());
|
||||
}
|
||||
}
|
||||
function onOpenLeaveRoomDialog(roomid, reason) {
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/LeaveRoomDialog.qml");
|
||||
if (component.status == Component.Ready) {
|
||||
var dialog = component.createObject(timelineRoot, {
|
||||
"roomId": roomid,
|
||||
"reason": reason
|
||||
});
|
||||
dialog.open();
|
||||
destroyOnClose(dialog);
|
||||
} else {
|
||||
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) {
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/ImageOverlay.qml");
|
||||
if (component.status == Component.Ready) {
|
||||
var dialog = component.createObject(timelineRoot, {
|
||||
"room": room,
|
||||
"eventId": eventId,
|
||||
"url": url,
|
||||
"originalWidth": originalWidth ?? 0,
|
||||
"proportionalHeight": proportionalHeight ?? 0
|
||||
});
|
||||
dialog.showFullScreen();
|
||||
destroyOnClose(dialog);
|
||||
} else {
|
||||
console.error("Failed to create component: " + component.errorString());
|
||||
}
|
||||
}
|
||||
function onShowImagePackSettings(room, packlist) {
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/ImagePackSettingsDialog.qml")
|
||||
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/ImagePackSettingsDialog.qml");
|
||||
if (component.status == Component.Ready) {
|
||||
var packSet = component.createObject(timelineRoot, {
|
||||
"room": room,
|
||||
|
@ -227,87 +287,12 @@ Pane {
|
|||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/InviteDialog.qml")
|
||||
if (component.status == Component.Ready) {
|
||||
var dialog = component.createObject(timelineRoot, {
|
||||
"invitees": invitees
|
||||
});
|
||||
dialog.show();
|
||||
destroyOnClose(dialog);
|
||||
} else {
|
||||
console.error("Failed to create component: " + component.errorString());
|
||||
}
|
||||
}
|
||||
|
||||
function onOpenLeaveRoomDialog(roomid, reason) {
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/LeaveRoomDialog.qml")
|
||||
if (component.status == Component.Ready) {
|
||||
var dialog = component.createObject(timelineRoot, {
|
||||
"roomId": roomid,
|
||||
"reason": reason
|
||||
});
|
||||
dialog.open();
|
||||
destroyOnClose(dialog);
|
||||
} else {
|
||||
console.error("Failed to create component: " + component.errorString());
|
||||
}
|
||||
}
|
||||
|
||||
function onShowImageOverlay(room, eventId, url, originalWidth, proportionalHeight) {
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/ImageOverlay.qml")
|
||||
if (component.status == Component.Ready) {
|
||||
var dialog = component.createObject(timelineRoot, {
|
||||
"room": room,
|
||||
"eventId": eventId,
|
||||
"url": url,
|
||||
"originalWidth": originalWidth ?? 0,
|
||||
"proportionalHeight": proportionalHeight ?? 0
|
||||
}
|
||||
);
|
||||
dialog.showFullScreen();
|
||||
destroyOnClose(dialog);
|
||||
} else {
|
||||
console.error("Failed to create component: " + component.errorString());
|
||||
}
|
||||
}
|
||||
|
||||
target: TimelineManager
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onNewInviteState() {
|
||||
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) {
|
||||
var dialog = component.createObject(timelineRoot);
|
||||
dialog.open();
|
||||
|
@ -320,141 +305,97 @@ Pane {
|
|||
|
||||
target: CallManager
|
||||
}
|
||||
|
||||
SelfVerificationCheck {
|
||||
}
|
||||
|
||||
InputDialog {
|
||||
id: uiaPassPrompt
|
||||
|
||||
echoMode: TextInput.Password
|
||||
title: UIA.title
|
||||
prompt: qsTr("Please enter your login password to continue:")
|
||||
onAccepted: (t) => {
|
||||
title: UIA.title
|
||||
|
||||
onAccepted: t => {
|
||||
return UIA.continuePassword(t);
|
||||
}
|
||||
}
|
||||
|
||||
InputDialog {
|
||||
id: uiaEmailPrompt
|
||||
|
||||
title: UIA.title
|
||||
prompt: qsTr("Please enter a valid email address to continue:")
|
||||
onAccepted: (t) => {
|
||||
title: UIA.title
|
||||
|
||||
onAccepted: t => {
|
||||
return UIA.continueEmail(t);
|
||||
}
|
||||
}
|
||||
|
||||
PhoneNumberInputDialog {
|
||||
id: uiaPhoneNumberPrompt
|
||||
|
||||
title: UIA.title
|
||||
prompt: qsTr("Please enter a valid phone number to continue:")
|
||||
title: UIA.title
|
||||
|
||||
onAccepted: (p, t) => {
|
||||
return UIA.continuePhoneNumber(p, t);
|
||||
}
|
||||
}
|
||||
|
||||
InputDialog {
|
||||
id: uiaTokenPrompt
|
||||
|
||||
title: UIA.title
|
||||
prompt: qsTr("Please enter the token which has been sent to you:")
|
||||
onAccepted: (t) => {
|
||||
title: UIA.title
|
||||
|
||||
onAccepted: t => {
|
||||
return UIA.submit3pidToken(t);
|
||||
}
|
||||
}
|
||||
|
||||
Platform.MessageDialog {
|
||||
id: uiaErrorDialog
|
||||
|
||||
buttons: Platform.MessageDialog.Ok
|
||||
}
|
||||
|
||||
Platform.MessageDialog {
|
||||
id: uiaConfirmationLinkDialog
|
||||
|
||||
buttons: Platform.MessageDialog.Ok
|
||||
text: qsTr("Wait for the confirmation link to arrive, then continue.")
|
||||
|
||||
onAccepted: UIA.continue3pidReceived()
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onPassword() {
|
||||
console.log("UIA: password needed");
|
||||
uiaPassPrompt.show();
|
||||
}
|
||||
|
||||
function onEmail() {
|
||||
uiaEmailPrompt.show();
|
||||
}
|
||||
|
||||
function onPhoneNumber() {
|
||||
uiaPhoneNumberPrompt.show();
|
||||
}
|
||||
|
||||
function onPrompt3pidToken() {
|
||||
uiaTokenPrompt.show();
|
||||
}
|
||||
|
||||
function onConfirm3pidToken() {
|
||||
uiaConfirmationLinkDialog.open();
|
||||
}
|
||||
|
||||
function onEmail() {
|
||||
uiaEmailPrompt.show();
|
||||
}
|
||||
function onError(msg) {
|
||||
uiaErrorDialog.text = msg;
|
||||
uiaErrorDialog.open();
|
||||
}
|
||||
function onPassword() {
|
||||
console.log("UIA: password needed");
|
||||
uiaPassPrompt.show();
|
||||
}
|
||||
function onPhoneNumber() {
|
||||
uiaPhoneNumberPrompt.show();
|
||||
}
|
||||
function onPrompt3pidToken() {
|
||||
uiaTokenPrompt.show();
|
||||
}
|
||||
|
||||
target: UIA
|
||||
}
|
||||
|
||||
StackView {
|
||||
id: mainWindow
|
||||
|
||||
anchors.fill: parent
|
||||
initialItem: welcomePage
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
property Transition popEnterOrg
|
||||
property Transition popExitOrg
|
||||
|
||||
// for some reason direct bindings to a hidden StackView don't work, so manually store and restore here.
|
||||
property Transition pushEnterOrg
|
||||
property Transition pushExitOrg
|
||||
property Transition popEnterOrg
|
||||
property Transition popExitOrg
|
||||
property Transition replaceEnterOrg
|
||||
property Transition replaceExitOrg
|
||||
Component.onCompleted: {
|
||||
pushEnterOrg = pushEnter;
|
||||
popEnterOrg = popEnter;
|
||||
replaceEnterOrg = replaceEnter;
|
||||
pushExitOrg = pushExit;
|
||||
popExitOrg = popExit;
|
||||
replaceExitOrg = replaceExit;
|
||||
|
||||
updateTrans()
|
||||
}
|
||||
|
||||
function updateTrans() {
|
||||
pushEnter = Settings.reducedMotion ? reducedMotionTransitionEnter : pushEnterOrg;
|
||||
|
@ -465,65 +406,104 @@ Pane {
|
|||
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 {
|
||||
target: Settings
|
||||
function onReducedMotionChanged() {
|
||||
mainWindow.updateTrans();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
target: Settings
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: welcomePage
|
||||
|
||||
WelcomePage {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: chatPage
|
||||
|
||||
ChatPage {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: loginPage
|
||||
|
||||
LoginPage {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: registerPage
|
||||
|
||||
RegisterPage {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: userSettingsPage
|
||||
|
||||
UserSettingsPage {
|
||||
}
|
||||
}
|
||||
Snackbar {
|
||||
id: snackbar
|
||||
|
||||
}
|
||||
|
||||
|
||||
Snackbar { id: snackbar }
|
||||
|
||||
Connections {
|
||||
function onSwitchToChatPage() {
|
||||
mainWindow.replace(null, chatPage);
|
||||
}
|
||||
function onSwitchToLoginPage(error) {
|
||||
mainWindow.replace(welcomePage, {}, loginPage, {"error": error}, StackView.PopTransition);
|
||||
}
|
||||
function onShowNotification(msg) {
|
||||
snackbar.showNotification(msg);
|
||||
console.log("New snack: " + msg);
|
||||
}
|
||||
target: MainWindow
|
||||
function onSwitchToChatPage() {
|
||||
mainWindow.replace(null, chatPage);
|
||||
}
|
||||
function onSwitchToLoginPage(error) {
|
||||
mainWindow.replace(welcomePage, {}, loginPage, {
|
||||
"error": error
|
||||
}, StackView.PopTransition);
|
||||
}
|
||||
|
||||
target: MainWindow
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,22 +10,29 @@ import QtQuick.Layouts 1.3
|
|||
import im.nheko 1.0
|
||||
|
||||
Item {
|
||||
visible: false
|
||||
enabled: false
|
||||
visible: false
|
||||
|
||||
Dialog {
|
||||
id: showRecoverKeyDialog
|
||||
|
||||
property string recoveryKey: ""
|
||||
|
||||
parent: Overlay.overlay
|
||||
anchors.centerIn: parent
|
||||
height: content.height + implicitFooterHeight + implicitHeaderHeight
|
||||
width: content.width
|
||||
padding: 0
|
||||
modal: true
|
||||
standardButtons: Dialog.Ok
|
||||
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 {
|
||||
id: content
|
||||
|
@ -33,45 +40,33 @@ Item {
|
|||
spacing: 0
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4
|
||||
color: palette.text
|
||||
font.bold: true
|
||||
horizontalAlignment: TextEdit.AlignHCenter
|
||||
verticalAlignment: TextEdit.AlignVCenter
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
text: showRecoverKeyDialog.recoveryKey
|
||||
color: palette.text
|
||||
font.bold: true
|
||||
verticalAlignment: TextEdit.AlignVCenter
|
||||
wrapMode: TextEdit.Wrap
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: palette.window
|
||||
border.color: Nheko.theme.separator
|
||||
border.width: 1
|
||||
radius: Nheko.paddingSmall
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
P.MessageDialog {
|
||||
id: successDialog
|
||||
|
||||
buttons: P.MessageDialog.Ok
|
||||
text: qsTr("Encryption setup successfully")
|
||||
}
|
||||
|
||||
P.MessageDialog {
|
||||
id: failureDialog
|
||||
|
||||
|
@ -80,85 +75,86 @@ Item {
|
|||
buttons: P.MessageDialog.Ok
|
||||
text: qsTr("Failed to setup encryption: %1").arg(errorMessage)
|
||||
}
|
||||
|
||||
MainWindowDialog {
|
||||
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)
|
||||
|
||||
GridLayout {
|
||||
id: grid
|
||||
|
||||
width: bootstrapCrosssigning.useableWidth
|
||||
columnSpacing: 0
|
||||
columns: 2
|
||||
rowSpacing: 0
|
||||
columnSpacing: 0
|
||||
width: bootstrapCrosssigning.useableWidth
|
||||
z: 1
|
||||
|
||||
Label {
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.columnSpan: 2
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
color: palette.text
|
||||
font.pointSize: fontMetrics.font.pointSize * 2
|
||||
text: qsTr("Setup Encryption")
|
||||
color: palette.text
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.columnSpan: 2
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.columnSpan: 1
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
Layout.preferredHeight: storeSecretsOnline.height
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
Layout.preferredHeight: storeSecretsOnline.height
|
||||
|
||||
ToggleButton {
|
||||
id: storeSecretsOnline
|
||||
|
||||
checked: true
|
||||
|
||||
onClicked: console.log("Store secrets toggled: " + checked)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.columnSpan: 1
|
||||
Layout.rowSpan: 2
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
|
||||
visible: storeSecretsOnline.checked
|
||||
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.)"
|
||||
Layout.rowSpan: 2
|
||||
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
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
Layout.topMargin: Nheko.paddingLarge
|
||||
Layout.preferredHeight: storeSecretsOnline.height
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.rowSpan: usePassword.checked ? 1 : 2
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
Layout.preferredHeight: storeSecretsOnline.height
|
||||
Layout.rowSpan: usePassword.checked ? 1 : 2
|
||||
Layout.topMargin: Nheko.paddingLarge
|
||||
visible: storeSecretsOnline.checked
|
||||
|
||||
ToggleButton {
|
||||
|
@ -166,57 +162,43 @@ Item {
|
|||
|
||||
checked: false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
id: passwordField
|
||||
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.columnSpan: 1
|
||||
Layout.fillWidth: true
|
||||
visible: storeSecretsOnline.checked && usePassword.checked
|
||||
echoMode: TextInput.Password
|
||||
}
|
||||
|
||||
Label {
|
||||
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.columnSpan: 1
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
Layout.preferredHeight: storeSecretsOnline.height
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
Layout.preferredHeight: storeSecretsOnline.height
|
||||
|
||||
ToggleButton {
|
||||
id: useOnlineKeyBackup
|
||||
|
||||
checked: true
|
||||
|
||||
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 {
|
||||
id: verifyMasterKey
|
||||
|
||||
|
@ -225,54 +207,61 @@ Item {
|
|||
GridLayout {
|
||||
id: masterGrid
|
||||
|
||||
width: verifyMasterKey.useableWidth
|
||||
columns: 1
|
||||
width: verifyMasterKey.useableWidth
|
||||
z: 1
|
||||
|
||||
Label {
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
color: palette.text
|
||||
//Layout.columnSpan: 2
|
||||
font.pointSize: fontMetrics.font.pointSize * 2
|
||||
text: qsTr("Activate Encryption")
|
||||
color: palette.text
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
//Layout.columnSpan: 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
|
||||
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
|
||||
}
|
||||
|
||||
FlatButton {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("verify")
|
||||
|
||||
onClicked: {
|
||||
SelfVerificationStatus.verifyMasterKey();
|
||||
verifyMasterKey.close();
|
||||
}
|
||||
}
|
||||
|
||||
FlatButton {
|
||||
visible: SelfVerificationStatus.hasSSSS
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("enter passphrase")
|
||||
visible: SelfVerificationStatus.hasSSSS
|
||||
|
||||
onClicked: {
|
||||
SelfVerificationStatus.verifyMasterKeyWithPassphrase();
|
||||
verifyMasterKey.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onSetupCompleted() {
|
||||
successDialog.open();
|
||||
}
|
||||
function onSetupFailed(m) {
|
||||
failureDialog.errorMessage = m;
|
||||
failureDialog.open();
|
||||
}
|
||||
function onShowRecoveryKey(key) {
|
||||
showRecoverKeyDialog.recoveryKey = key;
|
||||
showRecoverKeyDialog.open();
|
||||
}
|
||||
function onStatusChanged() {
|
||||
console.log("STATUS CHANGED: " + SelfVerificationStatus.status);
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,15 +9,9 @@ import im.nheko 1.0
|
|||
ImageButton {
|
||||
id: indicator
|
||||
|
||||
required property int status
|
||||
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: {
|
||||
switch (status) {
|
||||
case MtxEvent.Failed:
|
||||
|
@ -32,11 +26,11 @@ ImageButton {
|
|||
return "";
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
if (status == MtxEvent.Read)
|
||||
room.showReadReceipts(eventId);
|
||||
|
||||
}
|
||||
ToolTip.visible: hovered && status != MtxEvent.Empty
|
||||
changeColorOnHover: (status == MtxEvent.Read)
|
||||
cursor: (status == MtxEvent.Read) ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
height: 16
|
||||
hoverEnabled: true
|
||||
image: {
|
||||
switch (status) {
|
||||
case MtxEvent.Failed:
|
||||
|
@ -51,4 +45,10 @@ ImageButton {
|
|||
return "";
|
||||
}
|
||||
}
|
||||
width: 16
|
||||
|
||||
onClicked: {
|
||||
if (status == MtxEvent.Read)
|
||||
room.showReadReceipts(eventId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,72 +13,45 @@ import im.nheko 1.0
|
|||
AbstractButton {
|
||||
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 body
|
||||
required property string formattedBody
|
||||
required property string callType
|
||||
required property int duration
|
||||
required property int encryptionError
|
||||
required property string eventId
|
||||
required property string filename
|
||||
required property string filesize
|
||||
required property string url
|
||||
required property string thumbnailUrl
|
||||
required property bool isOnlyEmoji
|
||||
required property bool isSender
|
||||
required property bool isEncrypted
|
||||
required property string formattedBody
|
||||
required property int index
|
||||
required property bool isEditable
|
||||
required property bool isEdited
|
||||
required property bool isEncrypted
|
||||
required property bool isOnlyEmoji
|
||||
required property bool isSender
|
||||
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 roomName
|
||||
required property string roomTopic
|
||||
required property int status
|
||||
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 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
|
||||
|
||||
hoverEnabled: true
|
||||
|
||||
width: parent.width
|
||||
height: row.height + (reactionRow.height > 0 ? reactionRow.height - 2 : 0) + unreadRow.height
|
||||
hoverEnabled: true
|
||||
width: parent.width
|
||||
|
||||
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 {
|
||||
name: "dragging"
|
||||
when: draghandler.active
|
||||
|
@ -86,265 +59,292 @@ AbstractButton {
|
|||
transitions: Transition {
|
||||
from: "dragging"
|
||||
to: ""
|
||||
|
||||
PropertyAnimation {
|
||||
target: r
|
||||
properties: "x"
|
||||
easing.type: Easing.InOutQuad
|
||||
to: 0
|
||||
duration: 100
|
||||
easing.type: Easing.InOutQuad
|
||||
properties: "x"
|
||||
target: r
|
||||
to: 0
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
let link = contentItem.child.linkAt != undefined && contentItem.child.linkAt(pressX - row.x - msg.x, pressY - row.y - msg.y - contentItem.y);
|
||||
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 {
|
||||
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.leftMargin: Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8) // align bubble with section header
|
||||
height: parent.height
|
||||
visible: threadId
|
||||
width: 4
|
||||
height: parent.height
|
||||
|
||||
onClicked: room.thread = threadId
|
||||
|
||||
Rectangle {
|
||||
id: threadLine
|
||||
|
||||
color: TimelineManager.userColor(threadId, palette.base)
|
||||
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 {
|
||||
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
|
||||
color: (Settings.bubbles && !isStateEvent) ? Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.2)) : "#00000000"
|
||||
radius: 4
|
||||
border.width: r.notificationlevel == MtxEvent.Highlight ? 1 : 0
|
||||
property bool bubbleOnRight: isSender && Settings.bubbles
|
||||
property int maxWidth: (parent.width - (Settings.smallAvatars || isStateEvent ? 0 : Nheko.avatarSize + 8)) * (Settings.bubbles && !isStateEvent ? 0.9 : 1)
|
||||
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.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 {
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
margins: (Settings.bubbles && ! isStateEvent)? 4 : 2
|
||||
leftMargin: 4
|
||||
rightMargin: 4
|
||||
}
|
||||
id: msg
|
||||
rowSpacing: 0
|
||||
|
||||
columnSpacing: 2
|
||||
columns: Settings.bubbles ? 1 : 2
|
||||
rowSpacing: 0
|
||||
rows: Settings.bubbles ? 3 : 2
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: 4
|
||||
margins: (Settings.bubbles && !isStateEvent) ? 4 : 2
|
||||
right: parent.right
|
||||
rightMargin: 4
|
||||
top: parent.top
|
||||
}
|
||||
|
||||
// fancy reply, if this is a 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
|
||||
|
||||
function fromModel(role) {
|
||||
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) ?? ""
|
||||
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) ?? ""
|
||||
filename: r.relatedEventCacheBuster, fromModel(Room.Filename) ?? ""
|
||||
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
|
||||
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
|
||||
typeString: r.relatedEventCacheBuster, fromModel(Room.TypeString) ?? ""
|
||||
url: r.relatedEventCacheBuster, fromModel(Room.Url) ?? ""
|
||||
originalWidth: r.relatedEventCacheBuster, fromModel(Room.OriginalWidth) ?? 0
|
||||
isOnlyEmoji: r.relatedEventCacheBuster, fromModel(Room.IsOnlyEmoji) ?? false
|
||||
isStateEvent: r.relatedEventCacheBuster, fromModel(Room.IsStateEvent) ?? false
|
||||
userColor: r.relatedEventCacheBuster, TimelineManager.userColor(userId, palette.base)
|
||||
userId: r.relatedEventCacheBuster, fromModel(Room.UserId) ?? ""
|
||||
userName: r.relatedEventCacheBuster, fromModel(Room.UserName) ?? ""
|
||||
thumbnailUrl: r.relatedEventCacheBuster, fromModel(Room.ThumbnailUrl) ?? ""
|
||||
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
|
||||
visible: replyTo
|
||||
}
|
||||
|
||||
// actual message content
|
||||
MessageDelegate {
|
||||
Layout.row: 1
|
||||
id: contentItem
|
||||
|
||||
Layout.column: 0
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: height
|
||||
id: contentItem
|
||||
|
||||
Layout.row: 1
|
||||
blurhash: r.blurhash
|
||||
body: r.body
|
||||
formattedBody: r.formattedBody
|
||||
callType: r.callType
|
||||
duration: r.duration
|
||||
encryptionError: r.encryptionError
|
||||
eventId: r.eventId
|
||||
filename: r.filename
|
||||
filesize: r.filesize
|
||||
formattedBody: r.formattedBody
|
||||
isOnlyEmoji: r.isOnlyEmoji
|
||||
isReply: false
|
||||
isStateEvent: r.isStateEvent
|
||||
metadataWidth: metadata.width
|
||||
originalWidth: r.originalWidth
|
||||
proportionalHeight: r.proportionalHeight
|
||||
relatedEventCacheBuster: r.relatedEventCacheBuster
|
||||
roomName: r.roomName
|
||||
roomTopic: r.roomTopic
|
||||
thumbnailUrl: r.thumbnailUrl
|
||||
type: r.type
|
||||
typeString: r.typeString ?? ""
|
||||
url: r.url
|
||||
thumbnailUrl: r.thumbnailUrl
|
||||
duration: r.duration
|
||||
originalWidth: r.originalWidth
|
||||
isOnlyEmoji: r.isOnlyEmoji
|
||||
isStateEvent: r.isStateEvent
|
||||
userId: r.userId
|
||||
userName: r.userName
|
||||
roomTopic: r.roomTopic
|
||||
roomName: r.roomName
|
||||
callType: r.callType
|
||||
encryptionError: r.encryptionError
|
||||
relatedEventCacheBuster: r.relatedEventCacheBuster
|
||||
isReply: false
|
||||
metadataWidth: metadata.width
|
||||
}
|
||||
|
||||
Row {
|
||||
id: metadata
|
||||
Layout.column: Settings.bubbles? 0 : 1
|
||||
Layout.row: Settings.bubbles? 2 : 0
|
||||
Layout.rowSpan: Settings.bubbles? 1 : 2
|
||||
Layout.bottomMargin: -2
|
||||
Layout.topMargin: (contentItem.fitsMetadata && Settings.bubbles)? -height-Layout.bottomMargin : 0
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignRight
|
||||
Layout.preferredWidth: implicitWidth
|
||||
visible: !isStateEvent
|
||||
spacing: 2
|
||||
|
||||
property double scaling: Settings.bubbles? 0.75 : 1
|
||||
|
||||
property int iconSize: Math.floor(fontMetrics.ascent * scaling)
|
||||
property double scaling: Settings.bubbles ? 0.75 : 1
|
||||
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignRight
|
||||
Layout.bottomMargin: -2
|
||||
Layout.column: Settings.bubbles ? 0 : 1
|
||||
Layout.preferredWidth: implicitWidth
|
||||
Layout.row: Settings.bubbles ? 2 : 0
|
||||
Layout.rowSpan: Settings.bubbles ? 1 : 2
|
||||
Layout.topMargin: (contentItem.fitsMetadata && Settings.bubbles) ? -height - Layout.bottomMargin : 0
|
||||
spacing: 2
|
||||
visible: !isStateEvent
|
||||
|
||||
StatusIndicator {
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||
height: parent.iconSize
|
||||
width: parent.iconSize
|
||||
status: r.status
|
||||
eventId: r.eventId
|
||||
anchors.verticalCenter: ts.verticalCenter
|
||||
}
|
||||
|
||||
Image {
|
||||
visible: isEdited || eventId == room.edit
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||
eventId: r.eventId
|
||||
height: parent.iconSize
|
||||
status: r.status
|
||||
width: parent.iconSize
|
||||
sourceSize.width: parent.iconSize * Screen.devicePixelRatio
|
||||
sourceSize.height: parent.iconSize * Screen.devicePixelRatio
|
||||
source: "image://colorimage/:/icons/icons/ui/edit.svg?" + ((eventId == room.edit) ? palette.highlight : palette.buttonText)
|
||||
ToolTip.visible: editHovered.hovered
|
||||
}
|
||||
Image {
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
ToolTip.text: qsTr("Edited")
|
||||
ToolTip.visible: editHovered.hovered
|
||||
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 {
|
||||
id: editHovered
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
ImageButton {
|
||||
visible: threadId
|
||||
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.text: qsTr("Part of a thread")
|
||||
ToolTip.visible: hovered
|
||||
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
|
||||
}
|
||||
|
||||
EncryptionIndicator {
|
||||
visible: room.isEncrypted
|
||||
encrypted: isEncrypted
|
||||
trust: trustlevel
|
||||
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
|
||||
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 {
|
||||
id: ts
|
||||
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||
Layout.preferredWidth: implicitWidth
|
||||
text: timestamp.toLocaleTimeString(Locale.ShortFormat)
|
||||
color: palette.inactive.text
|
||||
ToolTip.visible: ma.hovered
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
ToolTip.text: Qt.formatDateTime(timestamp, Qt.DefaultLocaleLongDate)
|
||||
ToolTip.visible: ma.hovered
|
||||
color: palette.inactive.text
|
||||
font.pointSize: fontMetrics.font.pointSize * parent.scaling
|
||||
text: timestamp.toLocaleTimeString(Locale.ShortFormat)
|
||||
|
||||
HoverHandler {
|
||||
id: ma
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
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
|
||||
|
||||
reactions: r.reactions
|
||||
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 {
|
||||
id: unreadRow
|
||||
|
||||
color: palette.highlight
|
||||
height: visible ? 3 : 0
|
||||
visible: (r.index > 0 && (room.fullyReadEventId == r.eventId))
|
||||
|
||||
anchors {
|
||||
top: reactionRow.bottom
|
||||
topMargin: 5
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
color: palette.highlight
|
||||
|
||||
visible: (r.index > 0 && (room.fullyReadEventId == r.eventId))
|
||||
height: visible ? 3 : 0
|
||||
|
||||
top: reactionRow.bottom
|
||||
topMargin: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,86 +20,85 @@ import im.nheko.EmojiModel 1.0
|
|||
Item {
|
||||
id: timelineView
|
||||
|
||||
required property PrivacyScreen privacyScreen
|
||||
property var room: null
|
||||
property var roomPreview: null
|
||||
property bool showBackButton: false
|
||||
property bool shouldEffectsRun: false
|
||||
required property PrivacyScreen privacyScreen
|
||||
property bool showBackButton: false
|
||||
|
||||
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 {
|
||||
id: emojiPopup
|
||||
|
||||
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 {
|
||||
sequence: StandardKey.Close
|
||||
|
||||
onActivated: Rooms.resetCurrentRoom()
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: !room && !TimelineManager.isInitialSync && (!roomPreview || !roomPreview.roomid)
|
||||
anchors.centerIn: parent
|
||||
text: qsTr("No room open")
|
||||
font.pointSize: 24
|
||||
text: qsTr("No room open")
|
||||
visible: !room && !TimelineManager.isInitialSync && (!roomPreview || !roomPreview.roomid)
|
||||
}
|
||||
|
||||
Spinner {
|
||||
visible: TimelineManager.isInitialSync
|
||||
anchors.centerIn: parent
|
||||
foreground: palette.mid
|
||||
running: TimelineManager.isInitialSync
|
||||
// height is somewhat arbitrary here... don't set width because width scales w/ height
|
||||
height: parent.height / 16
|
||||
z: 3
|
||||
opacity: hh.hovered ? 0.3 : 1
|
||||
running: TimelineManager.isInitialSync
|
||||
visible: TimelineManager.isInitialSync
|
||||
z: 3
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: 100; }
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: hh
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
ColumnLayout {
|
||||
id: timelineLayout
|
||||
|
||||
visible: room != null && !room.isSpace
|
||||
enabled: visible
|
||||
anchors.fill: parent
|
||||
enabled: visible
|
||||
spacing: 0
|
||||
visible: room != null && !room.isSpace
|
||||
|
||||
TopBar {
|
||||
id: topBar
|
||||
|
||||
showBackButton: timelineView.showBackButton
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
color: Nheko.theme.separator
|
||||
height: 1
|
||||
z: 3
|
||||
color: Nheko.theme.separator
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: msgView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
color: palette.base
|
||||
|
||||
ColumnLayout {
|
||||
|
@ -118,143 +117,121 @@ Item {
|
|||
|
||||
target: timelineView
|
||||
}
|
||||
|
||||
MessageView {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: msgView.height - typingIndicator.height
|
||||
searchString: topBar.searchString
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Loader {
|
||||
source: CallManager.isOnCall && CallManager.callType != CallType.VOICE ? "voip/VideoCall.qml" : ""
|
||||
|
||||
onLoaded: TimelineManager.setVideoCallItem()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TypingIndicator {
|
||||
id: typingIndicator
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
CallInviteBar {
|
||||
id: callInviteBar
|
||||
|
||||
Layout.fillWidth: true
|
||||
z: 3
|
||||
}
|
||||
|
||||
ActiveCallBar {
|
||||
Layout.fillWidth: true
|
||||
z: 3
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
z: 3
|
||||
height: 1
|
||||
color: Nheko.theme.separator
|
||||
height: 1
|
||||
z: 3
|
||||
}
|
||||
|
||||
|
||||
UploadBox {
|
||||
}
|
||||
|
||||
MessageInputWarning {
|
||||
text: qsTr("You are about to notify the whole room")
|
||||
visible: (room && room.permissions.canPingRoom() && room.input.containsAtRoom)
|
||||
}
|
||||
|
||||
MessageInputWarning {
|
||||
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
|
||||
}
|
||||
|
||||
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 : "")
|
||||
visible: room ? room.input.containsIncompleteCommand : false
|
||||
bubbleColor: Nheko.theme.orange
|
||||
}
|
||||
|
||||
ReplyPopup {
|
||||
}
|
||||
|
||||
MessageInput {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
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 roomName: room ? room.roomName : (roomPreview ? roomPreview.roomName : "")
|
||||
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.margins: Nheko.paddingLarge
|
||||
enabled: visible
|
||||
spacing: Nheko.paddingLarge
|
||||
visible: room != null && room.isSpace || roomPreview != null
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
Avatar {
|
||||
url: parent.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
roomid: parent.roomId
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
displayName: parent.roomName
|
||||
height: 130
|
||||
width: 130
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
enabled: false
|
||||
height: 130
|
||||
roomid: parent.roomId
|
||||
url: parent.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
width: 130
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Nheko.paddingMedium
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
MatrixText {
|
||||
text: !(roomPreview?.isFetched ?? false) ? qsTr("No preview available") : preview.roomName
|
||||
font.pixelSize: 24
|
||||
text: !(roomPreview?.isFetched ?? false) ? qsTr("No preview available") : preview.roomName
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
ToolTip.text: qsTr("Settings")
|
||||
ToolTip.visible: hovered
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/settings.svg"
|
||||
visible: !!room
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Settings")
|
||||
|
||||
onClicked: TimelineManager.openRoomSettings(room.roomId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: !!room
|
||||
spacing: Nheko.paddingMedium
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: Nheko.paddingMedium
|
||||
visible: !!room
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("%n member(s)", "", room ? room.roomMemberCount : 0)
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
image: ":/icons/icons/ui/people.svg"
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
|
@ -262,54 +239,53 @@ Item {
|
|||
Layout.rightMargin: Nheko.paddingLarge
|
||||
|
||||
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
|
||||
selectByMouse: true
|
||||
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)
|
||||
|
||||
CursorShape {
|
||||
anchors.fill: parent
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
FlatButton {
|
||||
visible: roomPreview && !roomPreview.isInvite
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("join the conversation")
|
||||
visible: roomPreview && !roomPreview.isInvite
|
||||
|
||||
onClicked: Rooms.joinPreview(roomPreview.roomid)
|
||||
}
|
||||
|
||||
FlatButton {
|
||||
visible: roomPreview && roomPreview.isInvite
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("accept invite")
|
||||
visible: roomPreview && roomPreview.isInvite
|
||||
|
||||
onClicked: Rooms.acceptInvite(roomPreview.roomid)
|
||||
}
|
||||
|
||||
FlatButton {
|
||||
visible: roomPreview && roomPreview.isInvite
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("decline invite")
|
||||
visible: roomPreview && roomPreview.isInvite
|
||||
|
||||
onClicked: Rooms.declineInvite(roomPreview.roomid)
|
||||
}
|
||||
|
||||
FlatButton {
|
||||
visible: !!room
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("leave")
|
||||
visible: !!room
|
||||
|
||||
onClicked: TimelineManager.openLeaveRoomDialog(room.roomId)
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: reasonField
|
||||
|
||||
property bool showReason: false
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
@ -319,17 +295,15 @@ Item {
|
|||
visible: preview.reason !== "" && showReason
|
||||
|
||||
TextArea {
|
||||
text: TimelineManager.escapeEmoji(preview.reason)
|
||||
wrapMode: TextEdit.WordWrap
|
||||
textFormat: TextEdit.RichText
|
||||
readOnly: true
|
||||
background: null
|
||||
selectByMouse: true
|
||||
horizontalAlignment: TextEdit.AlignHCenter
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
text: TimelineManager.escapeEmoji(preview.reason)
|
||||
textFormat: TextEdit.RichText
|
||||
wrapMode: TextEdit.WordWrap
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Button {
|
||||
id: showReasonButton
|
||||
|
||||
|
@ -337,61 +311,68 @@ Item {
|
|||
//Layout.fillWidth: true
|
||||
Layout.leftMargin: Nheko.paddingLarge
|
||||
Layout.rightMargin: Nheko.paddingLarge
|
||||
|
||||
visible: preview.reason !== ""
|
||||
text: reasonField.showReason ? qsTr("Hide invite reason") : qsTr("Show invite reason")
|
||||
visible: preview.reason !== ""
|
||||
|
||||
onClicked: {
|
||||
reasonField.showReason = !reasonField.showReason;
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: room != null
|
||||
Layout.preferredHeight: Math.ceil(fontMetrics.lineSpacing * 2)
|
||||
visible: room != null
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: backToRoomsButton
|
||||
|
||||
anchors.top: parent.top
|
||||
ToolTip.text: qsTr("Back to room list")
|
||||
ToolTip.visible: hovered
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
width: Nheko.avatarSize
|
||||
height: Nheko.avatarSize
|
||||
visible: (room == null || room.isSpace) && showBackButton
|
||||
anchors.top: parent.top
|
||||
enabled: visible
|
||||
height: Nheko.avatarSize
|
||||
image: ":/icons/icons/ui/angle-arrow-left.svg"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Back to room list")
|
||||
visible: (room == null || room.isSpace) && showBackButton
|
||||
width: Nheko.avatarSize
|
||||
|
||||
onClicked: Rooms.resetCurrentRoom()
|
||||
}
|
||||
|
||||
TimelineEffects {
|
||||
id: timelineEffects
|
||||
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
NhekoDropArea {
|
||||
anchors.fill: parent
|
||||
roomid: room ? room.roomId : ""
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: effectsTimer
|
||||
onTriggered: shouldEffectsRun = false;
|
||||
|
||||
interval: timelineEffects.maxLifespan
|
||||
repeat: false
|
||||
running: false
|
||||
}
|
||||
|
||||
onTriggered: shouldEffectsRun = false
|
||||
}
|
||||
Connections {
|
||||
function onConfetti() {
|
||||
if (!Settings.fancyEffects)
|
||||
return;
|
||||
shouldEffectsRun = true;
|
||||
timelineEffects.pulseConfetti();
|
||||
room.markSpecialEffectsDone();
|
||||
}
|
||||
function onConfettiDone() {
|
||||
if (!Settings.fancyEffects)
|
||||
return;
|
||||
effectsTimer.restart();
|
||||
}
|
||||
function onOpenReadReceiptsDialog(rr) {
|
||||
var dialog = readReceiptsDialog.createObject(timelineRoot, {
|
||||
"readReceipts": rr,
|
||||
|
@ -400,9 +381,20 @@ Item {
|
|||
dialog.show();
|
||||
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) {
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/RawMessageDialog.qml")
|
||||
var component = Qt.createComponent("qrc:/qml/dialogs/RawMessageDialog.qml");
|
||||
if (component.status == Component.Ready) {
|
||||
var dialog = component.createObject(timelineRoot, {
|
||||
"rawMessage": rawMessage
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,17 +11,44 @@ Switch {
|
|||
id: toggleButton
|
||||
|
||||
implicitWidth: indicatorItem.width
|
||||
|
||||
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: [
|
||||
State {
|
||||
name: "off"
|
||||
|
||||
PropertyChanges {
|
||||
target: track
|
||||
border.color: "#767676"
|
||||
target: track
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: handle
|
||||
x: 0
|
||||
|
@ -31,10 +58,9 @@ Switch {
|
|||
name: "on"
|
||||
|
||||
PropertyChanges {
|
||||
target: track
|
||||
border.color: palette.highlight
|
||||
target: track
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: handle
|
||||
x: indicatorItem.width - handle.width
|
||||
|
@ -43,55 +69,22 @@ Switch {
|
|||
]
|
||||
transitions: [
|
||||
Transition {
|
||||
to: "off"
|
||||
reversible: true
|
||||
to: "off"
|
||||
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
target: handle
|
||||
property: "x"
|
||||
duration: 200
|
||||
easing.type: Easing.InOutQuad
|
||||
property: "x"
|
||||
target: handle
|
||||
}
|
||||
|
||||
ColorAnimation {
|
||||
target: track
|
||||
properties: "color,border.color"
|
||||
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.Window 2.15
|
||||
import im.nheko 1.0
|
||||
|
||||
import "./delegates"
|
||||
|
||||
Pane {
|
||||
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 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 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 string searchString: ""
|
||||
|
||||
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
|
||||
Connections {
|
||||
function onHideMenu() {
|
||||
roomOptionsMenu.close()
|
||||
}
|
||||
target: MainWindow
|
||||
}
|
||||
|
||||
onRoomIdChanged: {
|
||||
searchString = "";
|
||||
searchButton.searchActive = false;
|
||||
searchField.text = ""
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Find
|
||||
onActivated: searchButton.searchActive = !searchButton.searchActive
|
||||
}
|
||||
property bool showBackButton: false
|
||||
property int trustlevel: room ? room.trustlevel : Crypto.Unverified
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: topLayout.height + Nheko.paddingMedium * 2
|
||||
padding: 0
|
||||
z: 3
|
||||
|
||||
padding: 0
|
||||
background: Rectangle {
|
||||
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 {
|
||||
GridLayout {
|
||||
id: topLayout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
columnSpacing: Nheko.paddingSmall
|
||||
rowSpacing: Nheko.paddingSmall
|
||||
|
||||
|
||||
Avatar {
|
||||
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 communityId: (Settings.groupView && room && room.parentSpace && room.parentSpace.roomid) || ""
|
||||
property string communityName: (Settings.groupView && room && room.parentSpace && room.parentSpace.roomName) || ""
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.column: 1
|
||||
Layout.row: 0
|
||||
Layout.alignment: Qt.AlignRight
|
||||
width: fontMetrics.lineSpacing
|
||||
height: fontMetrics.lineSpacing
|
||||
url: avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
roomid: communityId
|
||||
displayName: communityName
|
||||
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 {
|
||||
id: communityLabel
|
||||
visible: communityAvatar.visible
|
||||
|
||||
Layout.column: 2
|
||||
Layout.row: 0
|
||||
Layout.fillWidth: true
|
||||
Layout.row: 0
|
||||
color: palette.text
|
||||
text: qsTr("In %1").arg(communityAvatar.displayName)
|
||||
maximumLineCount: 1
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
text: qsTr("In %1").arg(communityAvatar.displayName)
|
||||
textFormat: Text.RichText
|
||||
visible: communityAvatar.visible
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: backToRoomsButton
|
||||
|
||||
Layout.column: 0
|
||||
Layout.row: 1
|
||||
Layout.rowSpan: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.column: 0
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
visible: showBackButton
|
||||
image: ":/icons/icons/ui/angle-arrow-left.svg"
|
||||
ToolTip.visible: hovered
|
||||
Layout.row: 1
|
||||
Layout.rowSpan: 2
|
||||
ToolTip.text: qsTr("Back to room list")
|
||||
ToolTip.visible: hovered
|
||||
image: ":/icons/icons/ui/angle-arrow-left.svg"
|
||||
visible: showBackButton
|
||||
|
||||
onClicked: Rooms.resetCurrentRoom()
|
||||
}
|
||||
|
||||
Avatar {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.column: 1
|
||||
Layout.row: 1
|
||||
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
|
||||
enabled: false
|
||||
height: Nheko.avatarSize
|
||||
roomid: roomId
|
||||
url: avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
userid: isDirect ? directChatOtherUserId : ""
|
||||
width: Nheko.avatarSize
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.column: 2
|
||||
Layout.fillWidth: true
|
||||
Layout.row: 1
|
||||
color: palette.text
|
||||
font.pointSize: fontMetrics.font.pointSize * 1.1
|
||||
font.bold: true
|
||||
text: roomName
|
||||
maximumLineCount: 1
|
||||
elide: Text.ElideRight
|
||||
font.bold: true
|
||||
font.pointSize: fontMetrics.font.pointSize * 1.1
|
||||
maximumLineCount: 1
|
||||
text: roomName
|
||||
textFormat: Text.RichText
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
id: roomTopicC
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.column: 2
|
||||
Layout.row: 2
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
|
||||
selectByMouse: false
|
||||
enabled: false
|
||||
Layout.row: 2
|
||||
clip: true
|
||||
enabled: false
|
||||
selectByMouse: false
|
||||
text: roomTopic
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: pinButton
|
||||
|
||||
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.column: 3
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg"
|
||||
ToolTip.visible: hovered
|
||||
Layout.row: 1
|
||||
Layout.rowSpan: 2
|
||||
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: {
|
||||
var ps = Settings.hiddenPins;
|
||||
if (pinsShown) {
|
||||
|
@ -226,30 +156,20 @@ Pane {
|
|||
}
|
||||
Settings.hiddenPins = ps;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AbstractButton {
|
||||
Layout.column: 4
|
||||
Layout.row: 1
|
||||
Layout.rowSpan: 2
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.row: 1
|
||||
Layout.rowSpan: 2
|
||||
background: null
|
||||
|
||||
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.text: {
|
||||
if (!isEncrypted)
|
||||
return qsTr("Show room members.");
|
||||
|
||||
switch (trustlevel) {
|
||||
case Crypto.Verified:
|
||||
return qsTr("This room contains only verified devices.");
|
||||
|
@ -259,209 +179,257 @@ Pane {
|
|||
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)
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: searchButton
|
||||
|
||||
property bool searchActive: false
|
||||
|
||||
visible: !!room
|
||||
Layout.column: 5
|
||||
Layout.row: 1
|
||||
Layout.rowSpan: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.column: 5
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
image: ":/icons/icons/ui/search.svg"
|
||||
ToolTip.visible: hovered
|
||||
Layout.row: 1
|
||||
Layout.rowSpan: 2
|
||||
ToolTip.text: qsTr("Search this room")
|
||||
onClicked: searchActive = !searchActive
|
||||
ToolTip.visible: hovered
|
||||
image: ":/icons/icons/ui/search.svg"
|
||||
visible: !!room
|
||||
|
||||
onClicked: searchActive = !searchActive
|
||||
onSearchActiveChanged: {
|
||||
if (searchActive) {
|
||||
searchField.forceActiveFocus();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
searchField.clear();
|
||||
topBar.searchString = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: roomOptionsButton
|
||||
|
||||
visible: !!room
|
||||
Layout.column: 6
|
||||
Layout.row: 1
|
||||
Layout.rowSpan: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.column: 6
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
image: ":/icons/icons/ui/options.svg"
|
||||
ToolTip.visible: hovered
|
||||
Layout.row: 1
|
||||
Layout.rowSpan: 2
|
||||
ToolTip.text: qsTr("Room options")
|
||||
ToolTip.visible: hovered
|
||||
image: ":/icons/icons/ui/options.svg"
|
||||
visible: !!room
|
||||
|
||||
onClicked: roomOptionsMenu.open(roomOptionsButton)
|
||||
|
||||
Platform.Menu {
|
||||
id: roomOptionsMenu
|
||||
|
||||
Platform.MenuItem {
|
||||
visible: room ? room.permissions.canInvite() : false
|
||||
text: qsTr("Invite users")
|
||||
visible: room ? room.permissions.canInvite() : false
|
||||
|
||||
onTriggered: TimelineManager.openInviteUsers(roomId)
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Members")
|
||||
|
||||
onTriggered: TimelineManager.openRoomMembers(room)
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Leave room")
|
||||
|
||||
onTriggered: TimelineManager.openLeaveRoomDialog(roomId)
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Settings")
|
||||
|
||||
onTriggered: TimelineManager.openRoomSettings(roomId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: pinnedMessages
|
||||
|
||||
Layout.row: 3
|
||||
Layout.column: 2
|
||||
Layout.columnSpan: 4
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4)
|
||||
|
||||
visible: !!room && room.pinnedMessages.length > 0 && !Settings.hiddenPins.includes(roomId)
|
||||
clip: true
|
||||
|
||||
Layout.row: 3
|
||||
ScrollBar.horizontal.visible: false
|
||||
clip: true
|
||||
visible: !!room && room.pinnedMessages.length > 0 && !Settings.hiddenPins.includes(roomId)
|
||||
|
||||
ListView {
|
||||
|
||||
spacing: Nheko.paddingSmall
|
||||
model: room ? room.pinnedMessages : undefined
|
||||
spacing: Nheko.paddingSmall
|
||||
|
||||
delegate: RowLayout {
|
||||
required property string modelData
|
||||
|
||||
width: ListView.view.width
|
||||
height: implicitHeight
|
||||
width: ListView.view.width
|
||||
|
||||
Reply {
|
||||
id: reply
|
||||
|
||||
property var e: room ? room.getDump(modelData, "pins") : {}
|
||||
Connections {
|
||||
function onPinnedMessagesChanged() { reply.e = room.getDump(modelData, "pins") }
|
||||
target: room
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: height
|
||||
|
||||
userColor: TimelineManager.userColor(e.userId, palette.window)
|
||||
blurhash: e.blurhash ?? ""
|
||||
body: e.body ?? ""
|
||||
formattedBody: e.formattedBody ?? ""
|
||||
encryptionError: e.encryptionError ?? 0
|
||||
eventId: e.eventId ?? ""
|
||||
filename: e.filename ?? ""
|
||||
filesize: e.filesize ?? ""
|
||||
formattedBody: e.formattedBody ?? ""
|
||||
isOnlyEmoji: e.isOnlyEmoji ?? false
|
||||
keepFullText: true
|
||||
originalWidth: e.originalWidth ?? 0
|
||||
proportionalHeight: e.proportionalHeight ?? 1
|
||||
type: e.type ?? MtxEvent.UnknownMessage
|
||||
typeString: e.typeString ?? ""
|
||||
url: e.url ?? ""
|
||||
originalWidth: e.originalWidth ?? 0
|
||||
isOnlyEmoji: e.isOnlyEmoji ?? false
|
||||
userColor: TimelineManager.userColor(e.userId, palette.window)
|
||||
userId: e.userId ?? ""
|
||||
userName: e.userName ?? ""
|
||||
encryptionError: e.encryptionError ?? 0
|
||||
keepFullText: true
|
||||
|
||||
Connections {
|
||||
function onPinnedMessagesChanged() {
|
||||
reply.e = room.getDump(modelData, "pins");
|
||||
}
|
||||
|
||||
target: room
|
||||
}
|
||||
}
|
||||
ImageButton {
|
||||
id: deletePinButton
|
||||
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
|
||||
Layout.preferredHeight: 16
|
||||
Layout.preferredWidth: 16
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
|
||||
visible: room.permissions.canChange(MtxEvent.PinnedEvents)
|
||||
|
||||
ToolTip.text: qsTr("Unpin")
|
||||
ToolTip.visible: hovered
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/dismiss.svg"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Unpin")
|
||||
visible: room.permissions.canChange(MtxEvent.PinnedEvents)
|
||||
|
||||
onClicked: room.unpin(modelData)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: widgets
|
||||
|
||||
Layout.row: 4
|
||||
Layout.column: 2
|
||||
Layout.columnSpan: 4
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 1.5)
|
||||
|
||||
visible: !!room && room.widgetLinks.length > 0 && !Settings.hiddenWidgets.includes(roomId)
|
||||
clip: true
|
||||
|
||||
Layout.row: 4
|
||||
ScrollBar.horizontal.visible: false
|
||||
clip: true
|
||||
visible: !!room && room.widgetLinks.length > 0 && !Settings.hiddenWidgets.includes(roomId)
|
||||
|
||||
ListView {
|
||||
|
||||
spacing: Nheko.paddingSmall
|
||||
model: room ? room.widgetLinks : undefined
|
||||
spacing: Nheko.paddingSmall
|
||||
|
||||
delegate: MatrixText {
|
||||
required property var modelData
|
||||
|
||||
color: palette.text
|
||||
text: modelData
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
id: searchField
|
||||
visible: searchButton.searchActive
|
||||
enabled: visible
|
||||
hasClear: true
|
||||
|
||||
Layout.row: 5
|
||||
Layout.column: 2
|
||||
Layout.columnSpan: 4
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.row: 5
|
||||
enabled: visible
|
||||
hasClear: true
|
||||
placeholderText: qsTr("Enter search query")
|
||||
visible: searchButton.searchActive
|
||||
|
||||
onAccepted: topBar.searchString = text
|
||||
}
|
||||
}
|
||||
|
||||
CursorShape {
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: (pinnedMessages.visible ? pinnedMessages.height : 0) + (widgets.visible ? widgets.height : 0)
|
||||
anchors.fill: parent
|
||||
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
|
||||
|
||||
Item {
|
||||
implicitHeight: Math.max(fontMetrics.height * 1.2, typingDisplay.height)
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: Math.max(fontMetrics.height * 1.2, typingDisplay.height)
|
||||
|
||||
Rectangle {
|
||||
id: typingRect
|
||||
|
||||
visible: (room && room.typingUsers.length > 0)
|
||||
color: palette.base
|
||||
anchors.fill: parent
|
||||
color: palette.base
|
||||
visible: (room && room.typingUsers.length > 0)
|
||||
z: 3
|
||||
|
||||
Label {
|
||||
id: typingDisplay
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.bottom: parent.bottom
|
||||
color: palette.text
|
||||
text: room ? room.formatTypingUsers(room.typingUsers, palette.base) : ""
|
||||
textFormat: Text.RichText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
import "./components"
|
||||
import "./ui"
|
||||
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.5
|
||||
import QtQuick.Layouts 1.3
|
||||
|
@ -12,31 +11,33 @@ import im.nheko 1.0
|
|||
|
||||
Page {
|
||||
id: uploadPopup
|
||||
visible: room && room.input.uploads.length > 0
|
||||
Layout.preferredHeight: 200
|
||||
clip: true
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.preferredHeight: 200
|
||||
clip: true
|
||||
padding: Nheko.paddingMedium
|
||||
visible: room && room.input.uploads.length > 0
|
||||
|
||||
background: Rectangle {
|
||||
color: palette.base
|
||||
}
|
||||
contentItem: ListView {
|
||||
id: uploadsList
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
model: room ? room.input.uploads : undefined
|
||||
orientation: ListView.Horizontal
|
||||
spacing: Nheko.paddingMedium
|
||||
width: Math.min(contentWidth, parent.availableWidth)
|
||||
|
||||
ScrollBar.horizontal: ScrollBar {
|
||||
id: scr
|
||||
|
||||
}
|
||||
|
||||
orientation: ListView.Horizontal
|
||||
width: Math.min(contentWidth, parent.availableWidth)
|
||||
model: room ? room.input.uploads : undefined
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
delegate: Pane {
|
||||
padding: Nheko.paddingSmall
|
||||
height: uploadPopup.availableHeight - buttons.height - (scr.visible ? scr.height : 0)
|
||||
padding: Nheko.paddingSmall
|
||||
width: uploadPopup.availableHeight - buttons.height
|
||||
|
||||
background: Rectangle {
|
||||
|
@ -45,46 +46,48 @@ Page {
|
|||
}
|
||||
contentItem: ColumnLayout {
|
||||
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.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.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 {
|
||||
id: namefield
|
||||
|
||||
Layout.fillWidth: true
|
||||
text: modelData.filename
|
||||
|
||||
onTextEdited: modelData.filename = text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: DialogButtonBox {
|
||||
id: buttons
|
||||
|
||||
standardButtons: DialogButtonBox.Cancel
|
||||
Button {
|
||||
text: qsTr("Upload %n file(s)", "", (room ? room.input.uploads.length : 0))
|
||||
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
|
||||
}
|
||||
|
||||
onAccepted: room.input.acceptUploads()
|
||||
onRejected: room.input.declineUploads()
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: palette.base
|
||||
Button {
|
||||
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
|
||||
text: qsTr("Upload %n file(s)", "", (room ? room.input.uploads.length : 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue