Enable qmlformat in linting

This commit is contained in:
Loren Burkholder 2023-10-30 22:11:03 -04:00
parent ee6ee46e7c
commit 814d9c6a17
No known key found for this signature in database
GPG key ID: AB62CB312CEC2BBC
94 changed files with 4317 additions and 4984 deletions

View file

@ -15,6 +15,14 @@ do
clang-format -i "$f" clang-format -i "$f"
done; done;
if command -v /usr/lib64/qt6/bin/qmlformat &> /dev/null; then
/usr/lib64/qt6/bin/qmlformat -i $QML_FILES
elif command -v /usr/lib/qt6/bin/qmlformat &> /dev/null; then
/usr/lib/qt6/bin/qmlformat -i $QML_FILES
else
echo "No qmlformat found, skipping check!"
fi
git diff --exit-code git diff --exit-code
if command -v /usr/lib64/qt6/bin/qmllint &> /dev/null; then if command -v /usr/lib64/qt6/bin/qmllint &> /dev/null; then

View file

@ -35,9 +35,9 @@ Page {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
boundsBehavior: Flickable.StopAtBounds
height: parent.height height: parent.height
model: Communities.filtered() model: Communities.filtered()
boundsBehavior: Flickable.StopAtBounds
ScrollBar.vertical: ScrollBar { ScrollBar.vertical: ScrollBar {
id: scrollbar id: scrollbar
@ -138,10 +138,11 @@ Page {
id: avatar id: avatar
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: avatarSize
Layout.preferredWidth: avatarSize
color: communityItem.backgroundColor color: communityItem.backgroundColor
displayName: model.displayName displayName: model.displayName
enabled: false enabled: false
Layout.preferredHeight: avatarSize
roomid: model.id roomid: model.id
textColor: model.avatarUrl?.startsWith(":/") == true ? communityItem.unimportantText : communityItem.importantText textColor: model.avatarUrl?.startsWith(":/") == true ? communityItem.unimportantText : communityItem.importantText
url: { url: {
@ -152,7 +153,6 @@ Page {
else else
return ""; return "";
} }
Layout.preferredWidth: avatarSize
NotificationBubble { NotificationBubble {
anchors.bottom: avatar.bottom anchors.bottom: avatar.bottom

View file

@ -149,10 +149,10 @@ Control {
spacing: rowSpacing spacing: rowSpacing
Avatar { Avatar {
displayName: model.displayName
enabled: false
Layout.preferredHeight: popup.avatarHeight Layout.preferredHeight: popup.avatarHeight
Layout.preferredWidth: popup.avatarWidth Layout.preferredWidth: popup.avatarWidth
displayName: model.displayName
enabled: false
url: model.avatarUrl.replace("mxc://", "image://MxcImage/") url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
userid: model.userid userid: model.userid
} }
@ -180,14 +180,14 @@ Control {
visible: !!model.unicode visible: !!model.unicode
} }
Avatar { Avatar {
Layout.preferredHeight: popup.avatarHeight
Layout.preferredWidth: popup.avatarWidth
crop: false crop: false
displayName: model.shortcode displayName: model.shortcode
enabled: false enabled: false
Layout.preferredHeight: popup.avatarHeight
//userid: model.shortcode //userid: model.shortcode
url: (model.url ? model.url : "").replace("mxc://", "image://MxcImage/") url: (model.url ? model.url : "").replace("mxc://", "image://MxcImage/")
visible: !model.unicode visible: !model.unicode
Layout.preferredWidth: popup.avatarWidth
} }
Label { Label {
Layout.leftMargin: Nheko.paddingSmall Layout.leftMargin: Nheko.paddingSmall
@ -227,12 +227,12 @@ Control {
spacing: rowSpacing spacing: rowSpacing
Avatar { Avatar {
Layout.preferredHeight: popup.avatarHeight
Layout.preferredWidth: popup.avatarWidth
displayName: model.roomName displayName: model.roomName
enabled: false enabled: false
Layout.preferredHeight: popup.avatarHeight
roomid: model.roomid roomid: model.roomid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/") url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
Layout.preferredWidth: popup.avatarWidth
} }
Label { Label {
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
@ -251,12 +251,12 @@ Control {
spacing: rowSpacing spacing: rowSpacing
Avatar { Avatar {
Layout.preferredHeight: popup.avatarHeight
Layout.preferredWidth: popup.avatarWidth
displayName: model.roomName displayName: model.roomName
enabled: false enabled: false
Layout.preferredHeight: popup.avatarHeight
roomid: model.roomid roomid: model.roomid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/") url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
Layout.preferredWidth: popup.avatarWidth
} }
Label { Label {
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text color: model.index == popup.currentIndex ? palette.highlightedText : palette.text

View file

@ -55,8 +55,8 @@ Popup {
id: replyPreview id: replyPreview
eventId: mid eventId: mid
userColor: TimelineManager.userColor(replyPreview.userId, palette.window)
maxWidth: parent.width maxWidth: parent.width
userColor: TimelineManager.userColor(replyPreview.userId, palette.window)
} }
MatrixTextField { MatrixTextField {
id: roomTextInput id: roomTextInput
@ -64,7 +64,7 @@ Popup {
color: palette.text color: palette.text
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2 width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
Keys.onPressed: (event) => { Keys.onPressed: event => {
if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) { if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
event.accepted = true; event.accepted = true;
completerPopup.up(); completerPopup.up();

View file

@ -140,8 +140,8 @@ ColumnLayout {
id: blueBar id: blueBar
Layout.fillWidth: true Layout.fillWidth: true
color: palette.highlight
Layout.preferredHeight: 1 Layout.preferredHeight: 1
color: palette.highlight
Rectangle { Rectangle {
id: blackBar id: blackBar

View file

@ -44,14 +44,14 @@ Rectangle {
ImageButton { ImageButton {
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
Layout.margins: 8 Layout.margins: 8
Layout.preferredHeight: 22
Layout.preferredWidth: 22
ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : (CallManager.isOnCallOnOtherDevice ? qsTr("Already on a call") : qsTr("Place a call")) ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : (CallManager.isOnCallOnOtherDevice ? qsTr("Already on a call") : qsTr("Place a call"))
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.preferredHeight: 22
hoverEnabled: true hoverEnabled: true
image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.svg" : ":/icons/icons/ui/place-call.svg" image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.svg" : ":/icons/icons/ui/place-call.svg"
opacity: (CallManager.haveCallInvite || CallManager.isOnCallOnOtherDevice) ? 0.3 : 1 opacity: (CallManager.haveCallInvite || CallManager.isOnCallOnOtherDevice) ? 0.3 : 1
visible: CallManager.callsSupported && showAllButtons visible: CallManager.callsSupported && showAllButtons
Layout.preferredWidth: 22
onClicked: { onClicked: {
if (room) { if (room) {
@ -72,13 +72,13 @@ Rectangle {
ImageButton { ImageButton {
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
Layout.margins: 8 Layout.margins: 8
Layout.preferredHeight: 22
Layout.preferredWidth: 22
ToolTip.text: qsTr("Send a file") ToolTip.text: qsTr("Send a file")
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.preferredHeight: 22
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/attach.svg" image: ":/icons/icons/ui/attach.svg"
visible: showAllButtons visible: showAllButtons
Layout.preferredWidth: 22
onClicked: room.input.openFileSelection() onClicked: room.input.openFileSelection()
@ -393,13 +393,13 @@ Rectangle {
Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.alignment: Qt.AlignRight | Qt.AlignBottom
Layout.margins: 8 Layout.margins: 8
Layout.preferredHeight: 22
Layout.preferredWidth: 22
ToolTip.text: qsTr("Stickers") ToolTip.text: qsTr("Stickers")
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.preferredHeight: 22
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/sticky-note-solid.svg" image: ":/icons/icons/ui/sticky-note-solid.svg"
visible: showAllButtons visible: showAllButtons
Layout.preferredWidth: 22
onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId, function (row) { onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId, function (row) {
room.input.sticker(row); room.input.sticker(row);
@ -417,12 +417,12 @@ Rectangle {
Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.alignment: Qt.AlignRight | Qt.AlignBottom
Layout.margins: 8 Layout.margins: 8
Layout.preferredHeight: 22
Layout.preferredWidth: 22
ToolTip.text: qsTr("Emoji") ToolTip.text: qsTr("Emoji")
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.preferredHeight: 22
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/smile.svg" image: ":/icons/icons/ui/smile.svg"
Layout.preferredWidth: 22
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, room.roomId, function (plaintext, markdown) { onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, room.roomId, function (plaintext, markdown) {
messageInput.insert(messageInput.cursorPosition, markdown); messageInput.insert(messageInput.cursorPosition, markdown);
@ -438,13 +438,13 @@ Rectangle {
ImageButton { ImageButton {
Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.alignment: Qt.AlignRight | Qt.AlignBottom
Layout.margins: 8 Layout.margins: 8
Layout.preferredHeight: 22
Layout.preferredWidth: 22
Layout.rightMargin: 8 Layout.rightMargin: 8
ToolTip.text: qsTr("Send") ToolTip.text: qsTr("Send")
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.preferredHeight: 22
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/send.svg" image: ":/icons/icons/ui/send.svg"
Layout.preferredWidth: 22
onClicked: { onClicked: {
room.input.send(); room.input.send();

View file

@ -16,8 +16,8 @@ Item {
property int availableWidth: width property int availableWidth: width
property int padding: Nheko.paddingMedium property int padding: Nheko.paddingMedium
property string searchString: ""
property Room roommodel: room property Room roommodel: room
property string searchString: ""
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu // HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
Connections { Connections {
@ -41,6 +41,7 @@ Item {
property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < chatRoot.availableWidth) ? Settings.timelineMaxWidth : chatRoot.availableWidth) - chatRoot.padding * 2 - (scrollbar.interactive ? scrollbar.width : 0) property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < chatRoot.availableWidth) ? Settings.timelineMaxWidth : chatRoot.availableWidth) - chatRoot.padding * 2 - (scrollbar.interactive ? scrollbar.width : 0)
readonly property alias filteringInProgress: filteredTimeline.filteringInProgress readonly property alias filteringInProgress: filteredTimeline.filteringInProgress
property int lastScrollPos: 0
ScrollBar.vertical: scrollbar ScrollBar.vertical: scrollbar
anchors.fill: parent anchors.fill: parent
@ -49,6 +50,7 @@ Item {
//onModelChanged: if (room) room.sendReset() //onModelChanged: if (room) room.sendReset()
//reuseItems: true //reuseItems: true
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
delegate: Settings.bubbles ? bubbleMessageStyle : defaultMessageStyle
displayMarginBeginning: height / 4 displayMarginBeginning: height / 4
displayMarginEnd: height / 4 displayMarginEnd: height / 4
model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? filteredTimeline : room model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? filteredTimeline : room
@ -56,35 +58,6 @@ Item {
spacing: 2 spacing: 2
verticalLayoutDirection: ListView.BottomToTop verticalLayoutDirection: ListView.BottomToTop
property int lastScrollPos: 0
// Fixup the scroll position when the height changes. Without this, the view is kept around the center of the currently visible content, while we usually want to stick to the bottom.
onMovementEnded: lastScrollPos = (contentY+height)
onModelChanged: lastScrollPos = (contentY+height)
onHeightChanged: contentY = (lastScrollPos-height)
Component {
id: defaultMessageStyle
TimelineDefaultMessageStyle {
messageActions: messageActionsC
messageContextMenu: messageContextMenuC
replyContextMenu: replyContextMenuC
scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
}
}
Component {
id: bubbleMessageStyle
TimelineBubbleMessageStyle {
messageActions: messageActionsC
messageContextMenu: messageContextMenuC
replyContextMenu: replyContextMenuC
scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
}
}
delegate: Settings.bubbles ? bubbleMessageStyle : defaultMessageStyle
footer: Item { footer: Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.margins: Nheko.paddingLarge anchors.margins: Nheko.paddingLarge
@ -109,7 +82,32 @@ Item {
if (atYEnd && room) if (atYEnd && room)
model.currentIndex = 0; model.currentIndex = 0;
} }
onHeightChanged: contentY = (lastScrollPos - height)
onModelChanged: lastScrollPos = (contentY + height)
// Fixup the scroll position when the height changes. Without this, the view is kept around the center of the currently visible content, while we usually want to stick to the bottom.
onMovementEnded: lastScrollPos = (contentY + height)
Component {
id: defaultMessageStyle
TimelineDefaultMessageStyle {
messageActions: messageActionsC
messageContextMenu: messageContextMenuC
replyContextMenu: replyContextMenuC
scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
}
}
Component {
id: bubbleMessageStyle
TimelineBubbleMessageStyle {
messageActions: messageActionsC
messageContextMenu: messageContextMenuC
replyContextMenu: replyContextMenuC
scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
}
}
TimelineFilter { TimelineFilter {
id: filteredTimeline id: filteredTimeline
@ -124,13 +122,13 @@ Item {
// use comma to update on scroll // use comma to update on scroll
property alias model: row.model property alias model: row.model
hoverEnabled: true
padding: Nheko.paddingSmall
visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || hovered)
z: 10
parent: chat.contentItem
anchors.bottom: attached?.top anchors.bottom: attached?.top
anchors.right: attached?.right anchors.right: attached?.right
hoverEnabled: true
padding: Nheko.paddingSmall
parent: chat.contentItem
visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || hovered)
z: 10
background: Rectangle { background: Rectangle {
border.color: palette.buttonText border.color: palette.buttonText
@ -200,6 +198,7 @@ Item {
} }
} }
ImageButton { ImageButton {
Layout.preferredWidth: 16
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Edit") ToolTip.text: qsTr("Edit")
ToolTip.visible: hovered ToolTip.visible: hovered
@ -207,7 +206,6 @@ Item {
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/edit.svg" image: ":/icons/icons/ui/edit.svg"
visible: !!row.model && row.model.isEditable visible: !!row.model && row.model.isEditable
Layout.preferredWidth: 16
onClicked: { onClicked: {
if (row.model.isEditable) if (row.model.isEditable)
@ -217,13 +215,13 @@ Item {
ImageButton { ImageButton {
id: reactButton id: reactButton
Layout.preferredWidth: 16
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("React") ToolTip.text: qsTr("React")
ToolTip.visible: hovered ToolTip.visible: hovered
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/smile-add.svg" image: ":/icons/icons/ui/smile-add.svg"
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false
Layout.preferredWidth: 16
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, room.roomId, function (plaintext, markdown) { onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, room.roomId, function (plaintext, markdown) {
var event_id = row.model ? row.model.eventId : ""; var event_id = row.model ? row.model.eventId : "";
@ -232,28 +230,29 @@ Item {
}) })
} }
ImageButton { ImageButton {
Layout.preferredWidth: 16
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: (row.model && row.model.threadId) ? qsTr("Reply in thread") : qsTr("New thread") ToolTip.text: (row.model && row.model.threadId) ? qsTr("Reply in thread") : qsTr("New thread")
ToolTip.visible: hovered ToolTip.visible: hovered
hoverEnabled: true hoverEnabled: true
image: (row.model && row.model.threadId) ? ":/icons/icons/ui/thread.svg" : ":/icons/icons/ui/new-thread.svg" image: (row.model && row.model.threadId) ? ":/icons/icons/ui/thread.svg" : ":/icons/icons/ui/new-thread.svg"
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
Layout.preferredWidth: 16
onClicked: room.thread = (row.model.threadId || row.model.eventId) onClicked: room.thread = (row.model.threadId || row.model.eventId)
} }
ImageButton { ImageButton {
Layout.preferredWidth: 16
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Reply") ToolTip.text: qsTr("Reply")
ToolTip.visible: hovered ToolTip.visible: hovered
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/reply.svg" image: ":/icons/icons/ui/reply.svg"
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
Layout.preferredWidth: 16
onClicked: room.reply = row.model.eventId onClicked: room.reply = row.model.eventId
} }
ImageButton { ImageButton {
Layout.preferredWidth: 16
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Go to message") ToolTip.text: qsTr("Go to message")
ToolTip.visible: hovered ToolTip.visible: hovered
@ -261,7 +260,6 @@ Item {
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/go-to.svg" image: ":/icons/icons/ui/go-to.svg"
visible: !!row.model && filteredTimeline.filterByContent visible: !!row.model && filteredTimeline.filterByContent
Layout.preferredWidth: 16
onClicked: { onClicked: {
topBar.searchString = ""; topBar.searchString = "";
@ -271,12 +269,12 @@ Item {
ImageButton { ImageButton {
id: optionsButton id: optionsButton
Layout.preferredWidth: 16
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Options") ToolTip.text: qsTr("Options")
ToolTip.visible: hovered ToolTip.visible: hovered
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/options.svg" image: ":/icons/icons/ui/options.svg"
Layout.preferredWidth: 16
onClicked: messageContextMenuC.show(row.model.eventId, row.model.threadId, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton) onClicked: messageContextMenuC.show(row.model.eventId, row.model.threadId, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton)
} }
@ -413,9 +411,9 @@ Item {
Component { Component {
id: reportDialog id: reportDialog
ReportMessage {} ReportMessage {
}
} }
Platform.MenuItem { Platform.MenuItem {
enabled: visible enabled: visible
text: qsTr("Go to &message") text: qsTr("Go to &message")
@ -523,10 +521,13 @@ Item {
} }
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Report message")
enabled: visible enabled: visible
text: qsTr("Report message")
onTriggered: function () { onTriggered: function () {
var dialog = reportDialog.createObject(timelineRoot, {"eventId": messageContextMenu.eventId}); var dialog = reportDialog.createObject(timelineRoot, {
"eventId": messageContextMenu.eventId
});
dialog.show(); dialog.show();
dialog.forceActiveFocus(); dialog.forceActiveFocus();
timelineRoot.destroyOnClose(dialog); timelineRoot.destroyOnClose(dialog);

View file

@ -50,8 +50,8 @@ Item {
name: "Visible" name: "Visible"
PropertyChanges { PropertyChanges {
screenSaver.visible: true
screenSaver.opacity: 1 screenSaver.opacity: 1
screenSaver.visible: true
} }
}, },
State { State {

View file

@ -30,9 +30,9 @@ Rectangle {
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: Nheko.paddingSmall anchors.topMargin: Nheko.paddingSmall
eventId: room?.reply ?? "" eventId: room?.reply ?? ""
maxWidth: parent.width - anchors.leftMargin - anchors.rightMargin
userColor: TimelineManager.userColor(modelData.userId, palette.window) userColor: TimelineManager.userColor(modelData.userId, palette.window)
visible: room && room.reply visible: room && room.reply
maxWidth: parent.width - anchors.leftMargin - anchors.rightMargin
} }
ImageButton { ImageButton {
id: closeReplyButton id: closeReplyButton

View file

@ -26,8 +26,8 @@ Page {
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
color: Nheko.theme.separator
Layout.preferredHeight: 1 Layout.preferredHeight: 1
color: Nheko.theme.separator
} }
Pane { Pane {
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
@ -45,11 +45,11 @@ Page {
ImageButton { ImageButton {
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium Layout.margins: Nheko.paddingMedium
Layout.preferredHeight: 22
Layout.preferredWidth: 22
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Start a new chat") ToolTip.text: qsTr("Start a new chat")
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.preferredHeight: 22
Layout.preferredWidth: 22
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/add-square-button.svg" image: ":/icons/icons/ui/add-square-button.svg"
@ -97,11 +97,11 @@ Page {
ImageButton { ImageButton {
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium Layout.margins: Nheko.paddingMedium
Layout.preferredHeight: 22
Layout.preferredWidth: 22
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Room directory") ToolTip.text: qsTr("Room directory")
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.preferredHeight: 22
Layout.preferredWidth: 22
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/room-directory.svg" image: ":/icons/icons/ui/room-directory.svg"
visible: !collapsed visible: !collapsed
@ -115,11 +115,11 @@ Page {
ImageButton { ImageButton {
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium Layout.margins: Nheko.paddingMedium
Layout.preferredHeight: 22
Layout.preferredWidth: 22
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Search rooms (Ctrl+K)") ToolTip.text: qsTr("Search rooms (Ctrl+K)")
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.preferredHeight: 22
Layout.preferredWidth: 22
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/search.svg" image: ":/icons/icons/ui/search.svg"
ripple: false ripple: false
@ -139,11 +139,11 @@ Page {
ImageButton { ImageButton {
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium Layout.margins: Nheko.paddingMedium
Layout.preferredHeight: 22
Layout.preferredWidth: 22
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("User settings") ToolTip.text: qsTr("User settings")
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.preferredHeight: 22
Layout.preferredWidth: 22
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/settings.svg" image: ":/icons/icons/ui/settings.svg"
ripple: false ripple: false
@ -268,37 +268,45 @@ Page {
} }
Platform.MenuSeparator { Platform.MenuSeparator {
} }
Platform.MenuItemGroup { Platform.MenuItemGroup {
id: onlineStateGroup id: onlineStateGroup
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Automatic online status")
group: onlineStateGroup
checkable: true checkable: true
checked: Settings.presence == Settings.AutomaticPresence checked: Settings.presence == Settings.AutomaticPresence
onTriggered: if (checked) Settings.presence = Settings.AutomaticPresence group: onlineStateGroup
text: qsTr("Automatic online status")
onTriggered: if (checked)
Settings.presence = Settings.AutomaticPresence
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Online")
group: onlineStateGroup
checkable: true checkable: true
checked: Settings.presence == Settings.Online checked: Settings.presence == Settings.Online
onTriggered: if (checked) Settings.presence = Settings.Online group: onlineStateGroup
text: qsTr("Online")
onTriggered: if (checked)
Settings.presence = Settings.Online
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Unavailable")
group: onlineStateGroup
checkable: true checkable: true
checked: Settings.presence == Settings.Unavailable checked: Settings.presence == Settings.Unavailable
onTriggered: if (checked) Settings.presence = Settings.Unavailable group: onlineStateGroup
text: qsTr("Unavailable")
onTriggered: if (checked)
Settings.presence = Settings.Unavailable
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Offline")
group: onlineStateGroup
checkable: true checkable: true
checked: Settings.presence == Settings.Offline checked: Settings.presence == Settings.Offline
onTriggered: if (checked) Settings.presence = Settings.Offline group: onlineStateGroup
text: qsTr("Offline")
onTriggered: if (checked)
Settings.presence = Settings.Offline
} }
} }
TapHandler { TapHandler {
@ -319,8 +327,8 @@ Page {
} }
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
color: Nheko.theme.separator
Layout.preferredHeight: 2 Layout.preferredHeight: 2
color: Nheko.theme.separator
} }
Rectangle { Rectangle {
id: unverifiedStuffBubble id: unverifiedStuffBubble
@ -366,14 +374,14 @@ Page {
id: closeUnverifiedBubble id: closeUnverifiedBubble
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
Layout.preferredHeight: fontMetrics.font.pixelSize
Layout.preferredWidth: fontMetrics.font.pixelSize
Layout.rightMargin: Nheko.paddingMedium Layout.rightMargin: Nheko.paddingMedium
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Close") ToolTip.text: qsTr("Close")
ToolTip.visible: closeUnverifiedBubble.hovered ToolTip.visible: closeUnverifiedBubble.hovered
Layout.preferredHeight: fontMetrics.font.pixelSize
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/dismiss.svg" image: ":/icons/icons/ui/dismiss.svg"
Layout.preferredWidth: fontMetrics.font.pixelSize
onClicked: unverifiedStuffBubble.visible = false onClicked: unverifiedStuffBubble.visible = false
} }
@ -398,8 +406,8 @@ Page {
} }
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
color: Nheko.theme.separator
Layout.preferredHeight: 1 Layout.preferredHeight: 1
color: Nheko.theme.separator
visible: unverifiedStuffBubble.visible visible: unverifiedStuffBubble.visible
} }
} }
@ -436,9 +444,9 @@ Page {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
boundsBehavior: Flickable.StopAtBounds
height: parent.height height: parent.height
model: Rooms model: Rooms
boundsBehavior: Flickable.StopAtBounds
//reuseItems: true //reuseItems: true
ScrollBar.vertical: ScrollBar { ScrollBar.vertical: ScrollBar {
@ -550,13 +558,13 @@ Page {
id: avatar id: avatar
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: avatarSize
Layout.preferredWidth: avatarSize
displayName: roomName displayName: roomName
enabled: false enabled: false
roomid: roomId roomid: roomId
url: avatarUrl.replace("mxc://", "image://MxcImage/") url: avatarUrl.replace("mxc://", "image://MxcImage/")
userid: isDirect ? directChatOtherUserId : "" userid: isDirect ? directChatOtherUserId : ""
Layout.preferredWidth: avatarSize
Layout.preferredHeight: avatarSize
NotificationBubble { NotificationBubble {
id: collapsedNotificationBubble id: collapsedNotificationBubble
@ -576,8 +584,8 @@ Page {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.minimumWidth: 100 Layout.minimumWidth: 100
Layout.preferredWidth: roomItem.width - avatar.width
Layout.preferredHeight: avatar.height Layout.preferredHeight: avatar.height
Layout.preferredWidth: roomItem.width - avatar.width
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
visible: !collapsed visible: !collapsed

View file

@ -9,54 +9,53 @@ import im.nheko
TimelineEvent { TimelineEvent {
id: wrapper id: wrapper
ListView.delayRemove: true
width: chat.delegateMaxWidth
height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 10)
anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter
//room: chatRoot.roommodel
required property var day
required property bool isSender
required property int index
property var previousMessageDay: (index + 1) >= chat.count ? 0 : chat.model.dataByIndex(index + 1, Room.Day)
property bool previousMessageIsStateEvent: (index + 1) >= chat.count ? true : chat.model.dataByIndex(index + 1, Room.IsStateEvent)
property string previousMessageUserId: (index + 1) >= chat.count ? "" : chat.model.dataByIndex(index + 1, Room.UserId)
required property date timestamp
required property string userId
required property string userName
required property string threadId
required property int userPowerlevel
required property bool isEdited
required property bool isEncrypted
required property var reactions
required property int status
required property int trustlevel
required property int notificationlevel
required property int type
required property bool isEditable
required property QtObject messageContextMenu
required property QtObject replyContextMenu
required property Item messageActions
property int avatarMargin: (wrapper.isStateEvent || Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8)) // align bubble with section header property int avatarMargin: (wrapper.isStateEvent || Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8)) // align bubble with section header
property alias hovered: messageHover.hovered
property bool scrolledToThis: false
mainInset: (threadId ? (4 + Nheko.paddingSmall) : 0) + 4
replyInset: mainInset + 4 + Nheko.paddingSmall
property int bubbleMargin: 40 property int bubbleMargin: 40
//room: chatRoot.roommodel
required property var day
property alias hovered: messageHover.hovered
required property int index
required property bool isEditable
required property bool isEdited
required property bool isEncrypted
required property bool isSender
required property Item messageActions
required property QtObject messageContextMenu
required property int notificationlevel
property var previousMessageDay: (index + 1) >= chat.count ? 0 : chat.model.dataByIndex(index + 1, Room.Day)
property bool previousMessageIsStateEvent: (index + 1) >= chat.count ? true : chat.model.dataByIndex(index + 1, Room.IsStateEvent)
property string previousMessageUserId: (index + 1) >= chat.count ? "" : chat.model.dataByIndex(index + 1, Room.UserId)
required property var reactions
required property QtObject replyContextMenu
property bool scrolledToThis: false
required property int status
required property string threadId
required property date timestamp
required property int trustlevel
required property int type
required property string userId
required property string userName
required property int userPowerlevel
ListView.delayRemove: true
anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter
height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 10)
mainInset: (threadId ? (4 + Nheko.paddingSmall) : 0) + 4
maxWidth: chat.delegateMaxWidth - avatarMargin - bubbleMargin maxWidth: chat.delegateMaxWidth - avatarMargin - bubbleMargin
replyInset: mainInset + 4 + Nheko.paddingSmall
width: chat.delegateMaxWidth
data: [ data: [
Loader { Loader {
id: section id: section
active: wrapper.previousMessageUserId !== wrapper.userId || wrapper.previousMessageDay !== wrapper.day || wrapper.previousMessageIsStateEvent !== wrapper.isStateEvent active: wrapper.previousMessageUserId !== wrapper.userId || wrapper.previousMessageDay !== wrapper.day || wrapper.previousMessageIsStateEvent !== wrapper.isStateEvent
visible: status == Loader.Ready
z: 4
//asynchronous: true //asynchronous: true
sourceComponent: TimelineSectionHeader { sourceComponent: TimelineSectionHeader {
day: wrapper.day day: wrapper.day
@ -71,13 +70,12 @@ TimelineEvent {
userName: wrapper.userName userName: wrapper.userName
userPowerlevel: wrapper.userPowerlevel userPowerlevel: wrapper.userPowerlevel
} }
visible: status == Loader.Ready
z: 4
}, },
Rectangle { Rectangle {
anchors.fill: gridContainer
property color threadColor: TimelineManager.userColor(wrapper.threadId, palette.base)
property color threadBackgroundColor: wrapper.threadId ? Qt.tint(palette.base, Qt.hsla(threadColor.hslHue, 0.7, threadColor.hslLightness, 0.1)) : "transparent" property color threadBackgroundColor: wrapper.threadId ? Qt.tint(palette.base, Qt.hsla(threadColor.hslHue, 0.7, threadColor.hslLightness, 0.1)) : "transparent"
property color threadColor: TimelineManager.userColor(wrapper.threadId, palette.base)
anchors.fill: gridContainer
color: (Settings.messageHoverHighlight && messageHover.hovered) ? palette.alternateBase : threadBackgroundColor color: (Settings.messageHoverHighlight && messageHover.hovered) ? palette.alternateBase : threadBackgroundColor
// this looks better without margins // this looks better without margins
@ -91,8 +89,8 @@ TimelineEvent {
}, },
Rectangle { Rectangle {
id: scrollHighlight id: scrollHighlight
anchors.fill: gridContainer
anchors.fill: gridContainer
color: palette.highlight color: palette.highlight
enabled: false enabled: false
opacity: 0 opacity: 0
@ -133,37 +131,43 @@ TimelineEvent {
Item { Item {
id: gridContainer id: gridContainer
width: wrapper.width - wrapper.avatarMargin
implicitHeight: messageBubble.implicitHeight implicitHeight: messageBubble.implicitHeight
width: wrapper.width - wrapper.avatarMargin
x: wrapper.avatarMargin x: wrapper.avatarMargin
y: section.visible && section.active ? section.y + section.height : 0 y: section.visible && section.active ? section.y + section.height : 0
HoverHandler { HoverHandler {
id: messageHover id: messageHover
blocking: false blocking: false
onHoveredChanged: () => { onHoveredChanged: () => {
if (!Settings.mobileMode && hovered) { if (!Settings.mobileMode && hovered) {
if (!messageActions.hovered) { if (!messageActions.hovered) {
messageActions.model = wrapper; messageActions.model = wrapper;
messageActions.attached = wrapper; messageActions.attached = wrapper;
messageActions.anchors.bottomMargin = -gridContainer.y messageActions.anchors.bottomMargin = -gridContainer.y;
//messageActions.anchors.rightMargin = metadata.width //messageActions.anchors.rightMargin = metadata.width
} }
} }
} }
} }
AbstractButton { AbstractButton {
id: messageBubble id: messageBubble
anchors.left: (wrapper.isStateEvent || wrapper.isSender) ? undefined : parent.left // qmllint disable Quick.anchor-combinations
anchors.right: (wrapper.isStateEvent || !wrapper.isSender) ? undefined : parent.right
anchors.horizontalCenter: wrapper.isStateEvent ? parent.horizontalCenter : undefined
property color userColor: TimelineManager.userColor(wrapper.main?.userId ?? '', palette.base) property color userColor: TimelineManager.userColor(wrapper.main?.userId ?? '', palette.base)
anchors.horizontalCenter: wrapper.isStateEvent ? parent.horizontalCenter : undefined
anchors.left: (wrapper.isStateEvent || wrapper.isSender) ? undefined : parent.left // qmllint disable Quick.anchor-combinations
anchors.right: (wrapper.isStateEvent || !wrapper.isSender) ? undefined : parent.right
padding: wrapper.isStateEvent ? 0 : 4
background: Rectangle {
border.color: Nheko.theme.red
border.width: wrapper.notificationlevel == MtxEvent.Highlight ? 1 : 0
color: !wrapper.isStateEvent ? Qt.tint(palette.base, Qt.hsla(messageBubble.userColor.hslHue, wrapper.hovered ? 0.8 : 0.5, messageBubble.userColor.hslLightness, 0.2)) : "transparent"
radius: 4
}
contentItem: Item { contentItem: Item {
id: contentPlacementContainer id: contentPlacementContainer
@ -173,52 +177,47 @@ TimelineEvent {
// property bool fitsMetadataInside: wrapper.main?.positionAt ? (wrapper.main.positionAt(wrapper.main.width, wrapper.main.height - 4) == wrapper.main.positionAt(wrapper.main.width - metadata.width, wrapper.main.height - 4)) : false // property bool fitsMetadataInside: wrapper.main?.positionAt ? (wrapper.main.positionAt(wrapper.main.width, wrapper.main.height - 4) == wrapper.main.positionAt(wrapper.main.width - metadata.width, wrapper.main.height - 4)) : false
property bool fitsMetadataInside: false property bool fitsMetadataInside: false
implicitWidth: Math.max((wrapper.reply?.width ?? 0) + wrapper.replyInset, (wrapper.main?.width ?? 0) + wrapper.mainInset + ((fitsMetadata && !fitsMetadataInside) ? metadata.width : 0))
implicitHeight: contentColumn.implicitHeight + ((fitsMetadata || fitsMetadataInside) ? 0 : metadata.height) implicitHeight: contentColumn.implicitHeight + ((fitsMetadata || fitsMetadataInside) ? 0 : metadata.height)
implicitWidth: Math.max((wrapper.reply?.width ?? 0) + wrapper.replyInset, (wrapper.main?.width ?? 0) + wrapper.mainInset + ((fitsMetadata && !fitsMetadataInside) ? metadata.width : 0))
TimelineMetadata { TimelineMetadata {
id: metadata id: metadata
scaling: 0.75
anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right
visible: !wrapper.isStateEvent
eventId: wrapper.eventId eventId: wrapper.eventId
status: wrapper.status
trustlevel: wrapper.trustlevel
isEdited: wrapper.isEdited isEdited: wrapper.isEdited
isEncrypted: wrapper.isEncrypted isEncrypted: wrapper.isEncrypted
room: wrapper.room
scaling: 0.75
status: wrapper.status
threadId: wrapper.threadId threadId: wrapper.threadId
timestamp: wrapper.timestamp timestamp: wrapper.timestamp
room: wrapper.room trustlevel: wrapper.trustlevel
visible: !wrapper.isStateEvent
} }
Column { Column {
id: contentColumn id: contentColumn
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
data: [replyRow, wrapper.main]
AbstractButton { AbstractButton {
id: replyRow id: replyRow
visible: wrapper.reply
height: replyLine.height
anchors.left: parent.left
anchors.right: parent.right
property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base) property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base)
anchors.left: parent.left
anchors.right: parent.right
clip: true clip: true
height: replyLine.height
visible: wrapper.reply
NhekoCursorShape { background: Rectangle {
anchors.fill: parent //width: replyRow.implicitContentWidth
cursorShape: Qt.PointingHandCursor color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1))
} }
contentItem: Row { contentItem: Row {
id: replyRowLay id: replyRowLay
@ -226,94 +225,81 @@ TimelineEvent {
Rectangle { Rectangle {
id: replyLine id: replyLine
height: Math.min( wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height
color: replyRow.userColor color: replyRow.userColor
height: Math.min(wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height
width: 4 width: 4
} }
Column { Column {
spacing: 0
id: replyCol id: replyCol
data: [replyUserButton, wrapper.reply,]
spacing: 0
AbstractButton { AbstractButton {
id: replyUserButton id: replyUserButton
contentItem: Label { contentItem: Label {
id: userName_ id: userName_
text: wrapper.reply?.userName ?? ''
color: replyRow.userColor color: replyRow.userColor
text: wrapper.reply?.userName ?? ''
textFormat: Text.RichText textFormat: Text.RichText
width: wrapper.maxWidth width: wrapper.maxWidth
//elideWidth: wrapper.maxWidth //elideWidth: wrapper.maxWidth
} }
onClicked: wrapper.room.openUserProfile(wrapper.reply?.userId) onClicked: wrapper.room.openUserProfile(wrapper.reply?.userId)
} }
data: [
replyUserButton,
wrapper.reply,
]
} }
} }
background: Rectangle {
//width: replyRow.implicitContentWidth
color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1))
}
onClicked: { onClicked: {
let link = wrapper.reply.hoveredLink let link = wrapper.reply.hoveredLink;
if (link) { if (link) {
Nheko.openLink(link) Nheko.openLink(link);
} else { } else {
console.log("Scrolling to " + wrapper.replyTo); console.log("Scrolling to " + wrapper.replyTo);
wrapper.room.showEvent(wrapper.replyTo) wrapper.room.showEvent(wrapper.replyTo);
} }
} }
onPressAndHold: wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(pressX - replyLine.width - Nheko.paddingSmall, pressY - replyUserButton.implicitHeight) : "", wrapper.replyTo) onPressAndHold: wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(pressX - replyLine.width - Nheko.paddingSmall, pressY - replyUserButton.implicitHeight) : "", wrapper.replyTo)
NhekoCursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
TapHandler { TapHandler {
acceptedButtons: Qt.RightButton acceptedButtons: Qt.RightButton
onSingleTapped: (eventPoint) => wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(eventPoint.position.x-replyLine.width - Nheko.paddingSmall, eventPoint.position.y - replyUserButton.implicitHeight) : "", wrapper.replyTo)
gesturePolicy: TapHandler.ReleaseWithinBounds
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
} gesturePolicy: TapHandler.ReleaseWithinBounds
}
data: [replyRow, wrapper.main] onSingleTapped: eventPoint => wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(eventPoint.position.x - replyLine.width - Nheko.paddingSmall, eventPoint.position.y - replyUserButton.implicitHeight) : "", wrapper.replyTo)
} }
}
}
}
padding: wrapper.isStateEvent ? 0 : 4
background: Rectangle {
color: !wrapper.isStateEvent ? Qt.tint(palette.base, Qt.hsla(messageBubble.userColor.hslHue, wrapper.hovered ? 0.8 : 0.5, messageBubble.userColor.hslLightness, 0.2)) : "transparent"
radius: 4
border.color: Nheko.theme.red
border.width: wrapper.notificationlevel == MtxEvent.Highlight ? 1 : 0
} }
} }
DragHandler { DragHandler {
id: replyDragHandler id: replyDragHandler
yAxis.enabled: false
xAxis.enabled: true xAxis.enabled: true
xAxis.minimum: wrapper.avatarMargin - 100
xAxis.maximum: wrapper.avatarMargin xAxis.maximum: wrapper.avatarMargin
xAxis.minimum: wrapper.avatarMargin - 100
yAxis.enabled: false
onActiveChanged: { onActiveChanged: {
if (!replyDragHandler.active) { if (!replyDragHandler.active) {
if (replyDragHandler.xAxis.minimum <= replyDragHandler.xAxis.activeValue + 1) { if (replyDragHandler.xAxis.minimum <= replyDragHandler.xAxis.activeValue + 1) {
wrapper.room.reply = wrapper.eventId wrapper.room.reply = wrapper.eventId;
} }
gridContainer.x = wrapper.avatarMargin; gridContainer.x = wrapper.avatarMargin;
} }
} }
} }
TapHandler { TapHandler {
onDoubleTapped: wrapper.room.reply = wrapper.eventId onDoubleTapped: wrapper.room.reply = wrapper.eventId
} }
}, },
Reactions { Reactions {
id: reactionRow id: reactionRow
@ -347,4 +333,3 @@ TimelineEvent {
} }
] ]
} }

View file

@ -9,52 +9,52 @@ import im.nheko
TimelineEvent { TimelineEvent {
id: wrapper id: wrapper
ListView.delayRemove: true
width: chat.delegateMaxWidth
height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 10)
anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter
//room: chatRoot.roommodel
required property var day
required property bool isSender
required property int index
property var previousMessageDay: (index + 1) >= chat.count ? 0 : chat.model.dataByIndex(index + 1, Room.Day)
property bool previousMessageIsStateEvent: (index + 1) >= chat.count ? true : chat.model.dataByIndex(index + 1, Room.IsStateEvent)
property string previousMessageUserId: (index + 1) >= chat.count ? "" : chat.model.dataByIndex(index + 1, Room.UserId)
required property date timestamp
required property string userId
required property string userName
required property string threadId
required property int userPowerlevel
required property bool isEdited
required property bool isEncrypted
required property var reactions
required property int status
required property int trustlevel
required property int notificationlevel
required property int type
required property bool isEditable
required property QtObject messageContextMenu
required property QtObject replyContextMenu
required property Item messageActions
property int avatarMargin: (wrapper.isStateEvent || Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8)) // align bubble with section header property int avatarMargin: (wrapper.isStateEvent || Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8)) // align bubble with section header
//room: chatRoot.roommodel
required property var day
property alias hovered: messageHover.hovered property alias hovered: messageHover.hovered
required property int index
required property bool isEditable
required property bool isEdited
required property bool isEncrypted
required property bool isSender
required property Item messageActions
required property QtObject messageContextMenu
required property int notificationlevel
property var previousMessageDay: (index + 1) >= chat.count ? 0 : chat.model.dataByIndex(index + 1, Room.Day)
property bool previousMessageIsStateEvent: (index + 1) >= chat.count ? true : chat.model.dataByIndex(index + 1, Room.IsStateEvent)
property string previousMessageUserId: (index + 1) >= chat.count ? "" : chat.model.dataByIndex(index + 1, Room.UserId)
required property var reactions
required property QtObject replyContextMenu
property bool scrolledToThis: false property bool scrolledToThis: false
required property int status
required property string threadId
required property date timestamp
required property int trustlevel
required property int type
required property string userId
required property string userName
required property int userPowerlevel
ListView.delayRemove: true
anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter
height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 10)
mainInset: (threadId ? (4 + Nheko.paddingSmall) : 0) mainInset: (threadId ? (4 + Nheko.paddingSmall) : 0)
replyInset: mainInset + 4 + Nheko.paddingSmall
maxWidth: chat.delegateMaxWidth - avatarMargin - metadata.width maxWidth: chat.delegateMaxWidth - avatarMargin - metadata.width
replyInset: mainInset + 4 + Nheko.paddingSmall
width: chat.delegateMaxWidth
data: [ data: [
Loader { Loader {
id: section id: section
active: wrapper.previousMessageUserId !== wrapper.userId || wrapper.previousMessageDay !== wrapper.day || wrapper.previousMessageIsStateEvent !== wrapper.isStateEvent active: wrapper.previousMessageUserId !== wrapper.userId || wrapper.previousMessageDay !== wrapper.day || wrapper.previousMessageIsStateEvent !== wrapper.isStateEvent
visible: status == Loader.Ready
z: 4
//asynchronous: true //asynchronous: true
sourceComponent: TimelineSectionHeader { sourceComponent: TimelineSectionHeader {
day: wrapper.day day: wrapper.day
@ -69,8 +69,6 @@ TimelineEvent {
userName: wrapper.userName userName: wrapper.userName
userPowerlevel: wrapper.userPowerlevel userPowerlevel: wrapper.userPowerlevel
} }
visible: status == Loader.Ready
z: 4
}, },
Rectangle { Rectangle {
anchors.fill: gridContainer anchors.fill: gridContainer
@ -87,8 +85,8 @@ TimelineEvent {
}, },
Rectangle { Rectangle {
id: scrollHighlight id: scrollHighlight
anchors.fill: gridContainer
anchors.fill: gridContainer
color: palette.highlight color: palette.highlight
enabled: false enabled: false
opacity: 0 opacity: 0
@ -127,41 +125,41 @@ TimelineEvent {
} }
}, },
Rectangle { Rectangle {
anchors.top: gridContainer.top
anchors.left: gridContainer.left anchors.left: gridContainer.left
anchors.topMargin: -2
anchors.leftMargin: -2 anchors.leftMargin: -2
color: "transparent" anchors.top: gridContainer.top
anchors.topMargin: -2
border.color: Nheko.theme.red border.color: Nheko.theme.red
border.width: wrapper.notificationlevel == MtxEvent.Highlight ? 1 : 0 border.width: wrapper.notificationlevel == MtxEvent.Highlight ? 1 : 0
radius: 4 color: "transparent"
height: contentColumn.implicitHeight + 4 height: contentColumn.implicitHeight + 4
radius: 4
width: contentColumn.implicitWidth + 4 width: contentColumn.implicitWidth + 4
}, },
Row { Row {
id: gridContainer id: gridContainer
spacing: Nheko.paddingSmall
width: wrapper.width - wrapper.avatarMargin width: wrapper.width - wrapper.avatarMargin
x: wrapper.avatarMargin x: wrapper.avatarMargin
y: section.visible && section.active ? section.y + section.height : 0 y: section.visible && section.active ? section.y + section.height : 0
spacing: Nheko.paddingSmall
HoverHandler { HoverHandler {
id: messageHover id: messageHover
blocking: false blocking: false
onHoveredChanged: () => { onHoveredChanged: () => {
if (!Settings.mobileMode && hovered) { if (!Settings.mobileMode && hovered) {
if (!messageActions.hovered) { if (!messageActions.hovered) {
messageActions.model = wrapper; messageActions.model = wrapper;
messageActions.attached = wrapper; messageActions.attached = wrapper;
messageActions.anchors.bottomMargin = -gridContainer.y messageActions.anchors.bottomMargin = -gridContainer.y;
messageActions.anchors.rightMargin = metadata.width messageActions.anchors.rightMargin = metadata.width;
} }
} }
} }
} }
AbstractButton { AbstractButton {
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Part of a thread") ToolTip.text: qsTr("Part of a thread")
@ -179,31 +177,29 @@ TimelineEvent {
color: TimelineManager.userColor(wrapper.threadId, palette.base) color: TimelineManager.userColor(wrapper.threadId, palette.base)
} }
} }
Item { Item {
height: 1
visible: wrapper.isStateEvent visible: wrapper.isStateEvent
width: (wrapper.maxWidth - (wrapper.main?.width ?? 0)) / 2 width: (wrapper.maxWidth - (wrapper.main?.width ?? 0)) / 2
height: 1
} }
Column { Column {
id: contentColumn id: contentColumn
data: [replyRow, wrapper.main,]
AbstractButton { AbstractButton {
id: replyRow id: replyRow
visible: wrapper.reply
height: replyLine.height
property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base) property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base)
clip: true clip: true
height: replyLine.height
visible: wrapper.reply
NhekoCursorShape { background: Rectangle {
anchors.fill: parent //width: replyRow.implicitContentWidth
cursorShape: Qt.PointingHandCursor color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1))
} }
contentItem: Row { contentItem: Row {
id: replyRowLay id: replyRowLay
@ -211,80 +207,76 @@ TimelineEvent {
Rectangle { Rectangle {
id: replyLine id: replyLine
height: Math.min( wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height
color: replyRow.userColor color: replyRow.userColor
height: Math.min(wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height
width: 4 width: 4
} }
Column { Column {
spacing: 0
id: replyCol id: replyCol
data: [replyUserButton, wrapper.reply,]
spacing: 0
AbstractButton { AbstractButton {
id: replyUserButton id: replyUserButton
contentItem: Label { contentItem: Label {
id: userName_ id: userName_
text: wrapper.reply?.userName ?? ''
color: replyRow.userColor color: replyRow.userColor
text: wrapper.reply?.userName ?? ''
textFormat: Text.RichText textFormat: Text.RichText
width: wrapper.maxWidth width: wrapper.maxWidth
//elideWidth: wrapper.maxWidth //elideWidth: wrapper.maxWidth
} }
onClicked: wrapper.room.openUserProfile(wrapper.reply?.userId) onClicked: wrapper.room.openUserProfile(wrapper.reply?.userId)
} }
data: [
replyUserButton,
wrapper.reply,
]
} }
} }
background: Rectangle {
//width: replyRow.implicitContentWidth
color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1))
}
onClicked: { onClicked: {
let link = wrapper.reply.hoveredLink let link = wrapper.reply.hoveredLink;
if (link) { if (link) {
Nheko.openLink(link) Nheko.openLink(link);
} else { } else {
console.log("Scrolling to " + wrapper.replyTo); console.log("Scrolling to " + wrapper.replyTo);
wrapper.room.showEvent(wrapper.replyTo) wrapper.room.showEvent(wrapper.replyTo);
} }
} }
onPressAndHold: wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(pressX - replyLine.width - Nheko.paddingSmall, pressY - replyUserButton.implicitHeight) : "", wrapper.replyTo) onPressAndHold: wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(pressX - replyLine.width - Nheko.paddingSmall, pressY - replyUserButton.implicitHeight) : "", wrapper.replyTo)
NhekoCursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
TapHandler { TapHandler {
acceptedButtons: Qt.RightButton acceptedButtons: Qt.RightButton
onSingleTapped: (eventPoint) => wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(eventPoint.position.x-replyLine.width - Nheko.paddingSmall, eventPoint.position.y - replyUserButton.implicitHeight) : "", wrapper.replyTo)
gesturePolicy: TapHandler.ReleaseWithinBounds
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
} gesturePolicy: TapHandler.ReleaseWithinBounds
}
data: [ onSingleTapped: eventPoint => wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(eventPoint.position.x - replyLine.width - Nheko.paddingSmall, eventPoint.position.y - replyUserButton.implicitHeight) : "", wrapper.replyTo)
replyRow, wrapper.main, }
] }
} }
DragHandler { DragHandler {
id: replyDragHandler id: replyDragHandler
yAxis.enabled: false
xAxis.enabled: true xAxis.enabled: true
xAxis.minimum: wrapper.avatarMargin - 100
xAxis.maximum: wrapper.avatarMargin xAxis.maximum: wrapper.avatarMargin
xAxis.minimum: wrapper.avatarMargin - 100
yAxis.enabled: false
onActiveChanged: { onActiveChanged: {
if (!replyDragHandler.active) { if (!replyDragHandler.active) {
if (replyDragHandler.xAxis.minimum <= replyDragHandler.xAxis.activeValue + 1) { if (replyDragHandler.xAxis.minimum <= replyDragHandler.xAxis.activeValue + 1) {
wrapper.room.reply = wrapper.eventId wrapper.room.reply = wrapper.eventId;
} }
gridContainer.x = wrapper.avatarMargin; gridContainer.x = wrapper.avatarMargin;
} }
} }
} }
TapHandler { TapHandler {
onDoubleTapped: wrapper.room.reply = wrapper.eventId onDoubleTapped: wrapper.room.reply = wrapper.eventId
} }
@ -292,21 +284,18 @@ TimelineEvent {
TimelineMetadata { TimelineMetadata {
id: metadata id: metadata
scaling: 1
anchors.right: parent.right anchors.right: parent.right
y: section.visible && section.active ? section.y + section.height : 0
visible: !wrapper.isStateEvent
eventId: wrapper.eventId eventId: wrapper.eventId
status: wrapper.status
trustlevel: wrapper.trustlevel
isEdited: wrapper.isEdited isEdited: wrapper.isEdited
isEncrypted: wrapper.isEncrypted isEncrypted: wrapper.isEncrypted
room: wrapper.room
scaling: 1
status: wrapper.status
threadId: wrapper.threadId threadId: wrapper.threadId
timestamp: wrapper.timestamp timestamp: wrapper.timestamp
room: wrapper.room trustlevel: wrapper.trustlevel
visible: !wrapper.isStateEvent
y: section.visible && section.active ? section.y + section.height : 0
}, },
Reactions { Reactions {
id: reactionRow id: reactionRow

View file

@ -11,17 +11,16 @@ import im.nheko
RowLayout { RowLayout {
id: metadata id: metadata
property int iconSize: Math.floor(fontMetrics.ascent * scaling)
required property double scaling
required property string eventId required property string eventId
required property int status property int iconSize: Math.floor(fontMetrics.ascent * scaling)
required property int trustlevel
required property bool isEdited required property bool isEdited
required property bool isEncrypted required property bool isEncrypted
required property Room room
required property double scaling
required property int status
required property string threadId required property string threadId
required property date timestamp required property date timestamp
required property Room room required property int trustlevel
spacing: 2 spacing: 2
@ -43,6 +42,7 @@ RowLayout {
sourceSize.height: parent.iconSize * Screen.devicePixelRatio sourceSize.height: parent.iconSize * Screen.devicePixelRatio
sourceSize.width: parent.iconSize * Screen.devicePixelRatio sourceSize.width: parent.iconSize * Screen.devicePixelRatio
visible: metadata.isEdited || metadata.eventId == metadata.room.edit visible: metadata.isEdited || metadata.eventId == metadata.room.edit
HoverHandler { HoverHandler {
id: editHovered id: editHovered

View file

@ -6,11 +6,9 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Window import QtQuick.Window
import im.nheko import im.nheko
import "./components" import "./components"
Column { Column {
required property var day required property var day
required property bool isSender required property bool isSender
required property bool isStateEvent required property bool isStateEvent
@ -79,31 +77,14 @@ Column {
target: room target: room
} }
AbstractButton { AbstractButton {
id: userNameButton id: userNameButton
PowerlevelIndicator {
id: powerlevelIndicator
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
powerlevel: userPowerlevel
height: fontMetrics.ascent
width: height
sourceSize.width: fontMetrics.lineSpacing
sourceSize.height: fontMetrics.lineSpacing
permissions: room ? room.permissions : null
visible: isAdmin || isModerator
}
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: userId ToolTip.text: userId
ToolTip.visible: hovered ToolTip.visible: hovered
leftPadding: powerlevelIndicator.visible ? 16 : 0
leftInset: 0 leftInset: 0
leftPadding: powerlevelIndicator.visible ? 16 : 0
rightInset: 0 rightInset: 0
rightPadding: 0 rightPadding: 0
@ -117,6 +98,19 @@ Column {
onClicked: room.openUserProfile(userId) onClicked: room.openUserProfile(userId)
PowerlevelIndicator {
id: powerlevelIndicator
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
height: fontMetrics.ascent
permissions: room ? room.permissions : null
powerlevel: userPowerlevel
sourceSize.height: fontMetrics.lineSpacing
sourceSize.width: fontMetrics.lineSpacing
visible: isAdmin || isModerator
width: height
}
TextMetrics { TextMetrics {
id: userNameTextMetrics id: userNameTextMetrics
@ -161,4 +155,3 @@ Column {
} }
} }
} }

View file

@ -188,9 +188,9 @@ Item {
displayName: parent.roomName displayName: parent.roomName
enabled: false enabled: false
implicitHeight: 130 implicitHeight: 130
implicitWidth: 130
roomid: parent.roomId roomid: parent.roomId
url: parent.avatarUrl.replace("mxc://", "image://MxcImage/") url: parent.avatarUrl.replace("mxc://", "image://MxcImage/")
implicitWidth: 130
} }
RowLayout { RowLayout {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -293,10 +293,10 @@ Item {
displayName: roomPreview?.inviterDisplayName ?? "" displayName: roomPreview?.inviterDisplayName ?? ""
enabled: true enabled: true
implicitHeight: 48 implicitHeight: 48
implicitWidth: 48
roomid: preview.roomId roomid: preview.roomId
url: (roomPreview?.inviterAvatarUrl ?? "").replace("mxc://", "image://MxcImage/") url: (roomPreview?.inviterAvatarUrl ?? "").replace("mxc://", "image://MxcImage/")
userid: roomPreview?.inviterUserId ?? "" userid: roomPreview?.inviterUserId ?? ""
implicitWidth: 48
onClicked: TimelineManager.openGlobalUserProfile(roomPreview.inviterUserId) onClicked: TimelineManager.openGlobalUserProfile(roomPreview.inviterUserId)
} }
@ -378,8 +378,8 @@ Item {
running: false running: false
onTriggered: { onTriggered: {
timelineEffects.removeParticles() timelineEffects.removeParticles();
shouldEffectsRun = false shouldEffectsRun = false;
} }
} }
Connections { Connections {

View file

@ -124,9 +124,9 @@ Pane {
Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
Layout.row: 2 Layout.row: 2
clip: true clip: true
enabled: false
// don't use the disabled color // don't use the disabled color
color: topBar.palette.text color: topBar.palette.text
enabled: false
selectByMouse: false selectByMouse: false
text: roomTopic text: roomTopic
} }
@ -287,9 +287,9 @@ Pane {
property var e: room ? room.getDump(modelData, "pins") : {} property var e: room ? room.getDump(modelData, "pins") : {}
maxWidth: pinnedMessages.width
//Layout.preferredHeight: height //Layout.preferredHeight: height
eventId: e.eventId ?? "" eventId: e.eventId ?? ""
maxWidth: pinnedMessages.width
userColor: TimelineManager.userColor(e.userId, palette.window) userColor: TimelineManager.userColor(e.userId, palette.window)
Connections { Connections {

View file

@ -13,13 +13,80 @@ Container {
id: container id: container
property Component handle: Rectangle {
anchors.right: parent.right
color: Nheko.theme.separator
height: container.height
width: visible ? 1 : 0
z: 3
}
property Component handleToucharea: Item {
id: splitter
property int calculatedWidth: {
if (!visible)
return 0;
else if (container.singlePageMode)
return container.width;
else
return (collapsible && x < minimumWidth) ? collapsedWidth : x;
}
property int collapsedWidth: parent.collapsedWidth
property bool collapsible: parent.collapsible
property int maximumWidth: parent.maximumWidth
property int minimumWidth: parent.minimumWidth
enabled: !container.singlePageMode
height: container.height
width: 1
x: parent.preferredWidth
z: 3
NhekoCursorShape {
cursorShape: Qt.SizeHorCursor
height: parent.height
width: container.splitterGrabMargin * 2
x: -container.splitterGrabMargin
}
DragHandler {
id: dragHandler
enabled: !container.singlePageMode
grabPermissions: PointerHandler.CanTakeOverFromAnything | PointerHandler.ApprovesTakeOverByHandlersOfSameType
margin: container.splitterGrabMargin
xAxis.enabled: true
xAxis.maximum: splitter.maximumWidth
xAxis.minimum: splitter.minimumWidth - 1
yAxis.enabled: false
onActiveChanged: {
if (!active) {
splitter.x = splitter.calculatedWidth;
splitter.parent.preferredWidth = splitter.calculatedWidth;
}
}
}
HoverHandler {
enabled: !container.singlePageMode
margin: container.splitterGrabMargin
}
}
property alias pageIndex: view.currentIndex
property bool singlePageMode: width < 800 property bool singlePageMode: width < 800
property int splitterGrabMargin: Nheko.paddingSmall property int splitterGrabMargin: Nheko.paddingSmall
property alias pageIndex: view.currentIndex
property Component handle
property Component handleToucharea
onSinglePageModeChanged: if (!singlePageMode) pageIndex = 0 contentItem: ListView {
id: view
boundsBehavior: Flickable.StopAtBounds
currentIndex: container.singlePageMode ? container.pageIndex : 0
highlightMoveDuration: (container.singlePageMode && !Settings.reducedMotion) ? 200 : 0
highlightRangeMode: ListView.StrictlyEnforceRange
interactive: singlePageMode
model: container.contentModel
orientation: ListView.Horizontal
snapMode: ListView.SnapOneItem
}
Component.onCompleted: { Component.onCompleted: {
for (var i = 0; i < count - 1; i++) { for (var i = 0; i < count - 1; i++) {
@ -40,7 +107,6 @@ Container {
for (var i = 0; i < count - 1; i++) { for (var i = 0; i < count - 1; i++) {
if (contentChildren[i].width) if (contentChildren[i].width)
w = w - contentChildren[i].width; w = w - contentChildren[i].width;
} }
return w; return w;
} }
@ -55,81 +121,6 @@ Container {
}); });
} }
} }
onSinglePageModeChanged: if (!singlePageMode)
handle: Rectangle { pageIndex = 0
z: 3
color: Nheko.theme.separator
height: container.height
width: visible ? 1 : 0
anchors.right: parent.right
}
handleToucharea: Item {
id: splitter
property int minimumWidth: parent.minimumWidth
property int maximumWidth: parent.maximumWidth
property int collapsedWidth: parent.collapsedWidth
property bool collapsible: parent.collapsible
property int calculatedWidth: {
if (!visible)
return 0;
else if (container.singlePageMode)
return container.width;
else
return (collapsible && x < minimumWidth) ? collapsedWidth : x;
}
enabled: !container.singlePageMode
height: container.height
width: 1
x: parent.preferredWidth
z: 3
NhekoCursorShape {
height: parent.height
width: container.splitterGrabMargin * 2
x: -container.splitterGrabMargin
cursorShape: Qt.SizeHorCursor
}
DragHandler {
id: dragHandler
enabled: !container.singlePageMode
xAxis.enabled: true
yAxis.enabled: false
xAxis.minimum: splitter.minimumWidth - 1
xAxis.maximum: splitter.maximumWidth
margin: container.splitterGrabMargin
grabPermissions: PointerHandler.CanTakeOverFromAnything | PointerHandler.ApprovesTakeOverByHandlersOfSameType
onActiveChanged: {
if (!active) {
splitter.x = splitter.calculatedWidth;
splitter.parent.preferredWidth = splitter.calculatedWidth;
}
}
}
HoverHandler {
enabled: !container.singlePageMode
margin: container.splitterGrabMargin
}
}
contentItem: ListView {
id: view
model: container.contentModel
snapMode: ListView.SnapOneItem
orientation: ListView.Horizontal
highlightRangeMode: ListView.StrictlyEnforceRange
interactive: singlePageMode
highlightMoveDuration: (container.singlePageMode && !Settings.reducedMotion) ? 200 : 0
currentIndex: container.singlePageMode ? container.pageIndex : 0
boundsBehavior: Flickable.StopAtBounds
}
} }

View file

@ -5,13 +5,13 @@
import QtQuick import QtQuick
Item { Item {
property int minimumWidth: 100 property bool collapsed: width < minimumWidth
property int maximumWidth: 400
property int collapsedWidth: 40 property int collapsedWidth: 40
property bool collapsible: true property bool collapsible: true
property bool collapsed: width < minimumWidth property int maximumWidth: 400
property int splitterWidth: 1 property int minimumWidth: 100
property int preferredWidth: 100 property int preferredWidth: 100
property int splitterWidth: 1
Component.onCompleted: { Component.onCompleted: {
children[0].width = Qt.binding(() => { children[0].width = Qt.binding(() => {

View file

@ -10,25 +10,26 @@ import im.nheko
Rectangle { Rectangle {
id: tile id: tile
property color background: palette.window
property color importantText: palette.text
property color unimportantText: palette.buttonText
property color bubbleBackground: palette.highlight
property color bubbleText: palette.highlightedText
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3) property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
required property string avatarUrl required property string avatarUrl
required property string title property color background: palette.window
required property string subtitle property color bubbleBackground: palette.highlight
required property int index property color bubbleText: palette.highlightedText
required property int selectedIndex
property bool crop: true property bool crop: true
property color importantText: palette.text
required property int index
property alias roomid: avatar.roomid property alias roomid: avatar.roomid
required property int selectedIndex
required property string subtitle
required property string title
property color unimportantText: palette.buttonText
property alias userid: avatar.userid property alias userid: avatar.userid
color: background color: background
height: avatarSize + 2 * Nheko.paddingMedium height: avatarSize + 2 * Nheko.paddingMedium
width: ListView.view.width
state: "normal" state: "normal"
width: ListView.view.width
states: [ states: [
State { State {
name: "highlight" name: "highlight"
@ -37,13 +38,12 @@ Rectangle {
PropertyChanges { PropertyChanges {
tile { tile {
background: palette.dark background: palette.dark
importantText: palette.brightText
unimportantText: palette.brightText
bubbleBackground: palette.highlight bubbleBackground: palette.highlight
bubbleText: palette.highlightedText bubbleText: palette.highlightedText
importantText: palette.brightText
unimportantText: palette.brightText
} }
} }
}, },
State { State {
name: "selected" name: "selected"
@ -52,37 +52,35 @@ Rectangle {
PropertyChanges { PropertyChanges {
tile { tile {
background: palette.highlight background: palette.highlight
importantText: palette.highlightedText
unimportantText: palette.highlightedText
bubbleBackground: palette.highlightedText bubbleBackground: palette.highlightedText
bubbleText: palette.highlight bubbleText: palette.highlight
importantText: palette.highlightedText
unimportantText: palette.highlightedText
} }
} }
} }
] ]
HoverHandler { HoverHandler {
id: hovered id: hovered
}
}
RowLayout { RowLayout {
spacing: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
Avatar { Avatar {
id: avatar id: avatar
enabled: false
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
crop: tile.crop
displayName: title
enabled: false
implicitHeight: avatarSize implicitHeight: avatarSize
implicitWidth: avatarSize implicitWidth: avatarSize
url: tile.avatarUrl.replace("mxc://", "image://MxcImage/") url: tile.avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: title
crop: tile.crop
} }
ColumnLayout { ColumnLayout {
id: textContent id: textContent
@ -103,33 +101,25 @@ Rectangle {
fullText: title fullText: title
textFormat: Text.PlainText textFormat: Text.PlainText
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
spacing: 0 spacing: 0
ElidedLabel { ElidedLabel {
color: tile.unimportantText color: tile.unimportantText
font.pixelSize: fontMetrics.font.pixelSize * 0.9
elideWidth: textContent.width - Nheko.paddingSmall elideWidth: textContent.width - Nheko.paddingSmall
font.pixelSize: fontMetrics.font.pixelSize * 0.9
fullText: subtitle fullText: subtitle
textFormat: Text.PlainText textFormat: Text.PlainText
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
} }
} }
} }

View file

@ -12,53 +12,51 @@ import im.nheko
Button { Button {
id: control id: control
implicitHeight: Math.ceil(control.contentItem.implicitHeight * 1.70)
implicitWidth: Math.ceil(control.contentItem.implicitWidth + control.contentItem.implicitHeight)
hoverEnabled: true
property string iconImage: "" property string iconImage: ""
MultiEffect { hoverEnabled: true
anchors.fill: control.background implicitHeight: Math.ceil(control.contentItem.implicitHeight * 1.70)
shadowHorizontalOffset: 3 implicitWidth: Math.ceil(control.contentItem.implicitWidth + control.contentItem.implicitHeight)
shadowVerticalOffset: 3
shadowBlur: 8
shadowEnabled: true
shadowColor: "#80000000"
source: control.background
}
contentItem: RowLayout {
spacing: 0
anchors.centerIn: parent
Image {
Layout.leftMargin: Nheko.paddingMedium
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
Layout.preferredHeight: fontMetrics.font.pixelSize * 1.5
Layout.preferredWidth: fontMetrics.font.pixelSize * 1.5
visible: !!iconImage
source: iconImage
}
Text {
Layout.alignment: Qt.AlignHCenter
text: control.text
//font: control.font
font.capitalization: Font.AllUppercase
font.pointSize: Math.ceil(fontMetrics.font.pointSize * 1.5)
//font.capitalization: Font.AllUppercase
color: palette.light
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
}
background: Rectangle { background: Rectangle {
color: Qt.lighter(palette.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1))
//height: control.contentItem.implicitHeight * 2 //height: control.contentItem.implicitHeight * 2
//width: control.contentItem.implicitWidth * 2 //width: control.contentItem.implicitWidth * 2
radius: height / 8 radius: height / 8
color: Qt.lighter(palette.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1)) }
contentItem: RowLayout {
anchors.centerIn: parent
spacing: 0
Image {
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
Layout.leftMargin: Nheko.paddingMedium
Layout.preferredHeight: fontMetrics.font.pixelSize * 1.5
Layout.preferredWidth: fontMetrics.font.pixelSize * 1.5
source: iconImage
visible: !!iconImage
}
Text {
Layout.alignment: Qt.AlignHCenter
//font.capitalization: Font.AllUppercase
color: palette.light
elide: Text.ElideRight
//font: control.font
font.capitalization: Font.AllUppercase
font.pointSize: Math.ceil(fontMetrics.font.pointSize * 1.5)
horizontalAlignment: Text.AlignHCenter
text: control.text
verticalAlignment: Text.AlignVCenter
}
} }
MultiEffect {
anchors.fill: control.background
shadowBlur: 8
shadowColor: "#80000000"
shadowEnabled: true
shadowHorizontalOffset: 3
shadowVerticalOffset: 3
source: control.background
}
} }

View file

@ -10,30 +10,29 @@ Dialog {
default property alias inner: scroll.data default property alias inner: scroll.data
property int useableWidth: scroll.width - scroll.ScrollBar.vertical.width property int useableWidth: scroll.width - scroll.ScrollBar.vertical.width
parent: Overlay.overlay
anchors.centerIn: parent anchors.centerIn: parent
height: (Math.floor(parent.height / 2) - Nheko.paddingLarge) * 2
width: (Math.floor(parent.width / 2) - Nheko.paddingLarge) * 2
padding: 0
modal: true
standardButtons: Dialog.Ok | Dialog.Cancel
closePolicy: Popup.NoAutoClose closePolicy: Popup.NoAutoClose
height: (Math.floor(parent.height / 2) - Nheko.paddingLarge) * 2
modal: true
padding: 0
parent: Overlay.overlay
standardButtons: Dialog.Ok | Dialog.Cancel
width: (Math.floor(parent.width / 2) - Nheko.paddingLarge) * 2
background: Rectangle {
border.color: Nheko.theme.separator
border.width: 1
color: palette.window
radius: Nheko.paddingSmall
}
contentChildren: [ contentChildren: [
ScrollView { ScrollView {
id: scroll id: scroll
clip: true
anchors.fill: parent
ScrollBar.horizontal.visible: false ScrollBar.horizontal.visible: false
ScrollBar.vertical.visible: true ScrollBar.vertical.visible: true
anchors.fill: parent
clip: true
} }
] ]
background: Rectangle {
color: palette.window
border.color: Nheko.theme.separator
border.width: 1
radius: Nheko.paddingSmall
}
} }

View file

@ -9,21 +9,19 @@ import im.nheko 1.0
TabButton { TabButton {
id: control id: control
contentItem: Text {
text: control.text
font: control.font
opacity: enabled ? 1.0 : 0.3
color: control.down ? palette.highlightedText : palette.text
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
background: Rectangle { background: Rectangle {
border.color: control.down ? palette.highlight : Nheko.theme.separator border.color: control.down ? palette.highlight : Nheko.theme.separator
color: control.checked ? palette.highlight : palette.base
border.width: 1 border.width: 1
color: control.checked ? palette.highlight : palette.base
radius: 2 radius: 2
} }
contentItem: Text {
color: control.down ? palette.highlightedText : palette.text
elide: Text.ElideRight
font: control.font
horizontalAlignment: Text.AlignHCenter
opacity: enabled ? 1.0 : 0.3
text: control.text
verticalAlignment: Text.AlignVCenter
}
} }

View file

@ -9,39 +9,38 @@ import im.nheko 1.0
Rectangle { Rectangle {
id: bubbleRoot id: bubbleRoot
required property int notificationCount
required property bool hasLoudNotification
required property color bubbleBackgroundColor required property color bubbleBackgroundColor
required property color bubbleTextColor required property color bubbleTextColor
property bool mayBeVisible: true
property alias font: notificationBubbleText.font property alias font: notificationBubbleText.font
baselineOffset: notificationBubbleText.baseline - bubbleRoot.top required property bool hasLoudNotification
property bool mayBeVisible: true
required property int notificationCount
visible: mayBeVisible && notificationCount > 0 ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: notificationCount
ToolTip.visible: notificationBubbleHover.hovered && (notificationCount > 9999)
baselineOffset: notificationBubbleText.baseline - bubbleRoot.top
color: hasLoudNotification ? Nheko.theme.red : bubbleBackgroundColor
implicitHeight: notificationBubbleText.height + Nheko.paddingMedium implicitHeight: notificationBubbleText.height + Nheko.paddingMedium
implicitWidth: Math.max(notificationBubbleText.width, height) implicitWidth: Math.max(notificationBubbleText.width, height)
radius: height / 2 radius: height / 2
color: hasLoudNotification ? Nheko.theme.red : bubbleBackgroundColor visible: mayBeVisible && notificationCount > 0
ToolTip.text: notificationCount
ToolTip.delay: Nheko.tooltipDelay
ToolTip.visible: notificationBubbleHover.hovered && (notificationCount > 9999)
Label { Label {
id: notificationBubbleText id: notificationBubbleText
anchors.centerIn: bubbleRoot anchors.centerIn: bubbleRoot
horizontalAlignment: Text.AlignHCenter color: bubbleRoot.hasLoudNotification ? "white" : bubbleRoot.bubbleTextColor
verticalAlignment: Text.AlignVCenter
width: Math.max(implicitWidth + Nheko.paddingMedium, bubbleRoot.height)
font.bold: true font.bold: true
font.pixelSize: fontMetrics.font.pixelSize * 0.8 font.pixelSize: fontMetrics.font.pixelSize * 0.8
color: bubbleRoot.hasLoudNotification ? "white" : bubbleRoot.bubbleTextColor horizontalAlignment: Text.AlignHCenter
text: bubbleRoot.notificationCount > 9999 ? "9999+" : bubbleRoot.notificationCount text: bubbleRoot.notificationCount > 9999 ? "9999+" : bubbleRoot.notificationCount
verticalAlignment: Text.AlignVCenter
width: Math.max(implicitWidth + Nheko.paddingMedium, bubbleRoot.height)
HoverHandler { HoverHandler {
id: notificationBubbleHover id: notificationBubbleHover
}
} }
}
} }

View file

@ -7,13 +7,11 @@ import QtQuick.Controls
import im.nheko import im.nheko
Image { Image {
required property int powerlevel
required property var permissions
readonly property bool isAdmin: permissions ? permissions.changeLevel(MtxEvent.PowerLevels) <= powerlevel : false readonly property bool isAdmin: permissions ? permissions.changeLevel(MtxEvent.PowerLevels) <= powerlevel : false
readonly property bool isModerator: permissions ? permissions.redactLevel() <= powerlevel : false
readonly property bool isDefault: permissions ? permissions.defaultLevel() <= powerlevel : false readonly property bool isDefault: permissions ? permissions.defaultLevel() <= powerlevel : false
readonly property bool isModerator: permissions ? permissions.redactLevel() <= powerlevel : false
required property var permissions
required property int powerlevel
readonly property string sourceUrl: { readonly property string sourceUrl: {
if (isAdmin) if (isAdmin)
return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?"; return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?";
@ -23,8 +21,6 @@ Image {
return "image://colorimage/:/icons/icons/ui/person.svg?"; return "image://colorimage/:/icons/icons/ui/person.svg?";
} }
source: sourceUrl + (ma.hovered ? palette.highlight : palette.buttonText)
ToolTip.visible: ma.hovered
ToolTip.text: { ToolTip.text: {
if (isAdmin) if (isAdmin)
return qsTr("Administrator: %1").arg(powerlevel); return qsTr("Administrator: %1").arg(powerlevel);
@ -33,8 +29,11 @@ Image {
else else
return qsTr("User: %1").arg(powerlevel); return qsTr("User: %1").arg(powerlevel);
} }
ToolTip.visible: ma.hovered
source: sourceUrl + (ma.hovered ? palette.highlight : palette.buttonText)
HoverHandler { HoverHandler {
id: ma id: ma
} }
} }

View file

@ -8,8 +8,8 @@ import QtQml.Models
Item { Item {
id: root id: root
property alias model: visualModel.model
property Component delegate property Component delegate
property alias model: visualModel.model
Component { Component {
id: dragDelegate id: dragDelegate
@ -17,104 +17,117 @@ Item {
MouseArea { MouseArea {
id: dragArea id: dragArea
required property var model
required property int index
enabled: model.moveable == undefined || model.moveable
property bool held: false property bool held: false
required property int index
required property var model
anchors { left: parent.left; right: parent.right } drag.axis: Drag.YAxis
drag.target: held ? content : undefined
enabled: model.moveable == undefined || model.moveable
height: content.height height: content.height
drag.target: held ? content : undefined onHeldChanged: if (held)
drag.axis: Drag.YAxis ListView.view.currentIndex = dragArea.index
else
ListView.view.currentIndex = -1
onPressAndHold: held = true onPressAndHold: held = true
onPressed: if (mouse.source !== Qt.MouseEventNotSynthesized) { held = true } onPressed: if (mouse.source !== Qt.MouseEventNotSynthesized) {
held = true;
}
onReleased: held = false onReleased: held = false
onHeldChanged: if (held) ListView.view.currentIndex = dragArea.index; else ListView.view.currentIndex = -1
anchors {
left: parent.left
right: parent.right
}
Rectangle { Rectangle {
id: content id: content
Drag.active: dragArea.held
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
Drag.source: dragArea
border.color: palette.highlight
border.width: dragArea.enabled ? 1 : 0
color: dragArea.held ? palette.highlight : palette.base
height: actualDelegate.implicitHeight + 4
radius: 2
width: dragArea.width
Behavior on color {
ColorAnimation {
duration: 100
}
}
states: State {
when: dragArea.held
ParentChange {
parent: root
target: content
}
AnchorChanges {
target: content
anchors {
horizontalCenter: undefined
verticalCenter: undefined
}
}
}
anchors { anchors {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
} }
width: dragArea.width; height: actualDelegate.implicitHeight + 4
border.width: dragArea.enabled ? 1 : 0
border.color: palette.highlight
color: dragArea.held ? palette.highlight : palette.base
Behavior on color { ColorAnimation { duration: 100 } }
radius: 2
Drag.active: dragArea.held
Drag.source: dragArea
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
states: State {
when: dragArea.held
ParentChange { target: content; parent: root }
AnchorChanges {
target: content
anchors { horizontalCenter: undefined; verticalCenter: undefined }
}
}
Loader { Loader {
id: actualDelegate id: actualDelegate
sourceComponent: root.delegate
property var model: dragArea.model
property int index: dragArea.index property int index: dragArea.index
property var model: dragArea.model
property int offset: -view.contentY + dragArea.y property int offset: -view.contentY + dragArea.y
anchors { fill: parent; margins: 2 }
}
} sourceComponent: root.delegate
anchors {
fill: parent
margins: 2
}
}
}
DropArea { DropArea {
enabled: index != 0 || model.moveable == undefined || model.moveable enabled: index != 0 || model.moveable == undefined || model.moveable
anchors { fill: parent; margins: 8 }
onEntered: (drag)=> { onEntered: drag => {
visualModel.model.move(drag.source.index, dragArea.index) visualModel.model.move(drag.source.index, dragArea.index);
}
} }
anchors {
fill: parent
margins: 8
}
}
} }
} }
DelegateModel { DelegateModel {
id: visualModel id: visualModel
delegate: dragDelegate delegate: dragDelegate
} }
ListView { ListView {
id: view id: view
cacheBuffer: 50
clip: true clip: true
anchors { fill: parent; margins: 2 }
model: visualModel
highlightRangeMode: ListView.ApplyRange highlightRangeMode: ListView.ApplyRange
model: visualModel
preferredHighlightBegin: 0.2 * height preferredHighlightBegin: 0.2 * height
preferredHighlightEnd: 0.8 * height preferredHighlightEnd: 0.8 * height
spacing: 4 spacing: 4
cacheBuffer: 50
anchors {
fill: parent
margins: 2
}
} }
} }

View file

@ -9,76 +9,87 @@ import im.nheko 1.0
Platform.Menu { Platform.Menu {
id: spacesMenu id: spacesMenu
property string roomid
property Component childMenu property Component childMenu
property int position: modelData == undefined ? -2 : modelData.treeIndex
title: modelData != undefined ? modelData.name : qsTr("Add or remove from community")
property bool loadChildren: false property bool loadChildren: false
property int position: modelData == undefined ? -2 : modelData.treeIndex
property string roomid
title: modelData != undefined ? modelData.name : qsTr("Add or remove from community")
onAboutToShow: loadChildren = true onAboutToShow: loadChildren = true
//onAboutToHide: loadChildren = false //onAboutToHide: loadChildren = false
Platform.MenuItemGroup { Platform.MenuItemGroup {
id: modificationGroup id: modificationGroup
visible: position != -1 visible: position != -1
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Official community for this room")
group: modificationGroup
checkable: true checkable: true
checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && modelData.canonical) checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && modelData.canonical)
enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent) enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent)
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, true) group: modificationGroup
text: qsTr("Official community for this room")
onTriggered: if (checked)
Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, true)
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Affiliated community for this room")
group: modificationGroup
checkable: true checkable: true
checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && !modelData.canonical) checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && !modelData.canonical)
enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent) enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent)
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, false) group: modificationGroup
text: qsTr("Affiliated community for this room")
onTriggered: if (checked)
Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, false)
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Listed only for community members")
group: modificationGroup
checkable: true checkable: true
checked: spacesMenu.position >= 0 && (modelData.childValid && !modelData.parentValid) checked: spacesMenu.position >= 0 && (modelData.childValid && !modelData.parentValid)
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || modelData.childValid) && (!modelData.parentValid || modelData.canEditParent)) enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || modelData.childValid) && (!modelData.parentValid || modelData.canEditParent))
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, true, false) group: modificationGroup
text: qsTr("Listed only for community members")
onTriggered: if (checked)
Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, true, false)
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Listed only for room members")
group: modificationGroup
checkable: true checkable: true
checked: spacesMenu.position >= 0 && (!modelData.childValid && modelData.parentValid) checked: spacesMenu.position >= 0 && (!modelData.childValid && modelData.parentValid)
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild) && (modelData.parentValid || modelData.canEditParent)) enabled: spacesMenu.position >= 0 && ((modelData.canEditChild) && (modelData.parentValid || modelData.canEditParent))
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, false, false) group: modificationGroup
text: qsTr("Listed only for room members")
onTriggered: if (checked)
Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, false, false)
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Not related")
group: modificationGroup
checkable: true checkable: true
checked: spacesMenu.position >= 0 && (!modelData.childValid && !modelData.parentValid) checked: spacesMenu.position >= 0 && (!modelData.childValid && !modelData.parentValid)
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || !modelData.childValid) && (!modelData.parentValid || modelData.canEditParent)) enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || !modelData.childValid) && (!modelData.parentValid || modelData.canEditParent))
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, false, false)
}
Platform.MenuSeparator {
text: qsTr("Subcommunities")
group: modificationGroup group: modificationGroup
text: qsTr("Not related")
onTriggered: if (checked)
Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, false, false)
}
Platform.MenuSeparator {
group: modificationGroup
text: qsTr("Subcommunities")
visible: modificationGroup.visible && inst.model != undefined visible: modificationGroup.visible && inst.model != undefined
} }
Instantiator { Instantiator {
id: inst id: inst
model: spacesMenu.loadChildren ? Communities.spaceChildrenListFromIndex(spacesMenu.roomid, spacesMenu.position) : undefined
onObjectAdded: (idx, o) => {
spacesMenu.insertMenu(idx + (spacesMenu.position != -1 ? 6 : 0), o)
}
//onObjectRemoved: spacesMenu.removeMenu(object) //onObjectRemoved: spacesMenu.removeMenu(object)
delegate: childMenu delegate: childMenu
model: spacesMenu.loadChildren ? Communities.spaceChildrenListFromIndex(spacesMenu.roomid, spacesMenu.position) : undefined
onObjectAdded: (idx, o) => {
spacesMenu.insertMenu(idx + (spacesMenu.position != -1 ? 6 : 0), o);
}
} }
} }

View file

@ -10,37 +10,34 @@ import im.nheko 1.0 // for cursor shape
AbstractButton { AbstractButton {
id: button id: button
property color buttonTextColor: palette.buttonText
property alias cursor: mouseArea.cursorShape property alias cursor: mouseArea.cursorShape
property color highlightColor: palette.highlight property color highlightColor: palette.highlight
property color buttonTextColor: palette.buttonText
focusPolicy: Qt.NoFocus focusPolicy: Qt.NoFocus
width: buttonText.implicitWidth
height: buttonText.implicitHeight height: buttonText.implicitHeight
implicitWidth: buttonText.implicitWidth
implicitHeight: buttonText.implicitHeight implicitHeight: buttonText.implicitHeight
implicitWidth: buttonText.implicitWidth
width: buttonText.implicitWidth
Label { Label {
id: buttonText id: buttonText
anchors.centerIn: parent anchors.centerIn: parent
padding: 0
text: button.text
color: button.hovered ? highlightColor : buttonTextColor color: button.hovered ? highlightColor : buttonTextColor
font: button.font font: button.font
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
padding: 0
text: button.text
verticalAlignment: Text.AlignVCenter
} }
NhekoCursorShape { NhekoCursorShape {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
Ripple { Ripple {
color: Qt.rgba(buttonTextColor.r, buttonTextColor.g, buttonTextColor.b, 0.5) color: Qt.rgba(buttonTextColor.r, buttonTextColor.g, buttonTextColor.b, 0.5)
} }
} }

View file

@ -9,43 +9,50 @@ import QtQuick.Layouts 1.12
import im.nheko 1.0 import im.nheko 1.0
ItemDelegate { ItemDelegate {
property alias bgColor: background.color
property alias userid: avatar.userid
property alias displayName: avatar.displayName
property string avatarUrl property string avatarUrl
property alias bgColor: background.color
property alias displayName: avatar.displayName
property alias userid: avatar.userid
implicitHeight: layout.implicitHeight + Nheko.paddingSmall * 2 implicitHeight: layout.implicitHeight + Nheko.paddingSmall * 2
background: Rectangle {id: background}
background: Rectangle {
id: background
}
GridLayout { GridLayout {
id: layout id: layout
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width - Nheko.paddingSmall * 2 columnSpacing: Nheko.paddingMedium
rows: 2
columns: 2 columns: 2
rowSpacing: Nheko.paddingSmall rowSpacing: Nheko.paddingSmall
columnSpacing: Nheko.paddingMedium rows: 2
width: parent.width - Nheko.paddingSmall * 2
Avatar { Avatar {
id: avatar id: avatar
Layout.rowSpan: 2
Layout.preferredWidth: Nheko.avatarSize
Layout.preferredHeight: Nheko.avatarSize
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
url: avatarUrl.replace("mxc://", "image://MxcImage/") Layout.preferredHeight: Nheko.avatarSize
Layout.preferredWidth: Nheko.avatarSize
Layout.rowSpan: 2
enabled: false enabled: false
url: avatarUrl.replace("mxc://", "image://MxcImage/")
} }
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
text: displayName
color: TimelineManager.userColor(userid, palette.window) color: TimelineManager.userColor(userid, palette.window)
font.pointSize: fontMetrics.font.pointSize font.pointSize: fontMetrics.font.pointSize
text: displayName
} }
Label { Label {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
text: userid Layout.fillWidth: true
color: palette.buttonText color: palette.buttonText
font.pointSize: fontMetrics.font.pointSize * 0.9 font.pointSize: fontMetrics.font.pointSize * 0.9
text: userid
} }
} }
} }

View file

@ -13,29 +13,37 @@ Control {
required property int encryptionError required property int encryptionError
required property string eventId required property string eventId
padding: Nheko.paddingMedium
implicitHeight: contents.implicitHeight + Nheko.paddingMedium * 2
Layout.maximumWidth: contents.Layout.maximumWidth + padding * 2
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumWidth: contents.Layout.maximumWidth + padding * 2
implicitHeight: contents.implicitHeight + Nheko.paddingMedium * 2
padding: Nheko.paddingMedium
background: Rectangle {
color: palette.alternateBase
radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingMedium
visible: !Settings.bubbles // the bubble in a bubble looks odd
}
contentItem: RowLayout { contentItem: RowLayout {
id: contents id: contents
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
Image { Image {
source: "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?" + Nheko.theme.error
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: 24
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24
source: "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?" + Nheko.theme.error
} }
ColumnLayout { ColumnLayout {
spacing: Nheko.paddingSmall
Layout.fillWidth: true Layout.fillWidth: true
spacing: Nheko.paddingSmall
Label { Label {
id: encryptedText id: encryptedText
Layout.fillWidth: true
Layout.maximumWidth: implicitWidth + 1
color: palette.text
text: { text: {
switch (r.encryptionError) { switch (r.encryptionError) {
case Olm.MissingSession: case Olm.MissingSession:
@ -56,24 +64,13 @@ Control {
} }
textFormat: Text.PlainText textFormat: Text.PlainText
wrapMode: Label.WordWrap wrapMode: Label.WordWrap
color: palette.text
Layout.fillWidth: true
Layout.maximumWidth: implicitWidth + 1
} }
Button { Button {
visible: r.encryptionError == Olm.MissingSession || encryptionError == Olm.MissingSessionIndex
text: qsTr("Request key") text: qsTr("Request key")
visible: r.encryptionError == Olm.MissingSession || encryptionError == Olm.MissingSessionIndex
onClicked: room.requestKeyForEvent(eventId) onClicked: room.requestKeyForEvent(eventId)
} }
}
}
}
background: Rectangle {
color: palette.alternateBase
radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingMedium
visible: !Settings.bubbles // the bubble in a bubble looks odd
} }
} }

View file

@ -13,53 +13,48 @@ Control {
required property string userName required property string userName
padding: Nheko.paddingMedium Layout.fillWidth: true
//implicitHeight: contents.implicitHeight + padd * 2 //implicitHeight: contents.implicitHeight + padd * 2
Layout.maximumWidth: contents.Layout.maximumWidth + padding * 2 Layout.maximumWidth: contents.Layout.maximumWidth + padding * 2
Layout.fillWidth: true padding: Nheko.paddingMedium
background: Rectangle {
border.color: Nheko.theme.green
border.width: 2
color: palette.alternateBase
height: contents.implicitHeight + Nheko.paddingMedium * 2
radius: fontMetrics.lineSpacing / 2 + Nheko.paddingMedium
}
contentItem: RowLayout { contentItem: RowLayout {
id: contents id: contents
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
Image { Image {
source: "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: 24
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24
source: "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green
} }
ColumnLayout { ColumnLayout {
spacing: Nheko.paddingSmall
Layout.fillWidth: true Layout.fillWidth: true
spacing: Nheko.paddingSmall
MatrixText { MatrixText {
text: qsTr("%1 enabled end-to-end encryption").arg(r.userName)
font.bold: true
font.pointSize: 14
color: palette.text
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumWidth: implicitWidth + 1 Layout.maximumWidth: implicitWidth + 1
color: palette.text
font.bold: true
font.pointSize: 14
text: qsTr("%1 enabled end-to-end encryption").arg(r.userName)
} }
Label { Label {
Layout.fillWidth: true
Layout.maximumWidth: implicitWidth + 1
text: qsTr("Encryption keeps your messages safe by only allowing the people you sent the message to to read it. For extra security, if you want to make sure you are talking to the right people, you can verify them in real life.") text: qsTr("Encryption keeps your messages safe by only allowing the people you sent the message to to read it. For extra security, if you want to make sure you are talking to the right people, you can verify them in real life.")
textFormat: Text.PlainText textFormat: Text.PlainText
wrapMode: Label.WordWrap wrapMode: Label.WordWrap
Layout.fillWidth: true }
Layout.maximumWidth: implicitWidth + 1 }
}
}
}
background: Rectangle {
radius: fontMetrics.lineSpacing / 2 + Nheko.paddingMedium
height: contents.implicitHeight + Nheko.paddingMedium * 2
color: palette.alternateBase
border.color: Nheko.theme.green
border.width: 2
} }
} }

View file

@ -13,15 +13,19 @@ Control {
required property string eventId required property string eventId
required property string filename required property string filename
required property string filesize required property string filesize
property bool fitsMetadata: false
padding: Settings.bubbles? 8 : 12
//Layout.preferredHeight: rowa.implicitHeight + padding //Layout.preferredHeight: rowa.implicitHeight + padding
//Layout.maximumWidth: rowa.Layout.maximumWidth + metadataWidth + padding //Layout.maximumWidth: rowa.Layout.maximumWidth + metadataWidth + padding
property int metadataWidth: 0 property int metadataWidth: 0
property bool fitsMetadata: false
Layout.maximumWidth: rowa.Layout.maximumWidth + padding * 2 Layout.maximumWidth: rowa.Layout.maximumWidth + padding * 2
padding: Settings.bubbles ? 8 : 12
background: Rectangle {
color: palette.alternateBase
radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall
visible: !Settings.bubbles // the bubble in a bubble looks odd
}
contentItem: RowLayout { contentItem: RowLayout {
id: rowa id: rowa
@ -30,36 +34,32 @@ Control {
Rectangle { Rectangle {
id: button id: button
color: palette.light
radius: 22
Layout.preferredHeight: 44 Layout.preferredHeight: 44
Layout.preferredWidth: 44 Layout.preferredWidth: 44
color: palette.light
radius: 22
Image { Image {
id: img id: img
anchors.centerIn: parent
fillMode: Image.Pad
height: 40 height: 40
width: 40 source: "qrc:/icons/icons/ui/download.svg"
sourceSize.height: 40 sourceSize.height: 40
sourceSize.width: 40 sourceSize.width: 40
width: 40
anchors.centerIn: parent
source: "qrc:/icons/icons/ui/download.svg"
fillMode: Image.Pad
} }
TapHandler { TapHandler {
onSingleTapped: room.saveMedia(eventId)
gesturePolicy: TapHandler.ReleaseWithinBounds gesturePolicy: TapHandler.ReleaseWithinBounds
}
onSingleTapped: room.saveMedia(eventId)
}
NhekoCursorShape { NhekoCursorShape {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
} }
ColumnLayout { ColumnLayout {
id: col id: col
@ -68,31 +68,21 @@ Control {
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumWidth: implicitWidth + 1 Layout.maximumWidth: implicitWidth + 1
color: palette.text
elide: Text.ElideRight
text: evRoot.filename text: evRoot.filename
textFormat: Text.PlainText textFormat: Text.PlainText
elide: Text.ElideRight
color: palette.text
} }
Text { Text {
id: filesize_ id: filesize_
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumWidth: implicitWidth + 1 Layout.maximumWidth: implicitWidth + 1
color: palette.text
elide: Text.ElideRight
text: evRoot.filesize text: evRoot.filesize
textFormat: Text.PlainText textFormat: Text.PlainText
elide: Text.ElideRight
color: palette.text
} }
} }
} }
background: Rectangle {
color: palette.alternateBase
radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall
visible: !Settings.bubbles // the bubble in a bubble looks odd
}
} }

View file

@ -8,27 +8,28 @@ import QtQuick.Controls
import im.nheko import im.nheko
AbstractButton { AbstractButton {
required property int type
required property int originalWidth
required property int originalHeight
required property double proportionalHeight
required property string url
required property string blurhash required property string blurhash
required property string body required property string body
required property string filename
required property string eventId
required property int containerHeight required property int containerHeight
property double divisor: EventDelegateChooser.isReply ? 10 : 4 property double divisor: EventDelegateChooser.isReply ? 10 : 4
required property string eventId
required property string filename
property bool fitsMetadata: parent != null ? (parent.width - width) > metadataWidth + 4 : false
property int metadataWidth
required property int originalHeight
required property int originalWidth
required property double proportionalHeight
required property int type
required property string url
EventDelegateChooser.keepAspectRatio: true
EventDelegateChooser.maxWidth: originalWidth
EventDelegateChooser.maxHeight: containerHeight / divisor
EventDelegateChooser.aspectRatio: proportionalHeight EventDelegateChooser.aspectRatio: proportionalHeight
EventDelegateChooser.keepAspectRatio: true
hoverEnabled: true EventDelegateChooser.maxHeight: containerHeight / divisor
EventDelegateChooser.maxWidth: originalWidth
enabled: !EventDelegateChooser.isReply enabled: !EventDelegateChooser.isReply
hoverEnabled: true
state: (img.status != Image.Ready || timeline.privacyScreen.active) ? "BlurhashVisible" : "ImageVisible" state: (img.status != Image.Ready || timeline.privacyScreen.active) ? "BlurhashVisible" : "ImageVisible"
states: [ states: [
State { State {
name: "BlurhashVisible" name: "BlurhashVisible"
@ -39,11 +40,9 @@ AbstractButton {
visible: (img.status != Image.Ready) || (timeline.privacyScreen.active && blurhash) visible: (img.status != Image.Ready) || (timeline.privacyScreen.active && blurhash)
} }
} }
PropertyChanges { PropertyChanges {
img.opacity: 0 img.opacity: 0
} }
PropertyChanges { PropertyChanges {
mxcimage.opacity: 0 mxcimage.opacity: 0
} }
@ -57,11 +56,9 @@ AbstractButton {
visible: false visible: false
} }
} }
PropertyChanges { PropertyChanges {
img.opacity: 1 img.opacity: 1
} }
PropertyChanges { PropertyChanges {
mxcimage.opacity: 1 mxcimage.opacity: 1
} }
@ -70,114 +67,98 @@ AbstractButton {
transitions: [ transitions: [
Transition { Transition {
from: "ImageVisible" from: "ImageVisible"
to: "BlurhashVisible"
reversible: true reversible: true
to: "BlurhashVisible"
SequentialAnimation { SequentialAnimation {
PropertyAction { PropertyAction {
target: blurhash_
property: "visible" property: "visible"
target: blurhash_
} }
ParallelAnimation { ParallelAnimation {
NumberAnimation { NumberAnimation {
duration: 300
easing.type: Easing.Linear
property: "opacity"
target: blurhash_ target: blurhash_
property: "opacity" }
NumberAnimation {
duration: 300 duration: 300
easing.type: Easing.Linear easing.type: Easing.Linear
} property: "opacity"
NumberAnimation {
target: img target: img
property: "opacity"
duration: 300
easing.type: Easing.Linear
} }
NumberAnimation { NumberAnimation {
target: mxcimage
property: "opacity"
duration: 300 duration: 300
easing.type: Easing.Linear easing.type: Easing.Linear
property: "opacity"
target: mxcimage
} }
} }
} }
} }
] ]
property int metadataWidth onClicked: Settings.openImageExternal ? room.openMedia(eventId) : TimelineManager.openImageOverlay(room, url, eventId, originalWidth, proportionalHeight)
property bool fitsMetadata: parent != null ? (parent.width - width) > metadataWidth+4 : false
Image { Image {
id: img id: img
visible: !mxcimage.loaded
anchors.fill: parent anchors.fill: parent
source: url != "" ? (url.replace("mxc://", "image://MxcImage/") + "?scale") : ""
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
horizontalAlignment: Image.AlignLeft horizontalAlignment: Image.AlignLeft
smooth: true
mipmap: true mipmap: true
smooth: true
sourceSize.width: Math.min(Screen.desktopAvailableWidth, originalWidth < 1 ? Screen.desktopAvailableWidth : originalWidth) * Screen.devicePixelRatio source: url != "" ? (url.replace("mxc://", "image://MxcImage/") + "?scale") : ""
sourceSize.height: Math.min(Screen.desktopAvailableHeight, (originalWidth < 1 ? Screen.desktopAvailableHeight : originalWidth * proportionalHeight)) * Screen.devicePixelRatio sourceSize.height: Math.min(Screen.desktopAvailableHeight, (originalWidth < 1 ? Screen.desktopAvailableHeight : originalWidth * proportionalHeight)) * Screen.devicePixelRatio
sourceSize.width: Math.min(Screen.desktopAvailableWidth, originalWidth < 1 ? Screen.desktopAvailableWidth : originalWidth) * Screen.devicePixelRatio
visible: !mxcimage.loaded
} }
MxcAnimatedImage { MxcAnimatedImage {
id: mxcimage id: mxcimage
visible: loaded
roomm: room
play: !Settings.animateImagesOnHover || parent.hovered
eventId: parent.eventId
anchors.fill: parent anchors.fill: parent
eventId: parent.eventId
play: !Settings.animateImagesOnHover || parent.hovered
roomm: room
visible: loaded
} }
Image { Image {
id: blurhash_ id: blurhash_
source: blurhash ? ("image://blurhash/" + blurhash) : ("image://colorimage/:/icons/icons/ui/image-failed.svg?" + palette.buttonText) anchors.fill: parent
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
sourceSize.width: parent.width * Screen.devicePixelRatio source: blurhash ? ("image://blurhash/" + blurhash) : ("image://colorimage/:/icons/icons/ui/image-failed.svg?" + palette.buttonText)
sourceSize.height: parent.height * Screen.devicePixelRatio sourceSize.height: parent.height * Screen.devicePixelRatio
sourceSize.width: parent.width * Screen.devicePixelRatio
anchors.fill: parent
} }
onClicked: Settings.openImageExternal ? room.openMedia(eventId) : TimelineManager.openImageOverlay(room, url, eventId, originalWidth, proportionalHeight);
Item { Item {
id: overlay id: overlay
anchors.fill: parent anchors.fill: parent
visible: parent.hovered visible: parent.hovered
Rectangle { Rectangle {
id: container id: container
width: parent.width
implicitHeight: imgcaption.implicitHeight
anchors.bottom: overlay.bottom anchors.bottom: overlay.bottom
color: palette.window color: palette.window
implicitHeight: imgcaption.implicitHeight
opacity: 0.75 opacity: 0.75
width: parent.width
} }
Text { Text {
id: imgcaption id: imgcaption
anchors.fill: container anchors.fill: container
color: palette.text
elide: Text.ElideMiddle elide: Text.ElideMiddle
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
// See this MSC: https://github.com/matrix-org/matrix-doc/pull/2530 // See this MSC: https://github.com/matrix-org/matrix-doc/pull/2530
text: filename ? filename : body text: filename ? filename : body
color: palette.text verticalAlignment: Text.AlignVCenter
} }
} }
} }

View file

@ -5,11 +5,11 @@
import QtQuick 2.5 import QtQuick 2.5
import im.nheko 1.0 import im.nheko 1.0
TextMessage { TextMessage {
property bool isStateEvent property bool isStateEvent
font.italic: true
color: palette.buttonText color: palette.buttonText
font.italic: true
font.pointSize: isStateEvent ? 0.8 * Settings.fontSize : Settings.fontSize font.pointSize: isStateEvent ? 0.8 * Settings.fontSize : Settings.fontSize
horizontalAlignment: isStateEvent ? Text.AlignHCenter : undefined horizontalAlignment: isStateEvent ? Text.AlignHCenter : undefined
} }

View file

@ -7,14 +7,14 @@ import QtQuick.Controls 2.1
Label { Label {
property bool isStateEvent property bool isStateEvent
color: palette.text color: palette.text
horizontalAlignment: Text.AlignHCenter
height: Math.round(fontMetrics.height * 1.4) height: Math.round(fontMetrics.height * 1.4)
horizontalAlignment: Text.AlignHCenter
width: contentWidth * 1.2 width: contentWidth * 1.2
background: Rectangle { background: Rectangle {
radius: parent.height / 2
color: palette.alternateBase color: palette.alternateBase
radius: parent.height / 2
} }
} }

View file

@ -8,7 +8,7 @@ import im.nheko 1.0
MatrixText { MatrixText {
required property string typeString required property string typeString
text: qsTr("unimplemented event: ") + typeString
// width: parent.width // width: parent.width
color: palette.inactive.text color: palette.inactive.text
text: qsTr("unimplemented event: ") + typeString
} }

View file

@ -11,80 +11,79 @@ import im.nheko
Item { Item {
id: content id: content
required property double proportionalHeight
required property int type
required property int originalWidth
required property int duration
required property string thumbnailUrl
required property string eventId
required property string url
required property string body required property string body
required property string filesize
property double divisor: EventDelegateChooser.isReply ? 10 : 4 property double divisor: EventDelegateChooser.isReply ? 10 : 4
property int tempWidth: originalWidth < 1? 400: originalWidth required property int duration
implicitWidth: type == MtxEvent.VideoMessage ? Math.round(tempWidth*Math.min((timelineView.height/divisor)/(tempWidth*proportionalHeight), 1)) : 500 required property string eventId
width: Math.min(parent?.width ?? implicitWidth, implicitWidth) required property string filesize
height: (type == MtxEvent.VideoMessage ? width*proportionalHeight : 80) + fileInfoLabel.height property bool fitsMetadata: (parent.width - fileInfoLabel.width) > metadataWidth + 4
//implicitHeight: height //implicitHeight: height
property int metadataWidth property int metadataWidth
property bool fitsMetadata: (parent.width - fileInfoLabel.width) > metadataWidth+4 required property int originalWidth
required property double proportionalHeight
property int tempWidth: originalWidth < 1 ? 400 : originalWidth
required property string thumbnailUrl
required property int type
required property string url
height: (type == MtxEvent.VideoMessage ? width * proportionalHeight : 80) + fileInfoLabel.height
implicitWidth: type == MtxEvent.VideoMessage ? Math.round(tempWidth * Math.min((timelineView.height / divisor) / (tempWidth * proportionalHeight), 1)) : 500
width: Math.min(parent?.width ?? implicitWidth, implicitWidth)
MxcMedia { MxcMedia {
id: mxcmedia id: mxcmedia
// TODO: Show error in overlay or so? // TODO: Show error in overlay or so?
roomm: room roomm: room
videoOutput: videoOutput
audioOutput: AudioOutput { audioOutput: AudioOutput {
muted: mediaControls.muted muted: mediaControls.muted
volume: mediaControls.desiredVolume volume: mediaControls.desiredVolume
} }
videoOutput: videoOutput
} }
Rectangle { Rectangle {
id: videoContainer id: videoContainer
color: content.type == MtxEvent.VideoMessage ? palette.window : "transparent" color: content.type == MtxEvent.VideoMessage ? palette.window : "transparent"
width: parent.width
height: parent.height - fileInfoLabel.height height: parent.height - fileInfoLabel.height
width: parent.width
TapHandler { TapHandler {
onTapped: Settings.openVideoExternal ? room.openMedia(eventId) : mediaControls.showControls() onTapped: Settings.openVideoExternal ? room.openMedia(eventId) : mediaControls.showControls()
} }
Image { Image {
anchors.fill: parent anchors.fill: parent
source: content.thumbnailUrl ? thumbnailUrl.replace("mxc://", "image://MxcImage/") + "?scale" : "image://colorimage/:/icons/icons/ui/video-file.svg?" + palette.windowText
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
source: content.thumbnailUrl ? thumbnailUrl.replace("mxc://", "image://MxcImage/") + "?scale" : "image://colorimage/:/icons/icons/ui/video-file.svg?" + palette.windowText
VideoOutput { VideoOutput {
id: videoOutput id: videoOutput
visible: content.type == MtxEvent.VideoMessage
clip: true
anchors.fill: parent anchors.fill: parent
clip: true
fillMode: VideoOutput.PreserveAspectFit fillMode: VideoOutput.PreserveAspectFit
orientation: mxcmedia.orientation orientation: mxcmedia.orientation
visible: content.type == MtxEvent.VideoMessage
} }
} }
MediaControls { MediaControls {
id: mediaControls id: mediaControls
anchors.bottom: videoContainer.bottom
anchors.left: videoContainer.left anchors.left: videoContainer.left
anchors.right: videoContainer.right anchors.right: videoContainer.right
anchors.bottom: videoContainer.bottom
playingVideo: content.type == MtxEvent.VideoMessage
positionValue: mxcmedia.position
duration: mediaLoaded ? mxcmedia.duration : content.duration duration: mediaLoaded ? mxcmedia.duration : content.duration
mediaLoaded: mxcmedia.loaded mediaLoaded: mxcmedia.loaded
mediaState: mxcmedia.playbackState mediaState: mxcmedia.playbackState
onPositionChanged: mxcmedia.position = position playingVideo: content.type == MtxEvent.VideoMessage
onPlayPauseActivated: mxcmedia.playbackState == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play() positionValue: mxcmedia.position
onLoadActivated: mxcmedia.eventId = eventId onLoadActivated: mxcmedia.eventId = eventId
onPlayPauseActivated: mxcmedia.playbackState == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play()
onPositionChanged: mxcmedia.position = position
} }
} }
@ -93,15 +92,13 @@ Item {
id: fileInfoLabel id: fileInfoLabel
anchors.top: videoContainer.bottom anchors.top: videoContainer.bottom
color: palette.text
elide: Text.ElideRight
text: content.body + " [" + filesize + "]" text: content.body + " [" + filesize + "]"
textFormat: Text.RichText textFormat: Text.RichText
elide: Text.ElideRight
color: palette.text
background: Rectangle { background: Rectangle {
color: palette.base color: palette.base
} }
} }
} }

View file

@ -10,47 +10,50 @@ import im.nheko
Control { Control {
id: msgRoot id: msgRoot
property int metadataWidth: 0 required property string eventId
property bool fitsMetadata: false //parent.width - redactedLayout.width > metadataWidth + 4 property bool fitsMetadata: false //parent.width - redactedLayout.width > metadataWidth + 4
required property string eventId property int metadataWidth: 0
required property Room room required property Room room
contentItem: RowLayout {
id: redactedLayout
spacing: Nheko.paddingSmall
Image {
id: trashImg
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.preferredWidth: fontMetrics.font.pixelSize
Layout.preferredHeight: fontMetrics.font.pixelSize
source: "image://colorimage/:/icons/icons/ui/delete.svg?" + palette.text
}
Label {
id: redactedLabel
Layout.margins: 0
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.maximumWidth: implicitWidth + 1
Layout.fillWidth: true
property var redactedPair: msgRoot.room.formatRedactedEvent(msgRoot.eventId)
text: redactedPair["first"]
wrapMode: Label.WordWrap
ToolTip.text: redactedPair["second"]
ToolTip.visible: hh.hovered
HoverHandler {
id: hh
}
}
}
padding: Nheko.paddingSmall
Layout.maximumWidth: redactedLayout.Layout.maximumWidth + padding * 2 Layout.maximumWidth: redactedLayout.Layout.maximumWidth + padding * 2
padding: Nheko.paddingSmall
background: Rectangle { background: Rectangle {
color: palette.alternateBase color: palette.alternateBase
radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall
} }
contentItem: RowLayout {
id: redactedLayout
spacing: Nheko.paddingSmall
Image {
id: trashImg
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.preferredHeight: fontMetrics.font.pixelSize
Layout.preferredWidth: fontMetrics.font.pixelSize
source: "image://colorimage/:/icons/icons/ui/delete.svg?" + palette.text
}
Label {
id: redactedLabel
property var redactedPair: msgRoot.room.formatRedactedEvent(msgRoot.eventId)
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.fillWidth: true
Layout.margins: 0
Layout.maximumWidth: implicitWidth + 1
ToolTip.text: redactedPair["second"]
ToolTip.visible: hh.hovered
text: redactedPair["first"]
wrapMode: Label.WordWrap
HoverHandler {
id: hh
}
}
}
} }

View file

@ -11,43 +11,35 @@ import "../"
AbstractButton { AbstractButton {
id: r id: r
property color userColor: "red"
property bool keepFullText: false
required property string eventId required property string eventId
property bool keepFullText: false
required property int maxWidth
property var room_: room property var room_: room
property color userColor: "red"
property string userId: eventId ? room.dataById(eventId, Room.UserId, "") : "" property string userId: eventId ? room.dataById(eventId, Room.UserId, "") : ""
property string userName: eventId ? room.dataById(eventId, Room.UserName, "") : "" property string userName: eventId ? room.dataById(eventId, Room.UserName, "") : ""
implicitHeight: replyContainer.implicitHeight implicitHeight: replyContainer.implicitHeight
implicitWidth: replyContainer.implicitWidth implicitWidth: replyContainer.implicitWidth
required property int maxWidth
NhekoCursorShape { background: Rectangle {
anchors.fill: parent id: backgroundItem
cursorShape: Qt.PointingHandCursor
}
onClicked: { property color bgColor: palette.base
let link = reply.child.linkAt != undefined && reply.child.linkAt(pressX-colorline.width, pressY - userName_.implicitHeight); property color userColor: TimelineManager.userColor(r.userId, palette.base)
if (link) {
Nheko.openLink(link)
} else {
room.showEvent(r.eventId)
}
}
onPressAndHold: replyContextMenu.show(reply.child.copyText, reply.child.linkAt(pressX-colorline.width, pressY - userName_.implicitHeight), r.eventId)
color: Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.1))
z: -1
}
contentItem: TimelineEvent { contentItem: TimelineEvent {
id: timelineEvent id: timelineEvent
isStateEvent: false
room: r.room_
eventId: r.eventId eventId: r.eventId
replyTo: "" isStateEvent: false
mainInset: 4 + Nheko.paddingMedium mainInset: 4 + Nheko.paddingMedium
maxWidth: r.maxWidth maxWidth: r.maxWidth
replyTo: ""
room: r.room_
//height: replyContainer.implicitHeight //height: replyContainer.implicitHeight
data: Row { data: Row {
@ -58,14 +50,14 @@ AbstractButton {
Rectangle { Rectangle {
id: colorline id: colorline
width: 4
height: content.height
color: TimelineManager.userColor(r.userId, palette.base) color: TimelineManager.userColor(r.userId, palette.base)
height: content.height
width: 4
} }
Column { Column {
id: content id: content
data: [usernameBtn, timelineEvent.main,]
spacing: 0 spacing: 0
AbstractButton { AbstractButton {
@ -73,29 +65,31 @@ AbstractButton {
contentItem: Label { contentItem: Label {
id: userName_ id: userName_
text: r.userName
color: r.userColor color: r.userColor
text: r.userName
textFormat: Text.RichText textFormat: Text.RichText
width: timelineEvent.main?.width width: timelineEvent.main?.width
} }
onClicked: room.openUserProfile(r.userId) onClicked: room.openUserProfile(r.userId)
} }
data: [
usernameBtn, timelineEvent.main,
]
} }
} }
} }
background: Rectangle { onClicked: {
id: backgroundItem let link = reply.child.linkAt != undefined && reply.child.linkAt(pressX - colorline.width, pressY - userName_.implicitHeight);
if (link) {
z: -1 Nheko.openLink(link);
property color userColor: TimelineManager.userColor(r.userId, palette.base) } else {
property color bgColor: palette.base room.showEvent(r.eventId);
color: Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.1)) }
}
onPressAndHold: replyContextMenu.show(reply.child.copyText, reply.child.linkAt(pressX - colorline.width, pressY - userName_.implicitHeight), r.eventId)
NhekoCursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
} }
} }

View file

@ -7,14 +7,17 @@ import im.nheko
MatrixText { MatrixText {
required property string body required property string body
property string copyText: selectedText ? getText(selectionStart, selectionEnd) : body
property bool fitsMetadata: false //positionAt(width,height-4) == positionAt(width-metadataWidth-10, height-4)
required property string formatted
required property bool isOnlyEmoji required property bool isOnlyEmoji
property bool isReply: EventDelegateChooser.isReply property bool isReply: EventDelegateChooser.isReply
required property bool keepFullText required property bool keepFullText
required property string formatted
property string copyText: selectedText ? getText(selectionStart, selectionEnd) : body
property int metadataWidth: 100 property int metadataWidth: 100
property bool fitsMetadata: false //positionAt(width,height-4) == positionAt(width-metadataWidth-10, height-4)
enabled: !isReply
font.pointSize: (Settings.enlargeEmojiOnlyMessages && isOnlyEmoji > 0 && isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize
// table border-collapse doesn't seem to work // table border-collapse doesn't seem to work
text: ` text: `
@ -40,13 +43,9 @@ MatrixText {
`</style> `</style>
` + formatted.replace(/<del>/g, "<s>").replace(/<\/del>/g, "</s>").replace(/<strike>/g, "<s>").replace(/<\/strike>/g, "</s>") ` + formatted.replace(/<del>/g, "<s>").replace(/<\/del>/g, "</s>").replace(/<strike>/g, "<s>").replace(/<\/strike>/g, "</s>")
enabled: !isReply
font.pointSize: (Settings.enlargeEmojiOnlyMessages && isOnlyEmoji > 0 && isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize
NhekoCursorShape { NhekoCursorShape {
enabled: isReply
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: isReply
} }
} }

View file

@ -12,82 +12,69 @@ ApplicationWindow {
property var flow property var flow
onClosing: VerificationManager.removeVerificationFlow(flow)
title: stack.currentItem ? (stack.currentItem.title_ || "") : ""
modality: Qt.NonModal
color: palette.window color: palette.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: stack.currentItem.implicitHeight + 2 * Nheko.paddingMedium
//height: stack.currentItem.implicitHeight //height: stack.currentItem.implicitHeight
minimumHeight: stack.currentItem.implicitHeight + 2 * Nheko.paddingLarge minimumHeight: stack.currentItem.implicitHeight + 2 * Nheko.paddingLarge
height: stack.currentItem.implicitHeight + 2 * Nheko.paddingMedium
minimumWidth: 400 minimumWidth: 400
modality: Qt.NonModal
title: stack.currentItem ? (stack.currentItem.title_ || "") : ""
width: 400 width: 400
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
background: Rectangle { background: Rectangle {
color: palette.window color: palette.window
} }
onClosing: VerificationManager.removeVerificationFlow(flow)
StackView { StackView {
id: stack id: stack
anchors.centerIn: parent anchors.centerIn: parent
initialItem: newVerificationRequest
implicitWidth: dialog.width - 2* Nheko.paddingMedium
implicitHeight: dialog.height - 2 * Nheko.paddingMedium implicitHeight: dialog.height - 2 * Nheko.paddingMedium
implicitWidth: dialog.width - 2 * Nheko.paddingMedium
initialItem: newVerificationRequest
} }
Component { Component {
id: newVerificationRequest id: newVerificationRequest
NewVerificationRequest { NewVerificationRequest {
} }
} }
Component { Component {
id: waiting id: waiting
Waiting { Waiting {
} }
} }
Component { Component {
id: success id: success
Success { Success {
} }
} }
Component { Component {
id: failed id: failed
Failed { Failed {
} }
} }
Component { Component {
id: digitVerification id: digitVerification
DigitVerification { DigitVerification {
} }
} }
Component { Component {
id: emojiVerification id: emojiVerification
EmojiVerification { EmojiVerification {
} }
} }
Item { Item {
state: flow.state state: flow.state
states: [ states: [
State { State {
name: "PromptStartVerification" name: "PromptStartVerification"
@ -95,7 +82,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(null, newVerificationRequest) script: stack.replace(null, newVerificationRequest)
} }
}, },
State { State {
name: "CompareEmoji" name: "CompareEmoji"
@ -103,7 +89,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(null, emojiVerification) script: stack.replace(null, emojiVerification)
} }
}, },
State { State {
name: "CompareNumber" name: "CompareNumber"
@ -111,7 +96,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(null, digitVerification) script: stack.replace(null, digitVerification)
} }
}, },
State { State {
name: "WaitingForKeys" name: "WaitingForKeys"
@ -119,7 +103,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(null, waiting) script: stack.replace(null, waiting)
} }
}, },
State { State {
name: "WaitingForOtherToAccept" name: "WaitingForOtherToAccept"
@ -127,7 +110,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(null, waiting) script: stack.replace(null, waiting)
} }
}, },
State { State {
name: "WaitingForMac" name: "WaitingForMac"
@ -135,7 +117,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(null, waiting) script: stack.replace(null, waiting)
} }
}, },
State { State {
name: "Success" name: "Success"
@ -143,7 +124,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(null, success) script: stack.replace(null, success)
} }
}, },
State { State {
name: "Failed" name: "Failed"
@ -151,9 +131,7 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(null, failed) script: stack.replace(null, failed)
} }
} }
] ]
} }
} }

View file

@ -12,59 +12,56 @@ ColumnLayout {
spacing: 16 spacing: 16
Label { Label {
Layout.preferredWidth: 400
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap Layout.preferredWidth: 400
text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!")
color: palette.text color: palette.text
text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!")
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
} }
Item { Layout.fillHeight: true; }
RowLayout { RowLayout {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Label { Label {
color: palette.text
font.pixelSize: Qt.application.font.pixelSize * 2 font.pixelSize: Qt.application.font.pixelSize * 2
text: flow.sasList[0] text: flow.sasList[0]
color: palette.text
} }
Label { Label {
color: palette.text
font.pixelSize: Qt.application.font.pixelSize * 2 font.pixelSize: Qt.application.font.pixelSize * 2
text: flow.sasList[1] text: flow.sasList[1]
color: palette.text
} }
Label { Label {
color: palette.text
font.pixelSize: Qt.application.font.pixelSize * 2 font.pixelSize: Qt.application.font.pixelSize * 2
text: flow.sasList[2] text: flow.sasList[2]
color: palette.text
} }
} }
Item { Layout.fillHeight: true; } Item {
Layout.fillHeight: true
}
RowLayout { RowLayout {
Button { Button {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("They do not match!") text: qsTr("They do not match!")
onClicked: { onClicked: {
flow.cancel(); flow.cancel();
dialog.close(); dialog.close();
} }
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
Button { Button {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
text: qsTr("They match!") text: qsTr("They match!")
onClicked: flow.next() onClicked: flow.next()
} }
} }
} }

View file

@ -8,9 +8,9 @@ import QtQuick.Layouts
Rectangle { Rectangle {
color: "red" color: "red"
height: Qt.application.font.pixelSize * 4
implicitHeight: Qt.application.font.pixelSize * 4 implicitHeight: Qt.application.font.pixelSize * 4
implicitWidth: col.width implicitWidth: col.width
height: Qt.application.font.pixelSize * 4
width: col.width width: col.width
ColumnLayout { ColumnLayout {
@ -21,17 +21,14 @@ Rectangle {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
Label { Label {
Layout.preferredHeight: font.pixelSize * 2
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: col.emoji.emoji Layout.preferredHeight: font.pixelSize * 2
font.pixelSize: Qt.application.font.pixelSize * 2 font.pixelSize: Qt.application.font.pixelSize * 2
text: col.emoji.emoji
} }
Label { Label {
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
text: col.emoji.description text: col.emoji.description
} }
} }
} }

View file

@ -13,15 +13,16 @@ ColumnLayout {
spacing: 16 spacing: 16
Label { Label {
Layout.preferredWidth: 400
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap Layout.preferredWidth: 400
text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!")
color: palette.text color: palette.text
text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!")
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
} }
Item { Layout.fillHeight: true; }
RowLayout { RowLayout {
id: emojis id: emojis
@ -370,58 +371,52 @@ ColumnLayout {
Label { Label {
//height: font.pixelSize * 2 //height: font.pixelSize * 2
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: col.emoji.emoji
font.pixelSize: Qt.application.font.pixelSize * 2
font.family: Settings.emojiFont
color: palette.text color: palette.text
font.family: Settings.emojiFont
font.pixelSize: Qt.application.font.pixelSize * 2
text: col.emoji.emoji
} }
Label { Label {
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
color: palette.text
text: col.emoji.description text: col.emoji.description
color: palette.text
} }
} }
} }
} }
} }
Item { Layout.fillHeight: true; } Item {
Layout.fillHeight: true
}
Label { Label {
Layout.preferredWidth: 400
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap Layout.preferredWidth: 400
text: qsTr("The displayed emoji might look different in different clients if a different font is used. Similarly they might be translated into different languages. Nonetheless they should depict one of 64 different objects or animals. For example a lion and a cat are different, but a cat is the same even if one client just shows a cat face, while another client shows a full cat body.")
color: palette.text color: palette.text
text: qsTr("The displayed emoji might look different in different clients if a different font is used. Similarly they might be translated into different languages. Nonetheless they should depict one of 64 different objects or animals. For example a lion and a cat are different, but a cat is the same even if one client just shows a cat face, while another client shows a full cat body.")
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
} }
Item { Layout.fillHeight: true; }
RowLayout { RowLayout {
Button { Button {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("They do not match!") text: qsTr("They do not match!")
onClicked: { onClicked: {
flow.cancel(); flow.cancel();
dialog.close(); dialog.close();
} }
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
Button { Button {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
text: qsTr("They match!") text: qsTr("They match!")
onClicked: flow.next() onClicked: flow.next()
} }
} }
} }

View file

@ -9,14 +9,15 @@ import im.nheko 1.0
ColumnLayout { ColumnLayout {
property string title: qsTr("Verification failed") property string title: qsTr("Verification failed")
spacing: 16 spacing: 16
Text { Text {
id: content id: content
Layout.preferredWidth: 400
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap Layout.preferredWidth: 400
color: palette.text
text: { text: {
switch (flow.error) { switch (flow.error) {
case DeviceVerificationFlow.UnknownMethod: case DeviceVerificationFlow.UnknownMethod:
@ -35,23 +36,21 @@ ColumnLayout {
return qsTr("Unknown verification error."); return qsTr("Unknown verification error.");
} }
} }
color: palette.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
} }
Item { Layout.fillHeight: true; }
RowLayout { RowLayout {
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
Button { Button {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
text: qsTr("Close") text: qsTr("Close")
onClicked: dialog.close() onClicked: dialog.close()
} }
} }
} }

View file

@ -12,11 +12,11 @@ ColumnLayout {
spacing: 16 spacing: 16
Label { Label {
Layout.fillWidth: true
// Self verification // Self verification
Layout.preferredWidth: 400 Layout.preferredWidth: 400
Layout.fillWidth: true color: palette.text
wrapMode: Text.Wrap
text: { text: {
if (flow.sender) { if (flow.sender) {
if (flow.isSelfVerification) if (flow.isSelfVerification)
@ -35,32 +35,30 @@ ColumnLayout {
return qsTr("Your device (%1) has requested to be verified.").arg(flow.deviceId); return qsTr("Your device (%1) has requested to be verified.").arg(flow.deviceId);
} }
} }
color: palette.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
} }
Item { Layout.fillHeight: true; }
RowLayout { RowLayout {
Button { Button {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: flow.sender ? qsTr("Cancel") : qsTr("Deny") text: flow.sender ? qsTr("Cancel") : qsTr("Deny")
onClicked: { onClicked: {
flow.cancel(); flow.cancel();
dialog.close(); dialog.close();
} }
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
Button { Button {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
text: flow.sender ? qsTr("Start verification") : qsTr("Accept") text: flow.sender ? qsTr("Start verification") : qsTr("Accept")
onClicked: flow.next() onClicked: flow.next()
} }
} }
} }

View file

@ -14,27 +14,25 @@ ColumnLayout {
Label { Label {
id: content id: content
Layout.preferredWidth: 400
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap Layout.preferredWidth: 400
text: qsTr("Verification successful! Both sides verified their devices!")
color: palette.text color: palette.text
text: qsTr("Verification successful! Both sides verified their devices!")
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
} }
Item { Layout.fillHeight: true; }
RowLayout { RowLayout {
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
Button { Button {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
text: qsTr("Close") text: qsTr("Close")
onClicked: dialog.close() onClicked: dialog.close()
} }
} }
} }

View file

@ -10,14 +10,15 @@ import im.nheko 1.0
ColumnLayout { ColumnLayout {
property string title: qsTr("Waiting for other party…") property string title: qsTr("Waiting for other party…")
spacing: 16 spacing: 16
Label { Label {
id: content id: content
Layout.preferredWidth: 400
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap Layout.preferredWidth: 400
color: palette.text
text: { text: {
switch (flow.state) { switch (flow.state) {
case "WaitingForOtherToAccept": case "WaitingForOtherToAccept":
@ -30,32 +31,31 @@ ColumnLayout {
return ""; return "";
} }
} }
color: palette.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
} }
Item { Layout.fillHeight: true; }
Spinner { Spinner {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
foreground: palette.mid foreground: palette.mid
} }
Item { Layout.fillHeight: true; } Item {
Layout.fillHeight: true
}
RowLayout { RowLayout {
Button { Button {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("Cancel") text: qsTr("Cancel")
onClicked: { onClicked: {
flow.cancel(); flow.cancel();
dialog.close(); dialog.close();
} }
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
} }

View file

@ -8,21 +8,31 @@ import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import im.nheko import im.nheko
ApplicationWindow { ApplicationWindow {
id: aliasEditorW id: aliasEditorW
property var roomSettings
property var editingModel: Nheko.editAliases(roomSettings.roomId) property var editingModel: Nheko.editAliases(roomSettings.roomId)
property var roomSettings
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
minimumWidth: 300
minimumHeight: 400
height: 600 height: 600
minimumHeight: 400
minimumWidth: 300
modality: Qt.NonModal
title: qsTr("Aliases to %1").arg(roomSettings.roomName)
width: 500 width: 500
title: qsTr("Aliases to %1").arg(roomSettings.roomName); footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
editingModel.commit();
aliasEditorW.close();
}
onRejected: aliasEditorW.close()
}
// Shortcut { // Shortcut {
// sequence: StandardKey.Cancel // sequence: StandardKey.Cancel
@ -30,32 +40,27 @@ ApplicationWindow {
// } // }
ColumnLayout { ColumnLayout {
anchors.margins: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: 0 spacing: 0
MatrixText { MatrixText {
text: qsTr("List of aliases to this room. Usually you can only add aliases on your server. You can have one canonical alias and many alternate aliases.")
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
Layout.fillWidth: true
Layout.fillHeight: false
color: palette.text
Layout.bottomMargin: Nheko.paddingMedium Layout.bottomMargin: Nheko.paddingMedium
} Layout.fillHeight: false
ListView {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true color: palette.text
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
text: qsTr("List of aliases to this room. Usually you can only add aliases on your server. You can have one canonical alias and many alternate aliases.")
}
ListView {
id: view id: view
Layout.fillHeight: true
Layout.fillWidth: true
cacheBuffer: 50
clip: true clip: true
model: editingModel model: editingModel
spacing: 4 spacing: 4
cacheBuffer: 50
delegate: RowLayout { delegate: RowLayout {
anchors.left: parent.left anchors.left: parent.left
@ -63,79 +68,70 @@ ApplicationWindow {
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
text: model.name
color: model.isPublished ? palette.text : Nheko.theme.error color: model.isPublished ? palette.text : Nheko.theme.error
text: model.name
textFormat: Text.PlainText textFormat: Text.PlainText
} }
ImageButton { ImageButton {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.margins: 2 Layout.margins: 2
image: ":/icons/icons/ui/star.svg" ToolTip.text: model.isCanonical ? qsTr("Primary alias") : qsTr("Make primary alias")
hoverEnabled: true ToolTip.visible: hovered
buttonTextColor: model.isCanonical ? palette.highlight : palette.text buttonTextColor: model.isCanonical ? palette.highlight : palette.text
highlightColor: editingModel.canAdvertize ? palette.highlight : buttonTextColor highlightColor: editingModel.canAdvertize ? palette.highlight : buttonTextColor
hoverEnabled: true
ToolTip.visible: hovered image: ":/icons/icons/ui/star.svg"
ToolTip.text: model.isCanonical ? qsTr("Primary alias") : qsTr("Make primary alias")
onClicked: editingModel.makeCanonical(model.index) onClicked: editingModel.makeCanonical(model.index)
} }
ImageButton { ImageButton {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.margins: 2 Layout.margins: 2
image: ":/icons/icons/ui/building-shop.svg" ToolTip.text: qsTr("Advertise as an alias in this room")
hoverEnabled: true ToolTip.visible: hovered
buttonTextColor: model.isAdvertized ? palette.highlight : palette.text buttonTextColor: model.isAdvertized ? palette.highlight : palette.text
highlightColor: editingModel.canAdvertize ? palette.highlight : buttonTextColor highlightColor: editingModel.canAdvertize ? palette.highlight : buttonTextColor
hoverEnabled: true
ToolTip.visible: hovered image: ":/icons/icons/ui/building-shop.svg"
ToolTip.text: qsTr("Advertise as an alias in this room")
onClicked: editingModel.toggleAdvertize(model.index) onClicked: editingModel.toggleAdvertize(model.index)
} }
ImageButton { ImageButton {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.margins: 2 Layout.margins: 2
image: ":/icons/icons/ui/room-directory.svg"
hoverEnabled: true
buttonTextColor: model.isPublished ? palette.highlight : palette.text
ToolTip.visible: hovered
ToolTip.text: qsTr("Publish in room directory") ToolTip.text: qsTr("Publish in room directory")
ToolTip.visible: hovered
buttonTextColor: model.isPublished ? palette.highlight : palette.text
hoverEnabled: true
image: ":/icons/icons/ui/room-directory.svg"
onClicked: editingModel.togglePublish(model.index) onClicked: editingModel.togglePublish(model.index)
} }
ImageButton { ImageButton {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.margins: 2 Layout.margins: 2
image: ":/icons/icons/ui/dismiss.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Remove this alias") ToolTip.text: qsTr("Remove this alias")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/dismiss.svg"
onClicked: editingModel.deleteAlias(model.index) onClicked: editingModel.deleteAlias(model.index)
} }
} }
} }
RowLayout { RowLayout {
spacing: Nheko.paddingMedium
Layout.fillWidth: true Layout.fillWidth: true
spacing: Nheko.paddingMedium
MatrixTextField { MatrixTextField {
id: newAliasVal id: newAliasVal
focus: true
Layout.fillWidth: true Layout.fillWidth: true
selectByMouse: true
font.pixelSize: fontMetrics.font.pixelSize
color: palette.text color: palette.text
focus: true
font.pixelSize: fontMetrics.font.pixelSize
placeholderText: qsTr("#new-alias:server.tld") placeholderText: qsTr("#new-alias:server.tld")
selectByMouse: true
Component.onCompleted: forceActiveFocus() Component.onCompleted: forceActiveFocus()
Keys.onPressed: { Keys.onPressed: {
@ -145,10 +141,10 @@ ApplicationWindow {
} }
} }
} }
Button { Button {
text: qsTr("Add")
Layout.preferredWidth: 100 Layout.preferredWidth: 100
text: qsTr("Add")
onClicked: { onClicked: {
editingModel.addAlias(newAliasVal.text); editingModel.addAlias(newAliasVal.text);
newAliasVal.clear(); newAliasVal.clear();
@ -156,16 +152,4 @@ ApplicationWindow {
} }
} }
} }
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
editingModel.commit();
aliasEditorW.close();
}
onRejected: aliasEditorW.close();
}
} }

View file

@ -14,47 +14,54 @@ ApplicationWindow {
property var roomSettings property var roomSettings
minimumWidth: 340
minimumHeight: 450
width: 450
height: 680
color: palette.window color: palette.window
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 680
minimumHeight: 450
minimumWidth: 340
modality: Qt.NonModal
title: qsTr("Allowed rooms settings") title: qsTr("Allowed rooms settings")
width: 450
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
roomSettings.applyAllowedFromModel();
allowedDialog.close();
}
onRejected: allowedDialog.close()
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: roomSettingsDialog.close() onActivated: roomSettingsDialog.close()
} }
ColumnLayout { ColumnLayout {
anchors.margins: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: 0 spacing: 0
MatrixText { MatrixText {
text: qsTr("List of rooms that allow access to this room. Anyone who is in any of those rooms can join this room.")
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
Layout.fillWidth: true
Layout.fillHeight: false
color: palette.text
Layout.bottomMargin: Nheko.paddingMedium Layout.bottomMargin: Nheko.paddingMedium
} Layout.fillHeight: false
ListView {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true color: palette.text
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
text: qsTr("List of rooms that allow access to this room. Anyone who is in any of those rooms can join this room.")
}
ListView {
id: view id: view
Layout.fillHeight: true
Layout.fillWidth: true
cacheBuffer: 50
clip: true clip: true
model: roomSettings.allowedRoomsModel model: roomSettings.allowedRoomsModel
spacing: 4 spacing: 4
cacheBuffer: 50
delegate: RowLayout { delegate: RowLayout {
anchors.left: parent.left anchors.left: parent.left
@ -62,63 +69,57 @@ ApplicationWindow {
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
text: model.name
color: palette.text color: palette.text
text: model.name
textFormat: Text.PlainText textFormat: Text.PlainText
} }
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
text: model.isParent ? qsTr("Parent community") : qsTr("Other room")
color: palette.buttonText color: palette.buttonText
text: model.isParent ? qsTr("Parent community") : qsTr("Other room")
textFormat: Text.PlainText textFormat: Text.PlainText
} }
} }
ToggleButton { ToggleButton {
checked: model.allowed
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: model.allowed
onCheckedChanged: model.allowed = checked onCheckedChanged: model.allowed = checked
} }
} }
} }
Column { Column {
id: roomEntryCompleter id: roomEntryCompleter
Layout.fillWidth: true
Layout.fillWidth: true
spacing: 1 spacing: 1
z: 5 z: 5
Completer { Completer {
id: roomCompleter id: roomCompleter
visible: roomEntry.text.length > 0
width: parent.width
roomId: allowedDialog.roomSettings.roomId
completerName: "room"
bottomToTop: true
fullWidth: true
avatarHeight: Nheko.avatarSize / 2 avatarHeight: Nheko.avatarSize / 2
avatarWidth: Nheko.avatarSize / 2 avatarWidth: Nheko.avatarSize / 2
bottomToTop: true
centerRowContent: false centerRowContent: false
completerName: "room"
fullWidth: true
roomId: allowedDialog.roomSettings.roomId
rowMargin: 2 rowMargin: 2
rowSpacing: 2 rowSpacing: 2
visible: roomEntry.text.length > 0
width: parent.width
} }
MatrixTextField { MatrixTextField {
id: roomEntry id: roomEntry
color: palette.text
placeholderText: qsTr("Enter additional rooms not in the list yet...")
width: parent.width width: parent.width
placeholderText: qsTr("Enter additional rooms not in the list yet...")
color: palette.text
onTextEdited: {
roomCompleter.completer.searchString = text;
}
Keys.onPressed: { Keys.onPressed: {
if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) { if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
event.accepted = true; event.accepted = true;
@ -134,37 +135,23 @@ ApplicationWindow {
event.accepted = true; event.accepted = true;
} }
} }
onTextEdited: {
roomCompleter.completer.searchString = text;
}
} }
} }
Connections { Connections {
function onCompletionSelected(id) { function onCompletionSelected(id) {
console.log("selected: " + id); console.log("selected: " + id);
roomSettings.allowedRoomsModel.addRoom(id); roomSettings.allowedRoomsModel.addRoom(id);
roomEntry.clear(); roomEntry.clear();
} }
function onCountChanged() { function onCountChanged() {
if (roomCompleter.count > 0 && (roomCompleter.currentIndex < 0 || roomCompleter.currentIndex >= roomCompleter.count)) if (roomCompleter.count > 0 && (roomCompleter.currentIndex < 0 || roomCompleter.currentIndex >= roomCompleter.count))
roomCompleter.currentIndex = 0; roomCompleter.currentIndex = 0;
} }
target: roomCompleter target: roomCompleter
} }
} }
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
roomSettings.applyAllowedFromModel();
allowedDialog.close();
}
onRejected: allowedDialog.close()
}
} }

View file

@ -15,119 +15,18 @@ ApplicationWindow {
required property RoomSummary summary required property RoomSummary summary
title: summary.isSpace ? qsTr("Confirm community join") : qsTr("Confirm room join")
modality: Qt.WindowModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
color: palette.window color: palette.window
width: 350 flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: content.implicitHeight + Nheko.paddingLarge + footer.implicitHeight height: content.implicitHeight + Nheko.paddingLarge + footer.implicitHeight
modality: Qt.WindowModal
Shortcut { title: summary.isSpace ? qsTr("Confirm community join") : qsTr("Confirm room join")
sequence: StandardKey.Cancel width: 350
onActivated: dbb.rejected()
}
ColumnLayout {
id: content
spacing: Nheko.paddingMedium
anchors.margins: Nheko.paddingMedium
anchors.fill: parent
Avatar {
Layout.topMargin: Nheko.paddingMedium
url: summary.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
roomid: summary.roomid
displayName: summary.roomName
Layout.preferredHeight: 130
Layout.preferredWidth: 130
Layout.alignment: Qt.AlignHCenter
}
Spinner {
Layout.alignment: Qt.AlignHCenter
visible: !summary.isLoaded
foreground: palette.mid
running: !summary.isLoaded
}
TextEdit {
readOnly: true
textFormat: TextEdit.RichText
text: summary.roomName
font.pixelSize: fontMetrics.font.pixelSize * 2
color: palette.text
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
horizontalAlignment: TextEdit.AlignHCenter
wrapMode: TextEdit.Wrap
selectByMouse: true
}
TextEdit {
readOnly: true
textFormat: TextEdit.RichText
text: summary.roomid
font.pixelSize: fontMetrics.font.pixelSize * 0.8
color: palette.text
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
horizontalAlignment: TextEdit.AlignHCenter
wrapMode: TextEdit.Wrap
selectByMouse: true
}
RowLayout {
spacing: Nheko.paddingMedium
Layout.alignment: Qt.AlignHCenter
MatrixText {
text: qsTr("%n member(s)", "", summary.memberCount)
}
ImageButton {
image: ":/icons/icons/ui/people.svg"
enabled: false
}
}
TextEdit {
readOnly: true
textFormat: TextEdit.RichText
text: summary.roomTopic
color: palette.text
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
horizontalAlignment: TextEdit.AlignHCenter
wrapMode: TextEdit.Wrap
selectByMouse: true
}
Label {
id: promptLabel
text: summary.isKnockOnly ? qsTr("This room can't be joined directly. You can, however, knock on the room and room members can accept or decline this join request. You can additionally provide a reason for them to let you in below:") : qsTr("Do you want to join this room? You can optionally add a reason below:")
color: palette.text
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
font.bold: true
}
MatrixTextField {
id: reason
focus: true
Layout.fillWidth: true
text: joinRoomRoot.summary.reason
}
}
footer: DialogButtonBox { footer: DialogButtonBox {
id: dbb id: dbb
standardButtons: DialogButtonBox.Cancel standardButtons: DialogButtonBox.Cancel
onAccepted: { onAccepted: {
summary.reason = reason.text; summary.reason = reason.text;
summary.join(); summary.join();
@ -138,11 +37,102 @@ ApplicationWindow {
} }
Button { Button {
text: summary.isKnockOnly ? qsTr("Knock") : qsTr("Join")
enabled: input.text.match("#.+?:.{3,}")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
enabled: input.text.match("#.+?:.{3,}")
text: summary.isKnockOnly ? qsTr("Knock") : qsTr("Join")
}
} }
} Shortcut {
sequence: StandardKey.Cancel
onActivated: dbb.rejected()
}
ColumnLayout {
id: content
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
Avatar {
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: 130
Layout.preferredWidth: 130
Layout.topMargin: Nheko.paddingMedium
displayName: summary.roomName
roomid: summary.roomid
url: summary.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
}
Spinner {
Layout.alignment: Qt.AlignHCenter
foreground: palette.mid
running: !summary.isLoaded
visible: !summary.isLoaded
}
TextEdit {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
color: palette.text
font.pixelSize: fontMetrics.font.pixelSize * 2
horizontalAlignment: TextEdit.AlignHCenter
readOnly: true
selectByMouse: true
text: summary.roomName
textFormat: TextEdit.RichText
wrapMode: TextEdit.Wrap
}
TextEdit {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
color: palette.text
font.pixelSize: fontMetrics.font.pixelSize * 0.8
horizontalAlignment: TextEdit.AlignHCenter
readOnly: true
selectByMouse: true
text: summary.roomid
textFormat: TextEdit.RichText
wrapMode: TextEdit.Wrap
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Nheko.paddingMedium
MatrixText {
text: qsTr("%n member(s)", "", summary.memberCount)
}
ImageButton {
enabled: false
image: ":/icons/icons/ui/people.svg"
}
}
TextEdit {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
color: palette.text
horizontalAlignment: TextEdit.AlignHCenter
readOnly: true
selectByMouse: true
text: summary.roomTopic
textFormat: TextEdit.RichText
wrapMode: TextEdit.Wrap
}
Label {
id: promptLabel
Layout.fillWidth: true
color: palette.text
font.bold: true
horizontalAlignment: Text.AlignHCenter
text: summary.isKnockOnly ? qsTr("This room can't be joined directly. You can, however, knock on the room and room members can accept or decline this join request. You can additionally provide a reason for them to let you in below:") : qsTr("Do you want to join this room? You can optionally add a reason below:")
wrapMode: Text.Wrap
}
MatrixTextField {
id: reason
Layout.fillWidth: true
focus: true
text: joinRoomRoot.summary.reason
}
}
} }

View file

@ -11,13 +11,31 @@ import im.nheko
ApplicationWindow { ApplicationWindow {
id: createDirectRoot id: createDirectRoot
title: qsTr("Create Direct Chat")
property var profile
property bool otherUserHasE2ee: profile ? profile.deviceList.rowCount() > 0 : true property bool otherUserHasE2ee: profile ? profile.deviceList.rowCount() > 0 : true
property var profile
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
minimumHeight: layout.implicitHeight + footer.implicitHeight + Nheko.paddingLarge * 2 minimumHeight: layout.implicitHeight + footer.implicitHeight + Nheko.paddingLarge * 2
minimumWidth: Math.max(footer.implicitWidth, layout.implicitWidth) minimumWidth: Math.max(footer.implicitWidth, layout.implicitWidth)
modality: Qt.NonModal modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint title: qsTr("Create Direct Chat")
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Cancel
onAccepted: {
profile.startChat(encryption.checked);
createDirectRoot.close();
}
onRejected: createDirectRoot.close()
Button {
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
enabled: userID.isValidMxid && profile
text: "Start Direct Chat"
}
}
onVisibilityChanged: { onVisibilityChanged: {
userID.forceActiveFocus(); userID.forceActiveFocus();
@ -25,54 +43,56 @@ ApplicationWindow {
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: createDirectRoot.close() onActivated: createDirectRoot.close()
} }
ColumnLayout { ColumnLayout {
id: layout id: layout
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingLarge anchors.margins: Nheko.paddingLarge
spacing: userID.height / 4 spacing: userID.height / 4
GridLayout { GridLayout {
Layout.fillWidth: true Layout.fillWidth: true
rows: 2 columnSpacing: Nheko.paddingMedium
columns: 2 columns: 2
rowSpacing: Nheko.paddingSmall rowSpacing: Nheko.paddingSmall
columnSpacing: Nheko.paddingMedium rows: 2
Avatar { Avatar {
Layout.rowSpan: 2
Layout.preferredWidth: Nheko.avatarSize
Layout.preferredHeight: Nheko.avatarSize
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
userid: profile? profile.userid : "" Layout.preferredHeight: Nheko.avatarSize
url: profile? profile.avatarUrl.replace("mxc://", "image://MxcImage/") : null Layout.preferredWidth: Nheko.avatarSize
Layout.rowSpan: 2
displayName: profile ? profile.displayName : "" displayName: profile ? profile.displayName : ""
enabled: false enabled: false
url: profile ? profile.avatarUrl.replace("mxc://", "image://MxcImage/") : null
userid: profile ? profile.userid : ""
} }
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
text: profile? profile.displayName : ""
color: TimelineManager.userColor(userID.text, palette.window) color: TimelineManager.userColor(userID.text, palette.window)
font.pointSize: fontMetrics.font.pointSize font.pointSize: fontMetrics.font.pointSize
text: profile ? profile.displayName : ""
} }
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
text: userID.text
color: palette.buttonText color: palette.buttonText
font.pointSize: fontMetrics.font.pointSize * 0.9 font.pointSize: fontMetrics.font.pointSize * 0.9
text: userID.text
} }
} }
MatrixTextField { MatrixTextField {
id: userID id: userID
property bool isValidMxid: text.match("@.+?:.{3,}") property bool isValidMxid: text.match("@.+?:.{3,}")
Layout.fillWidth: true Layout.fillWidth: true
focus: true focus: true
label: qsTr("User to invite") label: qsTr("User to invite")
placeholderText: qsTr("@user:server.tld") placeholderText: qsTr("@user:server.tld")
onTextChanged: { onTextChanged: {
// we can't use "isValidMxid" here, since the property might only be reevaluated after this change handler. // we can't use "isValidMxid" here, since the property might only be reevaluated after this change handler.
if (text.match("@.+?:.{3,}")) { if (text.match("@.+?:.{3,}")) {
@ -81,35 +101,24 @@ ApplicationWindow {
profile = null; profile = null;
} }
} }
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Label { Label {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("Encryption") Layout.fillWidth: true
color: palette.text color: palette.text
text: qsTr("Encryption")
} }
ToggleButton { ToggleButton {
Layout.alignment: Qt.AlignRight
id: encryption id: encryption
Layout.alignment: Qt.AlignRight
checked: otherUserHasE2ee checked: otherUserHasE2ee
} }
} }
Item {
Item {Layout.fillHeight: true} Layout.fillHeight: true
}
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Cancel
Button {
text: "Start Direct Chat"
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
enabled: userID.isValidMxid && profile
}
onRejected: createDirectRoot.close();
onAccepted: {
profile.startChat(encryption.checked)
createDirectRoot.close()
} }
} }
} }

View file

@ -14,11 +14,32 @@ ApplicationWindow {
property bool space: false property bool space: false
title: space ? qsTr("New community") : qsTr("New Room")
minimumWidth: Math.max(rootLayout.implicitWidth+2*rootLayout.anchors.margins, footer.implicitWidth + Nheko.paddingLarge)
minimumHeight: rootLayout.implicitHeight+footer.implicitHeight+2*rootLayout.anchors.margins
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
minimumHeight: rootLayout.implicitHeight + footer.implicitHeight + 2 * rootLayout.anchors.margins
minimumWidth: Math.max(rootLayout.implicitWidth + 2 * rootLayout.anchors.margins, footer.implicitWidth + Nheko.paddingLarge)
modality: Qt.NonModal
title: space ? qsTr("New community") : qsTr("New Room")
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Cancel
onAccepted: {
var preset = 0;
if (isPublic.checked) {
preset = 1;
} else {
preset = isTrusted.checked ? 2 : 0;
}
Nheko.createRoom(space, newRoomName.text, newRoomTopic.text, newRoomAlias.text, isEncrypted.checked, preset);
createRoomRoot.close();
}
onRejected: createRoomRoot.close()
Button {
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
text: qsTr("Create Room")
}
}
onVisibilityChanged: { onVisibilityChanged: {
newRoomName.forceActiveFocus(); newRoomName.forceActiveFocus();
@ -26,10 +47,12 @@ ApplicationWindow {
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: createRoomRoot.close() onActivated: createRoomRoot.close()
} }
GridLayout { GridLayout {
id: rootLayout id: rootLayout
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingLarge anchors.margins: Nheko.paddingLarge
columns: 2 columns: 2
@ -37,127 +60,118 @@ ApplicationWindow {
MatrixTextField { MatrixTextField {
id: newRoomName id: newRoomName
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
focus: true focus: true
label: qsTr("Name") label: qsTr("Name")
placeholderText: qsTr("No name") placeholderText: qsTr("No name")
} }
MatrixTextField { MatrixTextField {
id: newRoomTopic id: newRoomTopic
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
focus: true focus: true
label: qsTr("Topic") label: qsTr("Topic")
placeholderText: qsTr("No topic") placeholderText: qsTr("No topic")
} }
Item { Item {
Layout.preferredHeight: newRoomName.height / 2 Layout.preferredHeight: newRoomName.height / 2
} }
RowLayout { RowLayout {
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
Label { Label {
Layout.preferredWidth: implicitWidth Layout.preferredWidth: implicitWidth
text: "#"
color: palette.text color: palette.text
text: "#"
} }
MatrixTextField { MatrixTextField {
id: newRoomAlias id: newRoomAlias
focus: true focus: true
placeholderText: qsTr("Alias") placeholderText: qsTr("Alias")
} }
Label { Label {
Layout.preferredWidth: implicitWidth
property string userName: userInfoGrid.profile.userid property string userName: userInfoGrid.profile.userid
text: userName.substring(userName.indexOf(":"))
Layout.preferredWidth: implicitWidth
color: palette.text color: palette.text
text: userName.substring(userName.indexOf(":"))
} }
} }
Label { Label {
Layout.preferredWidth: implicitWidth
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("Public") Layout.preferredWidth: implicitWidth
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Public rooms can be joined by anyone; private rooms need explicit invites.")
ToolTip.visible: privateHover.hovered
color: palette.text color: palette.text
text: qsTr("Public")
HoverHandler { HoverHandler {
id: privateHover id: privateHover
} }
ToolTip.visible: privateHover.hovered
ToolTip.text: qsTr("Public rooms can be joined by anyone; private rooms need explicit invites.")
ToolTip.delay: Nheko.tooltipDelay
} }
ToggleButton { ToggleButton {
id: isPublic
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.preferredWidth: implicitWidth Layout.preferredWidth: implicitWidth
id: isPublic
checked: false checked: false
} }
Label { Label {
visible: !space
Layout.preferredWidth: implicitWidth
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("Trusted") Layout.preferredWidth: implicitWidth
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("All invitees are given the same power level as the creator")
ToolTip.visible: trustedHover.hovered
color: palette.text color: palette.text
text: qsTr("Trusted")
visible: !space
HoverHandler { HoverHandler {
id: trustedHover id: trustedHover
} }
ToolTip.visible: trustedHover.hovered
ToolTip.text: qsTr("All invitees are given the same power level as the creator")
ToolTip.delay: Nheko.tooltipDelay
} }
ToggleButton { ToggleButton {
visible: !space id: isTrusted
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.preferredWidth: implicitWidth Layout.preferredWidth: implicitWidth
id: isTrusted
checked: false checked: false
enabled: !isPublic.checked enabled: !isPublic.checked
visible: !space
} }
Label { Label {
visible: !space
Layout.preferredWidth: implicitWidth
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("Encryption") Layout.preferredWidth: implicitWidth
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Caution: Encryption cannot be disabled")
ToolTip.visible: encryptionHover.hovered
color: palette.text color: palette.text
text: qsTr("Encryption")
visible: !space
HoverHandler { HoverHandler {
id: encryptionHover id: encryptionHover
} }
ToolTip.visible: encryptionHover.hovered
ToolTip.text: qsTr("Caution: Encryption cannot be disabled")
ToolTip.delay: Nheko.tooltipDelay
} }
ToggleButton { ToggleButton {
visible: !space id: isEncrypted
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.preferredWidth: implicitWidth Layout.preferredWidth: implicitWidth
id: isEncrypted
checked: false checked: false
visible: !space
} }
Item {
Item {Layout.fillHeight: true} Layout.fillHeight: true
}
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Cancel
Button {
text: qsTr("Create Room")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
onRejected: createRoomRoot.close();
onAccepted: {
var preset = 0;
if (isPublic.checked) {
preset = 1;
}
else {
preset = isTrusted.checked ? 2 : 0;
}
Nheko.createRoom(space, newRoomName.text, newRoomTopic.text, newRoomAlias.text, isEncrypted.checked, preset)
createRoomRoot.close();
} }
} }
} }

View file

@ -11,157 +11,151 @@ import im.nheko
ApplicationWindow { ApplicationWindow {
id: dialog id: dialog
property string roomid: ""
property string roomName: ""
property var onAccepted: undefined property var onAccepted: undefined
property string roomName: ""
property string roomid: ""
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowTitleHint
width: 275
height: 330 height: 330
minimumWidth: 250
minimumHeight: 220 minimumHeight: 220
minimumWidth: 250
modality: Qt.NonModal
title: {
if (roomid) {
return qsTr("Event expiration for %1").arg(roomName);
} else {
return qsTr("Event expiration");
}
}
width: 275
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
eventExpiry.save();
dialog.close();
}
onRejected: dialog.close()
}
EventExpiry { EventExpiry {
id: eventExpiry id: eventExpiry
roomid: dialog.roomid roomid: dialog.roomid
} }
title: {
if (roomid) {
return qsTr("Event expiration for %1").arg(roomName);
}
else {
return qsTr("Event expiration");
}
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: dbb.rejected() onActivated: dbb.rejected()
} }
ColumnLayout { ColumnLayout {
spacing: Nheko.paddingMedium
anchors.margins: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
MatrixText { MatrixText {
id: promptLabel id: promptLabel
Layout.fillHeight: false
Layout.fillWidth: true
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.2)
text: { text: {
if (roomid) { if (roomid) {
return qsTr("You can configure when your messages will be deleted in %1. This only happens when Nheko is open and has permissions to delete messages until Matrix servers support this feature natively. In general 0 means disable.").arg(roomName); return qsTr("You can configure when your messages will be deleted in %1. This only happens when Nheko is open and has permissions to delete messages until Matrix servers support this feature natively. In general 0 means disable.").arg(roomName);
} } else {
else {
return qsTr("You can configure when your messages will be deleted in all rooms unless configured otherwise. This only happens when Nheko is open and has permissions to delete messages until Matrix servers support this feature natively. In general 0 means disable."); return qsTr("You can configure when your messages will be deleted in all rooms unless configured otherwise. This only happens when Nheko is open and has permissions to delete messages until Matrix servers support this feature natively. In general 0 means disable.");
} }
} }
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.2)
Layout.fillWidth: true
Layout.fillHeight: false
} }
GridLayout { GridLayout {
Layout.fillHeight: true
Layout.fillWidth: true
columns: 2 columns: 2
rowSpacing: Nheko.paddingMedium rowSpacing: Nheko.paddingMedium
Layout.fillWidth: true
Layout.fillHeight: true
MatrixText { MatrixText {
text: qsTr("Expire events after X days") Layout.fillWidth: true
ToolTip.text: qsTr("Automatically redacts messages after X days, unless otherwise protected. Set to 0 to disable.") ToolTip.text: qsTr("Automatically redacts messages after X days, unless otherwise protected. Set to 0 to disable.")
ToolTip.visible: hh1.hovered ToolTip.visible: hh1.hovered
Layout.fillWidth: true text: qsTr("Expire events after X days")
HoverHandler { HoverHandler {
id: hh1 id: hh1
}
}
}
}
SpinBox { SpinBox {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
from: 0
to: 1000
stepSize: 1
value: eventExpiry.expireEventsAfterDays
onValueChanged: eventExpiry.expireEventsAfterDays = value
editable: true editable: true
} from: 0
stepSize: 1
to: 1000
value: eventExpiry.expireEventsAfterDays
onValueChanged: eventExpiry.expireEventsAfterDays = value
}
MatrixText { MatrixText {
text: qsTr("Only keep latest X events") Layout.fillWidth: true
ToolTip.text: qsTr("Deletes your events in this room if there are more than X newer messages unless otherwise protected. Set to 0 to disable.") ToolTip.text: qsTr("Deletes your events in this room if there are more than X newer messages unless otherwise protected. Set to 0 to disable.")
ToolTip.visible: hh2.hovered ToolTip.visible: hh2.hovered
Layout.fillWidth: true text: qsTr("Only keep latest X events")
HoverHandler { HoverHandler {
id: hh2 id: hh2
} }
} }
SpinBox { SpinBox {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
from: 0
to: 1000000
stepSize: 1
value: eventExpiry.expireEventsAfterCount
onValueChanged: eventExpiry.expireEventsAfterCount = value
editable: true editable: true
} from: 0
stepSize: 1
to: 1000000
value: eventExpiry.expireEventsAfterCount
onValueChanged: eventExpiry.expireEventsAfterCount = value
}
MatrixText { MatrixText {
text: qsTr("Always keep latest X events") Layout.fillWidth: true
ToolTip.text: qsTr("This prevents events to be deleted by the above 2 settings if they are the latest X messages from you in the room.") ToolTip.text: qsTr("This prevents events to be deleted by the above 2 settings if they are the latest X messages from you in the room.")
ToolTip.visible: hh3.hovered ToolTip.visible: hh3.hovered
Layout.fillWidth: true text: qsTr("Always keep latest X events")
HoverHandler { HoverHandler {
id: hh3 id: hh3
} }
} }
SpinBox { SpinBox {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
from: 0
to: 1000000
stepSize: 1
value: eventExpiry.protectLatestEvents
onValueChanged: eventExpiry.protectLatestEvents = value
editable: true editable: true
} from: 0
stepSize: 1
to: 1000000
value: eventExpiry.protectLatestEvents
onValueChanged: eventExpiry.protectLatestEvents = value
}
MatrixText { MatrixText {
text: qsTr("Include state events") Layout.fillWidth: true
ToolTip.text: qsTr("If this is turned on, old state events also get redacted. The latest state event of any type+key combination is excluded from redaction to not remove the room name and similar state by accident.") ToolTip.text: qsTr("If this is turned on, old state events also get redacted. The latest state event of any type+key combination is excluded from redaction to not remove the room name and similar state by accident.")
ToolTip.visible: hh4.hovered ToolTip.visible: hh4.hovered
Layout.fillWidth: true text: qsTr("Include state events")
HoverHandler { HoverHandler {
id: hh4 id: hh4
}
}
}
}
ToggleButton { ToggleButton {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: eventExpiry.expireStateEvents checked: eventExpiry.expireStateEvents
onToggled: eventExpiry.expireStateEvents = checked onToggled: eventExpiry.expireStateEvents = checked
} }
} }
} }
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
eventExpiry.save();
dialog.close();
} }
onRejected: dialog.close();
}
}

View file

@ -15,49 +15,46 @@ ApplicationWindow {
fallback.confirm(); fallback.confirm();
fallbackRoot.close(); fallbackRoot.close();
} }
function reject() { function reject() {
fallback.cancel(); fallback.cancel();
fallbackRoot.close(); fallbackRoot.close();
} }
color: palette.window color: palette.window
title: qsTr("Fallback authentication")
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: msg.implicitHeight + footer.implicitHeight height: msg.implicitHeight + footer.implicitHeight
title: qsTr("Fallback authentication")
width: Math.max(msg.implicitWidth, footer.implicitWidth) width: Math.max(msg.implicitWidth, footer.implicitWidth)
Shortcut {
sequence: StandardKey.Cancel
onActivated: fallbackRoot.reject()
}
Label {
id: msg
anchors.fill: parent
padding: 8
text: qsTr("Open the fallback, follow the steps, and confirm after completing them.")
}
footer: DialogButtonBox { footer: DialogButtonBox {
onAccepted: fallbackRoot.accept() onAccepted: fallbackRoot.accept()
onRejected: fallbackRoot.reject() onRejected: fallbackRoot.reject()
Button { Button {
text: qsTr("Open Fallback in Browser") text: qsTr("Open Fallback in Browser")
onClicked: fallback.openFallbackAuth() onClicked: fallback.openFallbackAuth()
} }
Button { Button {
text: qsTr("Cancel")
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
text: qsTr("Cancel")
} }
Button { Button {
text: qsTr("Confirm")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
text: qsTr("Confirm")
} }
} }
Shortcut {
sequence: StandardKey.Cancel
onActivated: fallbackRoot.reject()
}
Label {
id: msg
anchors.fill: parent
padding: 8
text: qsTr("Open the fallback, follow the steps, and confirm after completing them.")
}
} }

View file

@ -11,119 +11,115 @@ import im.nheko 1.0
ApplicationWindow { ApplicationWindow {
id: hiddenEventsDialog id: hiddenEventsDialog
property string roomid: ""
property string roomName: ""
property var onAccepted: undefined property var onAccepted: undefined
property string roomName: ""
property string roomid: ""
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowTitleHint
width: 275
height: 220 height: 220
minimumWidth: 250
minimumHeight: 220 minimumHeight: 220
minimumWidth: 250
modality: Qt.NonModal
title: {
if (roomid) {
return qsTr("Hidden events for %1").arg(roomName);
} else {
return qsTr("Hidden events");
}
}
width: 275
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
hiddenEvents.save();
hiddenEventsDialog.close();
}
onRejected: hiddenEventsDialog.close()
}
HiddenEvents { HiddenEvents {
id: hiddenEvents id: hiddenEvents
roomid: hiddenEventsDialog.roomid roomid: hiddenEventsDialog.roomid
} }
title: {
if (roomid) {
return qsTr("Hidden events for %1").arg(roomName);
}
else {
return qsTr("Hidden events");
}
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: dbb.rejected() onActivated: dbb.rejected()
} }
ColumnLayout { ColumnLayout {
spacing: Nheko.paddingMedium
anchors.margins: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
MatrixText { MatrixText {
id: promptLabel id: promptLabel
Layout.fillHeight: false
Layout.fillWidth: true
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.2)
text: { text: {
if (roomid) { if (roomid) {
return qsTr("These events will be <b>shown</b> in %1:").arg(roomName); return qsTr("These events will be <b>shown</b> in %1:").arg(roomName);
} } else {
else {
return qsTr("These events will be <b>shown</b> in all rooms:"); return qsTr("These events will be <b>shown</b> in all rooms:");
} }
} }
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.2)
Layout.fillWidth: true
Layout.fillHeight: false
} }
GridLayout { GridLayout {
Layout.fillHeight: true
Layout.fillWidth: true
columns: 2 columns: 2
rowSpacing: Nheko.paddingMedium rowSpacing: Nheko.paddingMedium
Layout.fillWidth: true
Layout.fillHeight: true
MatrixText { MatrixText {
text: qsTr("User events") Layout.fillWidth: true
ToolTip.text: qsTr("Joins, leaves, avatar and name changes, bans, …") ToolTip.text: qsTr("Joins, leaves, avatar and name changes, bans, …")
ToolTip.visible: hh1.hovered ToolTip.visible: hh1.hovered
Layout.fillWidth: true text: qsTr("User events")
HoverHandler { HoverHandler {
id: hh1 id: hh1
}
}
}
}
ToggleButton { ToggleButton {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.Member) checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.Member)
onToggled: hiddenEvents.toggle(MtxEvent.Member) onToggled: hiddenEvents.toggle(MtxEvent.Member)
} }
MatrixText { MatrixText {
text: qsTr("Power level changes") Layout.fillWidth: true
ToolTip.text: qsTr("Sent when a moderator is added/removed or the permissions of a room are changed.") ToolTip.text: qsTr("Sent when a moderator is added/removed or the permissions of a room are changed.")
ToolTip.visible: hh2.hovered ToolTip.visible: hh2.hovered
Layout.fillWidth: true text: qsTr("Power level changes")
HoverHandler { HoverHandler {
id: hh2 id: hh2
}
}
}
}
ToggleButton { ToggleButton {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.PowerLevels) checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.PowerLevels)
onToggled: hiddenEvents.toggle(MtxEvent.PowerLevels) onToggled: hiddenEvents.toggle(MtxEvent.PowerLevels)
} }
MatrixText { MatrixText {
text: qsTr("Stickers")
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Stickers")
} }
ToggleButton { ToggleButton {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.Sticker) checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.Sticker)
onToggled: hiddenEvents.toggle(MtxEvent.Sticker) onToggled: hiddenEvents.toggle(MtxEvent.Sticker)
} }
} }
} }
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
hiddenEvents.save();
hiddenEventsDialog.close();
}
onRejected: hiddenEventsDialog.close();
}
} }

View file

@ -13,72 +13,74 @@ import "../"
Window { Window {
id: ignoredUsers id: ignoredUsers
title: qsTr("Ignored users") color: palette.window
flags: Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 650 height: 650
width: 420
minimumHeight: 420 minimumHeight: 420
color: palette.window title: qsTr("Ignored users")
width: 420
ListView { ListView {
id: view id: view
anchors.fill: parent anchors.fill: parent
spacing: Nheko.paddingMedium
footerPositioning: ListView.OverlayFooter footerPositioning: ListView.OverlayFooter
model: TimelineManager.ignoredUsers model: TimelineManager.ignoredUsers
header: ColumnLayout { spacing: Nheko.paddingMedium
Text {
Layout.fillWidth: true
Layout.maximumWidth: view.width
wrapMode: Text.Wrap
color: palette.text
text: qsTr("Ignoring a user hides their messages (they can still see yours!).")
}
Item { Layout.preferredHeight: Nheko.paddingLarge }
}
delegate: RowLayout { delegate: RowLayout {
property var profile: TimelineManager.getGlobalUserProfile(modelData) property var profile: TimelineManager.getGlobalUserProfile(modelData)
width: view.width width: view.width
Avatar { Avatar {
enabled: false
displayName: profile.displayName displayName: profile.displayName
userid: profile.userid enabled: false
url: profile.avatarUrl.replace("mxc://", "image://MxcImage/") url: profile.avatarUrl.replace("mxc://", "image://MxcImage/")
userid: profile.userid
} }
Text { Text {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
elide: Text.ElideRight Layout.fillWidth: true
color: palette.text color: palette.text
elide: Text.ElideRight
text: modelData text: modelData
} }
ImageButton { ImageButton {
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24 Layout.preferredWidth: 24
image: ":/icons/icons/ui/dismiss.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Stop Ignoring.") ToolTip.text: qsTr("Stop Ignoring.")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/dismiss.svg"
onClicked: profile.ignored = false onClicked: profile.ignored = false
} }
} }
footer: DialogButtonBox { footer: DialogButtonBox {
z: 2
width: view.width
alignment: Qt.AlignRight alignment: Qt.AlignRight
standardButtons: DialogButtonBox.Ok standardButtons: DialogButtonBox.Ok
onAccepted: ignoredUsers.close() width: view.width
z: 2
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
color: palette.window color: palette.window
} }
onAccepted: ignoredUsers.close()
}
header: ColumnLayout {
Text {
Layout.fillWidth: true
Layout.maximumWidth: view.width
color: palette.text
text: qsTr("Ignoring a user hides their messages (they can still see yours!).")
wrapMode: Text.Wrap
}
Item {
Layout.preferredHeight: Nheko.paddingLarge
}
} }
} }
} }

View file

@ -4,33 +4,32 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Window 2.15 import QtQuick.Window 2.15
import ".." import ".."
import im.nheko 1.0 import im.nheko 1.0
Window { Window {
id: imageOverlay id: imageOverlay
required property string url
required property string eventId required property string eventId
required property Room room
required property int originalWidth required property int originalWidth
required property double proportionalHeight required property double proportionalHeight
required property Room room
flags: Qt.FramelessWindowHint required property string url
//visibility: Window.FullScreen //visibility: Window.FullScreen
color: Qt.rgba(0.2, 0.2, 0.2, 0.66) color: Qt.rgba(0.2, 0.2, 0.2, 0.66)
flags: Qt.FramelessWindowHint
Component.onCompleted: Nheko.setWindowRole(imageOverlay, "imageoverlay") Component.onCompleted: Nheko.setWindowRole(imageOverlay, "imageoverlay")
Shortcut { Shortcut {
sequences: [StandardKey.Cancel] sequences: [StandardKey.Cancel]
onActivated: imageOverlay.close() onActivated: imageOverlay.close()
} }
Shortcut { Shortcut {
sequences: [StandardKey.Copy] sequences: [StandardKey.Copy]
onActivated: { onActivated: {
if (room) { if (room) {
room.copyMedia(eventId); room.copyMedia(eventId);
@ -39,94 +38,85 @@ Window {
} }
} }
} }
TapHandler { TapHandler {
onSingleTapped: imageOverlay.close(); onSingleTapped: imageOverlay.close()
} }
Item { Item {
id: imgContainer id: imgContainer
property int imgSrcWidth: (imageOverlay.originalWidth && imageOverlay.originalWidth > 100) ? imageOverlay.originalWidth : Screen.width
property int imgSrcHeight: imageOverlay.proportionalHeight ? imgSrcWidth * imageOverlay.proportionalHeight : Screen.height property int imgSrcHeight: imageOverlay.proportionalHeight ? imgSrcWidth * imageOverlay.proportionalHeight : Screen.height
property int imgSrcWidth: (imageOverlay.originalWidth && imageOverlay.originalWidth > 100) ? imageOverlay.originalWidth : Screen.width
height: Math.min(parent.height || Screen.height, imgSrcHeight) height: Math.min(parent.height || Screen.height, imgSrcHeight)
width: Math.min(parent.width || Screen.width, imgSrcWidth) width: Math.min(parent.width || Screen.width, imgSrcWidth)
x: (parent.width - width) / 2 x: (parent.width - width) / 2
y: (parent.height - height) / 2 y: (parent.height - height) / 2
onScaleChanged: {
if (scale > 10)
scale = 10;
if (scale < 0.1)
scale = 0.1;
}
Image { Image {
id: img id: img
visible: !mxcimage.loaded property bool loaded: status == Image.Ready
anchors.fill: parent anchors.fill: parent
source: url.replace("mxc://", "image://MxcImage/")
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
smooth: true
mipmap: true mipmap: true
property bool loaded: status == Image.Ready smooth: true
source: url.replace("mxc://", "image://MxcImage/")
visible: !mxcimage.loaded
} }
MxcAnimatedImage { MxcAnimatedImage {
id: mxcimage id: mxcimage
visible: loaded
anchors.fill: parent anchors.fill: parent
roomm: imageOverlay.room
play: !Settings.animateImagesOnHover || mouseArea.hovered
eventId: imageOverlay.eventId eventId: imageOverlay.eventId
} play: !Settings.animateImagesOnHover || mouseArea.hovered
roomm: imageOverlay.room
onScaleChanged: { visible: loaded
if (scale > 10) scale = 10;
if (scale < 0.1) scale = 0.1
} }
} }
Item { Item {
anchors.fill: parent anchors.fill: parent
PinchHandler { PinchHandler {
target: imgContainer
maximumScale: 10 maximumScale: 10
minimumScale: 0.1 minimumScale: 0.1
target: imgContainer
} }
WheelHandler { WheelHandler {
property: "scale"
// workaround for QTBUG-87646 / QTBUG-112394 / QTBUG-112432: // workaround for QTBUG-87646 / QTBUG-112394 / QTBUG-112432:
// Magic Mouse pretends to be a trackpad but doesn't work with PinchHandler // Magic Mouse pretends to be a trackpad but doesn't work with PinchHandler
// and we don't yet distinguish mice and trackpads on Wayland either // and we don't yet distinguish mice and trackpads on Wayland either
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
property: "scale"
target: imgContainer target: imgContainer
} }
DragHandler { DragHandler {
target: imgContainer target: imgContainer
} }
HoverHandler { HoverHandler {
id: mouseArea id: mouseArea
} }
} }
Row { Row {
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: Nheko.paddingLarge anchors.margins: Nheko.paddingLarge
anchors.right: parent.right
anchors.top: parent.top
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
ImageButton { ImageButton {
height: 48 height: 48
width: 48
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/copy.svg" image: ":/icons/icons/ui/copy.svg"
width: 48
//ToolTip.visible: hovered //ToolTip.visible: hovered
//ToolTip.delay: Nheko.tooltipDelay //ToolTip.delay: Nheko.tooltipDelay
@ -142,12 +132,11 @@ Window {
imageOverlay.close(); imageOverlay.close();
} }
} }
ImageButton { ImageButton {
height: 48 height: 48
width: 48
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/download.svg" image: ":/icons/icons/ui/download.svg"
width: 48
//ToolTip.visible: hovered //ToolTip.visible: hovered
//ToolTip.delay: Nheko.tooltipDelay //ToolTip.delay: Nheko.tooltipDelay
@ -165,9 +154,9 @@ Window {
} }
ImageButton { ImageButton {
height: 48 height: 48
width: 48
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/dismiss.svg" image: ":/icons/icons/ui/dismiss.svg"
width: 48
//ToolTip.visible: hovered //ToolTip.visible: hovered
//ToolTip.delay: Nheko.tooltipDelay //ToolTip.delay: Nheko.tooltipDelay
@ -176,5 +165,4 @@ Window {
onClicked: imageOverlay.close() onClicked: imageOverlay.close()
} }
} }
} }

View file

@ -14,34 +14,46 @@ ApplicationWindow {
id: win id: win
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3) property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
property SingleImagePackModel imagePack
property int currentImageIndex: -1 property int currentImageIndex: -1
property SingleImagePackModel imagePack
readonly property int stickerDim: 128 readonly property int stickerDim: 128
readonly property int stickerDimPad: 128 + Nheko.paddingSmall readonly property int stickerDimPad: 128 + Nheko.paddingSmall
title: qsTr("Editing image pack")
height: 600
width: 600
color: palette.base color: palette.base
modality: Qt.WindowModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 600
modality: Qt.WindowModal
title: qsTr("Editing image pack")
width: 600
footer: DialogButtonBox {
id: buttons
standardButtons: DialogButtonBox.Save | DialogButtonBox.Cancel
onAccepted: {
imagePack.save();
win.close();
}
onRejected: win.close()
}
AdaptiveLayout { AdaptiveLayout {
id: adaptiveView id: adaptiveView
anchors.fill: parent anchors.fill: parent
singlePageMode: false
pageIndex: 0 pageIndex: 0
singlePageMode: false
AdaptiveLayoutElement { AdaptiveLayoutElement {
id: packlistC id: packlistC
visible: Settings.groupView
minimumWidth: 200
collapsedWidth: 200
preferredWidth: 300
maximumWidth: 300
clip: true clip: true
collapsedWidth: 200
maximumWidth: 300
minimumWidth: 200
preferredWidth: 300
visible: Settings.groupView
ListView { ListView {
//required property bool isEmote //required property bool isEmote
@ -49,75 +61,67 @@ ApplicationWindow {
model: imagePack model: imagePack
header: AvatarListTile {
title: imagePack.packname
avatarUrl: imagePack.avatarUrl
roomid: imagePack.statekey
subtitle: imagePack.statekey
index: -1
selectedIndex: currentImageIndex
TapHandler {
onSingleTapped: currentImageIndex = -1
}
Rectangle {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
height: parent.height - Nheko.paddingSmall * 2
width: 3
color: palette.highlight
}
}
footer: Button {
onClicked: addFilesDialog.open()
width: ListView.view.width
text: qsTr("Add images")
FileDialog {
id: addFilesDialog
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
fileMode: FileDialog.OpenFiles
nameFilters: [qsTr("Images (*.png *.webp *.gif *.jpg *.jpeg)")]
title: qsTr("Select images for pack")
acceptLabel: qsTr("Add to pack")
onAccepted: imagePack.addStickers(files)
}
}
delegate: AvatarListTile { delegate: AvatarListTile {
id: packItem id: packItem
property color background: palette.window property color background: palette.window
property color importantText: palette.text required property string body
property color unimportantText: palette.buttonText
property color bubbleBackground: palette.highlight property color bubbleBackground: palette.highlight
property color bubbleText: palette.highlightedText property color bubbleText: palette.highlightedText
property color importantText: palette.text
required property string shortCode required property string shortCode
property color unimportantText: palette.buttonText
required property string url required property string url
required property string body
title: shortCode
subtitle: body
avatarUrl: url avatarUrl: url
selectedIndex: currentImageIndex
crop: false crop: false
selectedIndex: currentImageIndex
subtitle: body
title: shortCode
TapHandler { TapHandler {
onSingleTapped: currentImageIndex = index onSingleTapped: currentImageIndex = index
} }
} }
footer: Button {
text: qsTr("Add images")
width: ListView.view.width
onClicked: addFilesDialog.open()
FileDialog {
id: addFilesDialog
acceptLabel: qsTr("Add to pack")
fileMode: FileDialog.OpenFiles
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
nameFilters: [qsTr("Images (*.png *.webp *.gif *.jpg *.jpeg)")]
title: qsTr("Select images for pack")
onAccepted: imagePack.addStickers(files)
} }
} }
header: AvatarListTile {
avatarUrl: imagePack.avatarUrl
index: -1
roomid: imagePack.statekey
selectedIndex: currentImageIndex
subtitle: imagePack.statekey
title: imagePack.packname
TapHandler {
onSingleTapped: currentImageIndex = -1
}
Rectangle {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
color: palette.highlight
height: parent.height - Nheko.paddingSmall * 2
width: 3
}
}
}
}
AdaptiveLayoutElement { AdaptiveLayoutElement {
id: packinfoC id: packinfoC
@ -127,211 +131,189 @@ ApplicationWindow {
GridLayout { GridLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
visible: currentImageIndex == -1
enabled: visible
columns: 2 columns: 2
enabled: visible
rowSpacing: Nheko.paddingLarge rowSpacing: Nheko.paddingLarge
visible: currentImageIndex == -1
Avatar { Avatar {
Layout.alignment: Qt.AlignHCenter
Layout.columnSpan: 2 Layout.columnSpan: 2
url: imagePack.avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: imagePack.packname
roomid: imagePack.statekey
Layout.preferredHeight: 130 Layout.preferredHeight: 130
Layout.preferredWidth: 130 Layout.preferredWidth: 130
crop: false crop: false
Layout.alignment: Qt.AlignHCenter displayName: imagePack.packname
roomid: imagePack.statekey
url: imagePack.avatarUrl.replace("mxc://", "image://MxcImage/")
ImageButton { ImageButton {
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Change the overview image for this pack") ToolTip.text: qsTr("Change the overview image for this pack")
ToolTip.visible: hovered
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top
anchors.leftMargin: Nheko.paddingMedium anchors.leftMargin: Nheko.paddingMedium
anchors.top: parent.top
anchors.topMargin: Nheko.paddingMedium anchors.topMargin: Nheko.paddingMedium
hoverEnabled: true
image: ":/icons/icons/ui/edit.svg" image: ":/icons/icons/ui/edit.svg"
onClicked: addAvatarDialog.open() onClicked: addAvatarDialog.open()
FileDialog { FileDialog {
id: addAvatarDialog id: addAvatarDialog
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
fileMode: FileDialog.OpenFile fileMode: FileDialog.OpenFile
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
nameFilters: [qsTr("Overview Image (*.png *.webp *.jpg *.jpeg)")] nameFilters: [qsTr("Overview Image (*.png *.webp *.jpg *.jpeg)")]
title: qsTr("Select overview image for pack") title: qsTr("Select overview image for pack")
onAccepted: imagePack.setAvatar(file) onAccepted: imagePack.setAvatar(file)
} }
} }
} }
MatrixTextField { MatrixTextField {
id: statekeyField id: statekeyField
visible: imagePack.roomid
Layout.fillWidth: true
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true
label: qsTr("State key") label: qsTr("State key")
text: imagePack.statekey text: imagePack.statekey
visible: imagePack.roomid
onTextEdited: imagePack.statekey = text onTextEdited: imagePack.statekey = text
} }
MatrixTextField { MatrixTextField {
Layout.fillWidth: true
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true
label: qsTr("Packname") label: qsTr("Packname")
text: imagePack.packname text: imagePack.packname
onTextEdited: imagePack.packname = text onTextEdited: imagePack.packname = text
} }
MatrixTextField { MatrixTextField {
Layout.fillWidth: true
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true
label: qsTr("Attribution") label: qsTr("Attribution")
text: imagePack.attribution text: imagePack.attribution
onTextEdited: imagePack.attribution = text onTextEdited: imagePack.attribution = text
} }
MatrixText { MatrixText {
Layout.margins: statekeyField.textPadding Layout.margins: statekeyField.textPadding
font.weight: Font.DemiBold
font.letterSpacing: font.pixelSize * 0.02 font.letterSpacing: font.pixelSize * 0.02
font.weight: Font.DemiBold
text: qsTr("Use as Emoji") text: qsTr("Use as Emoji")
} }
ToggleButton { ToggleButton {
checked: imagePack.isEmotePack
onCheckedChanged: imagePack.isEmotePack = checked
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
} checked: imagePack.isEmotePack
onCheckedChanged: imagePack.isEmotePack = checked
}
MatrixText { MatrixText {
Layout.margins: statekeyField.textPadding Layout.margins: statekeyField.textPadding
font.weight: Font.DemiBold
font.letterSpacing: font.pixelSize * 0.02 font.letterSpacing: font.pixelSize * 0.02
font.weight: Font.DemiBold
text: qsTr("Use as Sticker") text: qsTr("Use as Sticker")
} }
ToggleButton { ToggleButton {
checked: imagePack.isStickerPack
onCheckedChanged: imagePack.isStickerPack = checked
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
} checked: imagePack.isStickerPack
onCheckedChanged: imagePack.isStickerPack = checked
}
Item { Item {
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillHeight: true Layout.fillHeight: true
} }
} }
GridLayout { GridLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
visible: currentImageIndex >= 0
enabled: visible
columns: 2 columns: 2
enabled: visible
rowSpacing: Nheko.paddingLarge rowSpacing: Nheko.paddingLarge
visible: currentImageIndex >= 0
Avatar { Avatar {
Layout.alignment: Qt.AlignHCenter
Layout.columnSpan: 2 Layout.columnSpan: 2
url: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Url).replace("mxc://", "image://MxcImage/") + "?scale"
displayName: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
roomid: displayName
Layout.preferredHeight: 130 Layout.preferredHeight: 130
Layout.preferredWidth: 130 Layout.preferredWidth: 130
crop: false crop: false
Layout.alignment: Qt.AlignHCenter displayName: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
roomid: displayName
url: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Url).replace("mxc://", "image://MxcImage/") + "?scale"
} }
MatrixTextField { MatrixTextField {
Layout.fillWidth: true
Layout.columnSpan: 2
label: qsTr("Shortcode")
property int bindingCounter: 0 property int bindingCounter: 0
Layout.columnSpan: 2
Layout.fillWidth: true
label: qsTr("Shortcode")
text: bindingCounter, imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode) text: bindingCounter, imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
onTextEdited: { onTextEdited: {
imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode); imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode);
// force text field to update in case the model disagreed with the new value. // force text field to update in case the model disagreed with the new value.
bindingCounter++; bindingCounter++;
} }
} }
MatrixTextField { MatrixTextField {
id: bodyField id: bodyField
Layout.fillWidth: true
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true
label: qsTr("Body") label: qsTr("Body")
text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Body) text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Body)
onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.Body) onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.Body)
} }
MatrixText { MatrixText {
Layout.margins: bodyField.textPadding Layout.margins: bodyField.textPadding
font.weight: Font.DemiBold
font.letterSpacing: font.pixelSize * 0.02 font.letterSpacing: font.pixelSize * 0.02
font.weight: Font.DemiBold
text: qsTr("Use as Emoji") text: qsTr("Use as Emoji")
} }
ToggleButton { ToggleButton {
checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsEmote)
onCheckedChanged: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsEmote)
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
} checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsEmote)
onCheckedChanged: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsEmote)
}
MatrixText { MatrixText {
Layout.margins: bodyField.textPadding Layout.margins: bodyField.textPadding
font.weight: Font.DemiBold
font.letterSpacing: font.pixelSize * 0.02 font.letterSpacing: font.pixelSize * 0.02
font.weight: Font.DemiBold
text: qsTr("Use as Sticker") text: qsTr("Use as Sticker")
} }
ToggleButton { ToggleButton {
checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsSticker)
onCheckedChanged: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsSticker)
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
} checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsSticker)
onCheckedChanged: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsSticker)
}
MatrixText { MatrixText {
Layout.margins: bodyField.textPadding Layout.margins: bodyField.textPadding
font.weight: Font.DemiBold
font.letterSpacing: font.pixelSize * 0.02 font.letterSpacing: font.pixelSize * 0.02
font.weight: Font.DemiBold
text: qsTr("Remove from pack") text: qsTr("Remove from pack")
} }
Button { Button {
Layout.alignment: Qt.AlignRight
text: qsTr("Remove") text: qsTr("Remove")
onClicked: { onClicked: {
let temp = currentImageIndex; let temp = currentImageIndex;
currentImageIndex = -1; currentImageIndex = -1;
imagePack.remove(temp); imagePack.remove(temp);
} }
Layout.alignment: Qt.AlignRight
} }
Item { Item {
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillHeight: true Layout.fillHeight: true
} }
} }
} }
} }
} }
footer: DialogButtonBox {
id: buttons
standardButtons: DialogButtonBox.Save | DialogButtonBox.Cancel
onAccepted: {
imagePack.save();
win.close();
}
onRejected: win.close()
}
} }

View file

@ -12,95 +12,74 @@ import im.nheko 1.0
ApplicationWindow { ApplicationWindow {
id: win id: win
property Room room
property ImagePackListModel packlist
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3) property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
property SingleImagePackModel currentPack: packlist.packAt(currentPackIndex) property SingleImagePackModel currentPack: packlist.packAt(currentPackIndex)
property int currentPackIndex: 0 property int currentPackIndex: 0
property ImagePackListModel packlist
property Room room
readonly property int stickerDim: 128 readonly property int stickerDim: 128
readonly property int stickerDimPad: 128 + Nheko.paddingSmall readonly property int stickerDimPad: 128 + Nheko.paddingSmall
title: qsTr("Image pack settings")
height: 600
width: 800
color: palette.base color: palette.base
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 600
modality: Qt.NonModal
title: qsTr("Image pack settings")
width: 800
footer: DialogButtonBox {
id: buttons
Button {
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
text: qsTr("Close")
onClicked: win.close()
}
}
Component { Component {
id: packEditor id: packEditor
ImagePackEditorDialog { ImagePackEditorDialog {
} }
} }
AdaptiveLayout { AdaptiveLayout {
id: adaptiveView id: adaptiveView
anchors.fill: parent anchors.fill: parent
singlePageMode: false
pageIndex: 0 pageIndex: 0
singlePageMode: false
AdaptiveLayoutElement { AdaptiveLayoutElement {
id: packlistC id: packlistC
visible: Settings.groupView
minimumWidth: 200
collapsedWidth: 200 collapsedWidth: 200
preferredWidth: 300
maximumWidth: 300 maximumWidth: 300
minimumWidth: 200
preferredWidth: 300
visible: Settings.groupView
ListView { ListView {
model: packlist
clip: true clip: true
model: packlist
footer: ColumnLayout {
Button {
onClicked: {
var dialog = packEditor.createObject(timelineRoot, {
"imagePack": packlist.newPack(false)
});
dialog.show();
timelineRoot.destroyOnClose(dialog);
}
Layout.preferredWidth: packlistC.width
visible: !packlist.containsAccountPack
text: qsTr("Create account pack")
}
Button {
onClicked: {
var dialog = packEditor.createObject(timelineRoot, {
"imagePack": packlist.newPack(true)
});
dialog.show();
timelineRoot.destroyOnClose(dialog);
}
Layout.preferredWidth: packlistC.width
visible: room.permissions.canChange(MtxEvent.ImagePackInRoom)
text: qsTr("New room pack")
}
}
delegate: AvatarListTile { delegate: AvatarListTile {
id: packItem id: packItem
property color background: palette.window property color background: palette.window
property color importantText: palette.text
property color unimportantText: palette.buttonText
property color bubbleBackground: palette.highlight property color bubbleBackground: palette.highlight
property color bubbleText: palette.highlightedText property color bubbleText: palette.highlightedText
required property string displayName required property string displayName
required property bool fromAccountData required property bool fromAccountData
required property bool fromCurrentRoom required property bool fromCurrentRoom
required property bool fromSpace required property bool fromSpace
property color importantText: palette.text
required property string statekey required property string statekey
property color unimportantText: palette.buttonText
title: displayName roomid: statekey
selectedIndex: currentPackIndex
subtitle: { subtitle: {
if (fromAccountData) if (fromAccountData)
return qsTr("Private pack"); return qsTr("Private pack");
@ -111,19 +90,42 @@ ApplicationWindow {
else else
return qsTr("Globally enabled pack"); return qsTr("Globally enabled pack");
} }
selectedIndex: currentPackIndex title: displayName
roomid: statekey
TapHandler { TapHandler {
onSingleTapped: currentPackIndex = index onSingleTapped: currentPackIndex = index
} }
} }
footer: ColumnLayout {
Button {
Layout.preferredWidth: packlistC.width
text: qsTr("Create account pack")
visible: !packlist.containsAccountPack
onClicked: {
var dialog = packEditor.createObject(timelineRoot, {
"imagePack": packlist.newPack(false)
});
dialog.show();
timelineRoot.destroyOnClose(dialog);
} }
} }
Button {
Layout.preferredWidth: packlistC.width
text: qsTr("New room pack")
visible: room.permissions.canChange(MtxEvent.ImagePackInRoom)
onClicked: {
var dialog = packEditor.createObject(timelineRoot, {
"imagePack": packlist.newPack(true)
});
dialog.show();
timelineRoot.destroyOnClose(dialog);
}
}
}
}
}
AdaptiveLayoutElement { AdaptiveLayoutElement {
id: packinfoC id: packinfoC
@ -133,9 +135,9 @@ ApplicationWindow {
ColumnLayout { ColumnLayout {
id: packinfo id: packinfo
property string packName: currentPack ? currentPack.packname : ""
property string attribution: currentPack ? currentPack.attribution : "" property string attribution: currentPack ? currentPack.attribution : ""
property string avatarUrl: currentPack ? currentPack.avatarUrl : "" property string avatarUrl: currentPack ? currentPack.avatarUrl : ""
property string packName: currentPack ? currentPack.packname : ""
property string statekey: currentPack ? currentPack.statekey : "" property string statekey: currentPack ? currentPack.statekey : ""
anchors.fill: parent anchors.fill: parent
@ -143,56 +145,52 @@ ApplicationWindow {
spacing: Nheko.paddingLarge spacing: Nheko.paddingLarge
Avatar { Avatar {
url: packinfo.avatarUrl.replace("mxc://", "image://MxcImage/") Layout.alignment: Qt.AlignHCenter
displayName: packinfo.packName
roomid: packinfo.statekey
Layout.preferredHeight: 100 Layout.preferredHeight: 100
Layout.preferredWidth: 100 Layout.preferredWidth: 100
Layout.alignment: Qt.AlignHCenter displayName: packinfo.packName
enabled: false enabled: false
roomid: packinfo.statekey
url: packinfo.avatarUrl.replace("mxc://", "image://MxcImage/")
} }
MatrixText { MatrixText {
text: packinfo.packName Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2
font.pixelSize: Math.ceil(fontMetrics.pixelSize * 1.1) font.pixelSize: Math.ceil(fontMetrics.pixelSize * 1.1)
horizontalAlignment: TextEdit.AlignHCenter horizontalAlignment: TextEdit.AlignHCenter
Layout.alignment: Qt.AlignHCenter text: packinfo.packName
Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2
textFormat: TextEdit.PlainText textFormat: TextEdit.PlainText
} }
MatrixText { MatrixText {
text: packinfo.attribution
wrapMode: TextEdit.Wrap
horizontalAlignment: TextEdit.AlignHCenter
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2 Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2
horizontalAlignment: TextEdit.AlignHCenter
text: packinfo.attribution
textFormat: TextEdit.PlainText textFormat: TextEdit.PlainText
wrapMode: TextEdit.Wrap
} }
GridLayout { GridLayout {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
visible: currentPack && currentPack.roomid != ""
columns: 2 columns: 2
rowSpacing: Nheko.paddingMedium rowSpacing: Nheko.paddingMedium
visible: currentPack && currentPack.roomid != ""
MatrixText { MatrixText {
text: qsTr("Enable globally") text: qsTr("Enable globally")
} }
ToggleButton { ToggleButton {
Layout.alignment: Qt.AlignRight
ToolTip.text: qsTr("Enables this pack to be used in all rooms") ToolTip.text: qsTr("Enables this pack to be used in all rooms")
checked: currentPack ? currentPack.isGloballyEnabled : false checked: currentPack ? currentPack.isGloballyEnabled : false
onCheckedChanged: currentPack.isGloballyEnabled = checked onCheckedChanged: currentPack.isGloballyEnabled = checked
Layout.alignment: Qt.AlignRight
} }
} }
Button { Button {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("Edit")
enabled: currentPack.canEdit enabled: currentPack.canEdit
text: qsTr("Edit")
onClicked: { onClicked: {
var dialog = packEditor.createObject(timelineRoot, { var dialog = packEditor.createObject(timelineRoot, {
"imagePack": currentPack "imagePack": currentPack
@ -201,61 +199,40 @@ ApplicationWindow {
timelineRoot.destroyOnClose(dialog); timelineRoot.destroyOnClose(dialog);
} }
} }
GridView { GridView {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
model: currentPack
cellWidth: stickerDimPad
cellHeight: stickerDimPad
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
cacheBuffer: 500
cellHeight: stickerDimPad
cellWidth: stickerDimPad
clip: true clip: true
currentIndex: -1 // prevent sorting from stealing focus currentIndex: -1 // prevent sorting from stealing focus
cacheBuffer: 500 model: currentPack
// Individual emoji // Individual emoji
delegate: AbstractButton { delegate: AbstractButton {
width: stickerDim
height: stickerDim
hoverEnabled: true
ToolTip.text: ":" + model.shortCode + ": - " + model.body ToolTip.text: ":" + model.shortCode + ": - " + model.body
ToolTip.visible: hovered ToolTip.visible: hovered
contentItem: Image {
height: stickerDim height: stickerDim
hoverEnabled: true
width: stickerDim width: stickerDim
source: model.url.replace("mxc://", "image://MxcImage/") + "?scale"
fillMode: Image.PreserveAspectFit
}
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
color: hovered ? palette.highlight : 'transparent' color: hovered ? palette.highlight : 'transparent'
radius: 5 radius: 5
} }
contentItem: Image {
fillMode: Image.PreserveAspectFit
height: stickerDim
source: model.url.replace("mxc://", "image://MxcImage/") + "?scale"
width: stickerDim
} }
} }
} }
} }
} }
} }
footer: DialogButtonBox {
id: buttons
Button {
text: qsTr("Close")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
onClicked: win.close()
} }
}
} }

View file

@ -11,52 +11,27 @@ import im.nheko 1.0
ApplicationWindow { ApplicationWindow {
id: inputDialog id: inputDialog
property alias prompt: promptLabel.text
property alias echoMode: statusInput.echoMode property alias echoMode: statusInput.echoMode
signal accepted(text: string) property alias prompt: promptLabel.text
modality: Qt.NonModal signal accepted(string text)
flags: Qt.Dialog
width: 350
height: fontMetrics.lineSpacing * 7
function forceActiveFocus() { function forceActiveFocus() {
statusInput.forceActiveFocus(); statusInput.forceActiveFocus();
} }
Shortcut { flags: Qt.Dialog
sequence: StandardKey.Cancel height: fontMetrics.lineSpacing * 7
onActivated: dbb.rejected() modality: Qt.NonModal
} width: 350
ColumnLayout {
spacing: Nheko.paddingMedium
anchors.margins: Nheko.paddingMedium
anchors.fill: parent
Label {
id: promptLabel
color: palette.text
}
MatrixTextField {
id: statusInput
Layout.fillWidth: true
onAccepted: dbb.accepted()
focus: true
}
}
footer: DialogButtonBox { footer: DialogButtonBox {
id: dbb id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: { onAccepted: {
inputDialog.accepted(statusInput.text); inputDialog.accepted(statusInput.text);
inputDialog.close(); inputDialog.close();
} }
onRejected: { onRejected: {
@ -64,4 +39,28 @@ ApplicationWindow {
} }
} }
Shortcut {
sequence: StandardKey.Cancel
onActivated: dbb.rejected()
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
Label {
id: promptLabel
color: palette.text
}
MatrixTextField {
id: statusInput
Layout.fillWidth: true
focus: true
onAccepted: dbb.accepted()
}
}
} }

View file

@ -12,84 +12,106 @@ import im.nheko
ApplicationWindow { ApplicationWindow {
id: inviteDialogRoot id: inviteDialogRoot
property InviteesModel invitees
property var friendsCompleter property var friendsCompleter
property InviteesModel invitees
property var profile property var profile
minimumWidth: 300
Component.onCompleted: {
friendsCompleter = TimelineManager.completerFor("user", "friends")
width = 600
}
function addInvite(mxid, displayName, avatarUrl) { function addInvite(mxid, displayName, avatarUrl) {
if (mxid.match("@.+?:.{3,}")) { if (mxid.match("@.+?:.{3,}")) {
invitees.addUser(mxid, displayName, avatarUrl); invitees.addUser(mxid, displayName, avatarUrl);
} else } else
console.log("invalid mxid: " + mxid) console.log("invalid mxid: " + mxid);
} }
function cleanUpAndClose() { function cleanUpAndClose() {
if (inviteeEntry.isValidMxid) if (inviteeEntry.isValidMxid)
addInvite(inviteeEntry.text, "", ""); addInvite(inviteeEntry.text, "", "");
invitees.accept(); invitees.accept();
close(); close();
} }
title: qsTr("Invite users to %1").arg(invitees.room.plainRoomName)
height: 380
width: 340
color: palette.window color: palette.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 380
minimumWidth: 300
title: qsTr("Invite users to %1").arg(invitees.room.plainRoomName)
width: 340
footer: DialogButtonBox {
id: buttons
Button {
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
enabled: invitees.count > 0
text: qsTr("Invite")
onClicked: cleanUpAndClose()
}
Button {
DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole
text: qsTr("Cancel")
onClicked: inviteDialogRoot.close()
}
}
Component.onCompleted: {
friendsCompleter = TimelineManager.completerFor("user", "friends");
width = 600;
}
Shortcut { Shortcut {
sequence: "Ctrl+Enter" sequence: "Ctrl+Enter"
onActivated: cleanUpAndClose() onActivated: cleanUpAndClose()
} }
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: inviteDialogRoot.close() onActivated: inviteDialogRoot.close()
} }
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
Flow { Flow {
layoutDirection: Qt.LeftToRight
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: implicitHeight Layout.preferredHeight: implicitHeight
layoutDirection: Qt.LeftToRight
spacing: 4 spacing: 4
visible: !inviteesList.visible visible: !inviteesList.visible
Repeater { Repeater {
id: inviteesRepeater id: inviteesRepeater
model: invitees model: invitees
delegate: ItemDelegate { delegate: ItemDelegate {
onClicked: invitees.removeUser(model.mxid)
id: inviteeButton id: inviteeButton
contentItem: Label {
anchors.centerIn: parent
id: inviteeUserid
text: model.displayName != "" ? model.displayName : model.userid
color: inviteeButton.hovered ? palette.highlightedText: palette.text
maximumLineCount: 1
}
background: Rectangle { background: Rectangle {
border.color: palette.text border.color: palette.text
color: inviteeButton.hovered ? palette.highlight : palette.window
border.width: 1 border.width: 1
color: inviteeButton.hovered ? palette.highlight : palette.window
radius: inviteeButton.height / 2 radius: inviteeButton.height / 2
} }
} contentItem: Label {
} id: inviteeUserid
anchors.centerIn: parent
color: inviteeButton.hovered ? palette.highlightedText : palette.text
maximumLineCount: 1
text: model.displayName != "" ? model.displayName : model.userid
} }
onClicked: invitees.removeUser(model.mxid)
}
}
}
Label { Label {
text: qsTr("Search user")
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text color: palette.text
text: qsTr("Search user")
} }
RowLayout { RowLayout {
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
@ -99,147 +121,136 @@ ApplicationWindow {
property bool isValidMxid: text.match("@.+?:.{3,}") property bool isValidMxid: text.match("@.+?:.{3,}")
Layout.fillWidth: true
backgroundColor: palette.window backgroundColor: palette.window
placeholderText: qsTr("@user:yourserver.example.com", "Example user id. The name 'user' can be localized however you want.") placeholderText: qsTr("@user:yourserver.example.com", "Example user id. The name 'user' can be localized however you want.")
Layout.fillWidth: true
onAccepted: {
if (isValidMxid) {
addInvite(text, "", "");
clear()
}
else if (userSearch.count > 0) {
addInvite(userSearch.itemAtIndex(0).userid, userSearch.itemAtIndex(0).displayName, userSearch.itemAtIndex(0).avatarUrl)
clear()
}
}
Component.onCompleted: forceActiveFocus() Component.onCompleted: forceActiveFocus()
Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers & Qt.ControlModifier))
Keys.onPressed: { Keys.onPressed: {
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers === Qt.ControlModifier)) if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers === Qt.ControlModifier))
cleanUpAndClose(); cleanUpAndClose();
}
Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers & Qt.ControlModifier))
onAccepted: {
if (isValidMxid) {
addInvite(text, "", "");
clear();
} else if (userSearch.count > 0) {
addInvite(userSearch.itemAtIndex(0).userid, userSearch.itemAtIndex(0).displayName, userSearch.itemAtIndex(0).avatarUrl);
clear();
}
} }
onTextChanged: { onTextChanged: {
searchTimer.restart() searchTimer.restart();
if (isValidMxid) { if (isValidMxid) {
profile = TimelineManager.getGlobalUserProfile(text); profile = TimelineManager.getGlobalUserProfile(text);
} else } else
profile = null; profile = null;
} }
Timer { Timer {
id: searchTimer id: searchTimer
interval: 350 interval: 350
onTriggered: {
userSearch.model.setSearchString(parent.text)
}
}
}
onTriggered: {
userSearch.model.setSearchString(parent.text);
}
}
}
ToggleButton { ToggleButton {
id: searchOnServer id: searchOnServer
checked: false checked: false
onClicked: userSearch.model.setSearchString(inviteeEntry.text) onClicked: userSearch.model.setSearchString(inviteeEntry.text)
} }
MatrixText { MatrixText {
text: qsTr("Search on Server") text: qsTr("Search on Server")
} }
} }
RowLayout { RowLayout {
UserListRow { UserListRow {
visible: inviteeEntry.isValidMxid
id: del3 id: del3
Layout.preferredWidth: inviteDialogRoot.width/2
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
Layout.preferredHeight: implicitHeight Layout.preferredHeight: implicitHeight
displayName: profile? profile.displayName : "" Layout.preferredWidth: inviteDialogRoot.width / 2
avatarUrl: profile ? profile.avatarUrl : "" avatarUrl: profile ? profile.avatarUrl : ""
userid: inviteeEntry.text
onClicked: addInvite(inviteeEntry.text, displayName, avatarUrl)
bgColor: del3.hovered ? palette.dark : inviteDialogRoot.color bgColor: del3.hovered ? palette.dark : inviteDialogRoot.color
displayName: profile ? profile.displayName : ""
userid: inviteeEntry.text
visible: inviteeEntry.isValidMxid
onClicked: addInvite(inviteeEntry.text, displayName, avatarUrl)
} }
ListView { ListView {
visible: !inviteeEntry.isValidMxid
id: userSearch id: userSearch
model: searchOnServer.checked? userDirectory : friendsCompleter
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true
clip: true clip: true
model: searchOnServer.checked ? userDirectory : friendsCompleter
visible: !inviteeEntry.isValidMxid
delegate: UserListRow { delegate: UserListRow {
id: del2 id: del2
width: ListView.view.width
height: implicitHeight
displayName: model.displayName
userid: model.userid
avatarUrl: model.avatarUrl avatarUrl: model.avatarUrl
onClicked: addInvite(userid, displayName, avatarUrl)
bgColor: del2.hovered ? palette.dark : inviteDialogRoot.color bgColor: del2.hovered ? palette.dark : inviteDialogRoot.color
displayName: model.displayName
height: implicitHeight
userid: model.userid
width: ListView.view.width
onClicked: addInvite(userid, displayName, avatarUrl)
} }
} }
Rectangle { Rectangle {
Layout.fillHeight: true Layout.fillHeight: true
visible: inviteesList.visible
Layout.preferredWidth: 1 Layout.preferredWidth: 1
color: Nheko.theme.separator color: Nheko.theme.separator
visible: inviteesList.visible
} }
ListView { ListView {
id: inviteesList id: inviteesList
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
model: invitees Layout.fillWidth: true
clip: true clip: true
model: invitees
visible: inviteDialogRoot.width >= 500 visible: inviteDialogRoot.width >= 500
delegate: UserListRow { delegate: UserListRow {
id: del id: del
hoverEnabled: true
width: ListView.view.width
height: implicitHeight
onClicked: TimelineManager.openGlobalUserProfile(model.mxid)
userid: model.mxid
avatarUrl: model.avatarUrl avatarUrl: model.avatarUrl
displayName: model.displayName
bgColor: del.hovered ? palette.dark : inviteDialogRoot.color bgColor: del.hovered ? palette.dark : inviteDialogRoot.color
displayName: model.displayName
height: implicitHeight
hoverEnabled: true
userid: model.mxid
width: ListView.view.width
onClicked: TimelineManager.openGlobalUserProfile(model.mxid)
ImageButton { ImageButton {
id: removeButton
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Nheko.paddingSmall anchors.rightMargin: Nheko.paddingSmall
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: Nheko.paddingSmall anchors.topMargin: Nheko.paddingSmall
id: removeButton
image: ":/icons/icons/ui/dismiss.svg" image: ":/icons/icons/ui/dismiss.svg"
onClicked: invitees.removeUser(model.mxid) onClicked: invitees.removeUser(model.mxid)
} }
NhekoCursorShape { NhekoCursorShape {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
}
} }
} }
} }
footer: DialogButtonBox {
id: buttons
Button {
text: qsTr("Invite")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
enabled: invitees.count > 0
onClicked: cleanUpAndClose()
} }
Button {
text: qsTr("Cancel")
DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole
onClicked: inviteDialogRoot.close()
}
}
} }

View file

@ -11,48 +11,18 @@ import im.nheko 1.0
ApplicationWindow { ApplicationWindow {
id: joinRoomRoot id: joinRoomRoot
title: qsTr("Join room")
modality: Qt.WindowModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
color: palette.window color: palette.window
width: 350 flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: fontMetrics.lineSpacing * 7 height: fontMetrics.lineSpacing * 7
modality: Qt.WindowModal
Shortcut { title: qsTr("Join room")
sequence: StandardKey.Cancel width: 350
onActivated: dbb.rejected()
}
ColumnLayout {
spacing: Nheko.paddingMedium
anchors.margins: Nheko.paddingMedium
anchors.fill: parent
Label {
id: promptLabel
text: qsTr("Room ID or alias")
color: palette.text
}
MatrixTextField {
id: input
focus: true
Layout.fillWidth: true
onAccepted: {
if (input.text.match("#.+?:.{3,}"))
dbb.accepted();
}
}
}
footer: DialogButtonBox { footer: DialogButtonBox {
id: dbb id: dbb
standardButtons: DialogButtonBox.Cancel standardButtons: DialogButtonBox.Cancel
onAccepted: { onAccepted: {
Nheko.joinRoom(input.text); Nheko.joinRoom(input.text);
joinRoomRoot.close(); joinRoomRoot.close();
@ -62,11 +32,38 @@ ApplicationWindow {
} }
Button { Button {
text: qsTr("Join")
enabled: input.text.match("#.+?:.{3,}")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
enabled: input.text.match("#.+?:.{3,}")
text: qsTr("Join")
}
} }
} Shortcut {
sequence: StandardKey.Cancel
onActivated: dbb.rejected()
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
Label {
id: promptLabel
color: palette.text
text: qsTr("Room ID or alias")
}
MatrixTextField {
id: input
Layout.fillWidth: true
focus: true
onAccepted: {
if (input.text.match("#.+?:.{3,}"))
dbb.accepted();
}
}
}
} }

View file

@ -9,21 +9,20 @@ import im.nheko
P.MessageDialog { P.MessageDialog {
id: leaveRoomRoot id: leaveRoomRoot
required property string roomId
property string reason: "" property string reason: ""
required property string roomId
title: qsTr("Leave room")
text: qsTr("Are you sure you want to leave?")
modality: Qt.ApplicationModal
buttons: P.MessageDialog.Ok | P.MessageDialog.Cancel buttons: P.MessageDialog.Ok | P.MessageDialog.Cancel
onAccepted: { modality: Qt.ApplicationModal
text: qsTr("Are you sure you want to leave?")
title: qsTr("Leave room")
onAccepted: {
if (CallManager.haveCallInvite) { if (CallManager.haveCallInvite) {
callManager.rejectInvite(); callManager.rejectInvite();
} else if (CallManager.isOnCall) { } else if (CallManager.isOnCall) {
CallManager.hangUp(); CallManager.hangUp();
} }
Rooms.leave(roomId, reason) Rooms.leave(roomId, reason);
} }
} }

View file

@ -9,10 +9,11 @@ import im.nheko
P.MessageDialog { P.MessageDialog {
id: logoutRoot id: logoutRoot
title: qsTr("Log out")
text: CallManager.isOnCall ? qsTr("A call is in progress. Log out?") : qsTr("Are you sure you want to log out?")
modality: Qt.WindowModal
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
buttons: P.MessageDialog.Ok | P.MessageDialog.Cancel buttons: P.MessageDialog.Ok | P.MessageDialog.Cancel
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
modality: Qt.WindowModal
text: CallManager.isOnCall ? qsTr("A call is in progress. Log out?") : qsTr("Are you sure you want to log out?")
title: qsTr("Log out")
onAccepted: Nheko.logout() onAccepted: Nheko.logout()
} }

File diff suppressed because it is too large Load diff

View file

@ -9,21 +9,38 @@ import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import im.nheko 1.0 import im.nheko 1.0
ApplicationWindow { ApplicationWindow {
id: plEditorW id: plEditorW
property var roomSettings
property var editingModel: Nheko.editPowerlevels(roomSettings.roomId) property var editingModel: Nheko.editPowerlevels(roomSettings.roomId)
property var roomSettings
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
minimumWidth: 300
minimumHeight: 400
height: 600 height: 600
minimumHeight: 400
minimumWidth: 300
modality: Qt.NonModal
title: qsTr("Permissions in %1").arg(roomSettings.roomName)
width: 300 width: 300
title: qsTr("Permissions in %1").arg(roomSettings.roomName); footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
if (editingModel.isSpace) {
// TODO(Nico): Replace with showing a list of spaces to apply to
editingModel.updateSpacesModel();
plEditorW.close();
timelineRoot.showSpacePLApplyPrompt(roomSettings, editingModel);
} else {
editingModel.commit();
plEditorW.close();
}
}
onRejected: plEditorW.close()
}
// Shortcut { // Shortcut {
// sequence: StandardKey.Cancel // sequence: StandardKey.Cancel
@ -31,22 +48,21 @@ ApplicationWindow {
// } // }
ColumnLayout { ColumnLayout {
anchors.margins: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: 0 spacing: 0
MatrixText { MatrixText {
text: qsTr("Be careful when editing permissions. You can't lower the permissions of people with a same or higher level than you. Be careful when promoting others.")
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
Layout.fillWidth: true
Layout.fillHeight: false
color: palette.text
Layout.bottomMargin: Nheko.paddingMedium Layout.bottomMargin: Nheko.paddingMedium
Layout.fillHeight: false
Layout.fillWidth: true
color: palette.text
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
text: qsTr("Be careful when editing permissions. You can't lower the permissions of people with a same or higher level than you. Be careful when promoting others.")
} }
TabBar { TabBar {
id: bar id: bar
Layout.preferredWidth: parent.width Layout.preferredWidth: parent.width
NhekoTabButton { NhekoTabButton {
@ -57,95 +73,95 @@ ApplicationWindow {
} }
} }
Rectangle { Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
color: palette.alternateBase Layout.fillWidth: true
border.width: 1
border.color: Nheko.theme.separator border.color: Nheko.theme.separator
border.width: 1
color: palette.alternateBase
StackLayout { StackLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
currentIndex: bar.currentIndex currentIndex: bar.currentIndex
ColumnLayout { ColumnLayout {
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
MatrixText { MatrixText {
text: qsTr("Move permissions between roles to change them")
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
Layout.fillWidth: true
Layout.fillHeight: false Layout.fillHeight: false
color: palette.text
}
ReorderableListview {
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
text: qsTr("Move permissions between roles to change them")
}
ReorderableListview {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true
model: editingModel.types model: editingModel.types
delegate: RowLayout { delegate: RowLayout {
Column { Column {
Layout.fillWidth: true Layout.fillWidth: true
Text { visible: model.isType; text: model.displayName; color: palette.text}
Text { Text {
visible: !model.isType; color: palette.text
text: model.displayName
visible: model.isType
}
Text {
color: palette.text
text: { text: {
if (editingModel.adminLevel == model.powerlevel) if (editingModel.adminLevel == model.powerlevel)
return qsTr("Administrator (%1)").arg(model.powerlevel) return qsTr("Administrator (%1)").arg(model.powerlevel);
else if (editingModel.moderatorLevel == model.powerlevel) else if (editingModel.moderatorLevel == model.powerlevel)
return qsTr("Moderator (%1)").arg(model.powerlevel) return qsTr("Moderator (%1)").arg(model.powerlevel);
else if (editingModel.defaultUserLevel == model.powerlevel) else if (editingModel.defaultUserLevel == model.powerlevel)
return qsTr("User (%1)").arg(model.powerlevel) return qsTr("User (%1)").arg(model.powerlevel);
else else
return qsTr("Custom (%1)").arg(model.powerlevel) return qsTr("Custom (%1)").arg(model.powerlevel);
} }
color: palette.text visible: !model.isType
} }
} }
ImageButton { ImageButton {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.rightMargin: 2 Layout.rightMargin: 2
ToolTip.text: model.isType ? qsTr("Remove event type") : qsTr("Add event type")
ToolTip.visible: hovered
hoverEnabled: true
image: model.isType ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg" image: model.isType ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg"
visible: !model.isType || model.removeable visible: !model.isType || model.removeable
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: model.isType ? qsTr("Remove event type") : qsTr("Add event type")
onClicked: { onClicked: {
if (model.isType) { if (model.isType) {
editingModel.types.remove(index); editingModel.types.remove(index);
} else { } else {
typeEntry.y = offset typeEntry.y = offset;
typeEntry.visible = true typeEntry.visible = true;
typeEntry.index = index; typeEntry.index = index;
typeEntry.forceActiveFocus() typeEntry.forceActiveFocus();
} }
} }
} }
} }
MatrixTextField { MatrixTextField {
id: typeEntry id: typeEntry
property int index property int index
color: palette.text
visible: false
width: parent.width width: parent.width
z: 5 z: 5
visible: false
color: palette.text
Keys.onPressed: { Keys.onPressed: {
if (typeEntry.text.includes('.') && event.matches(StandardKey.InsertParagraphSeparator)) { if (typeEntry.text.includes('.') && event.matches(StandardKey.InsertParagraphSeparator)) {
editingModel.types.add(typeEntry.index, typeEntry.text) editingModel.types.add(typeEntry.index, typeEntry.text);
typeEntry.visible = false; typeEntry.visible = false;
typeEntry.clear(); typeEntry.clear();
event.accepted = true; event.accepted = true;
} } else if (event.matches(StandardKey.Cancel)) {
else if (event.matches(StandardKey.Cancel)) {
typeEntry.visible = false; typeEntry.visible = false;
typeEntry.clear(); typeEntry.clear();
event.accepted = true; event.accepted = true;
@ -153,7 +169,6 @@ ApplicationWindow {
} }
} }
} }
Button { Button {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Add new role") text: qsTr("Add new role")
@ -164,19 +179,18 @@ ApplicationWindow {
id: newPLLay id: newPLLay
anchors.fill: parent anchors.fill: parent
visible: false
color: palette.alternateBase color: palette.alternateBase
visible: false
RowLayout { RowLayout {
spacing: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
spacing: Nheko.paddingMedium
SpinBox { SpinBox {
id: newPLVal id: newPLVal
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true
editable: true editable: true
//from: -9007199254740991 //from: -9007199254740991
//to: 9007199254740991 //to: 9007199254740991
@ -192,10 +206,10 @@ ApplicationWindow {
} }
} }
} }
Button { Button {
text: qsTr("Add")
Layout.preferredWidth: 100 Layout.preferredWidth: 100
text: qsTr("Add")
onClicked: { onClicked: {
editingModel.addRole(newPLVal.value); editingModel.addRole(newPLVal.value);
newPLLay.visible = false; newPLLay.visible = false;
@ -205,42 +219,109 @@ ApplicationWindow {
} }
} }
} }
ColumnLayout { ColumnLayout {
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
MatrixText { MatrixText {
text: qsTr("Move users up or down to change their permissions")
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
Layout.fillWidth: true
Layout.fillHeight: false Layout.fillHeight: false
}
ReorderableListview {
Layout.fillWidth: true Layout.fillWidth: true
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
text: qsTr("Move users up or down to change their permissions")
}
ReorderableListview {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true
model: editingModel.users model: editingModel.users
delegate: RowLayout {
//anchors { fill: parent; margins: 2 }
id: row
Avatar {
id: avatar
Layout.leftMargin: 2
Layout.preferredHeight: Nheko.avatarSize / 2
Layout.preferredWidth: Nheko.avatarSize / 2
displayName: model.displayName
enabled: false
url: {
if (model.isUser)
return model.avatarUrl.replace("mxc://", "image://MxcImage/");
else if (editingModel.adminLevel >= model.powerlevel)
return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?" + palette.buttonText;
else if (editingModel.moderatorLevel >= model.powerlevel)
return "image://colorimage/:/icons/icons/ui/ribbon.svg?" + palette.buttonText;
else
return "image://colorimage/:/icons/icons/ui/person.svg?" + palette.buttonText;
}
userid: model.mxid
}
Column {
Layout.fillWidth: true
Text {
color: palette.text
text: model.displayName
visible: model.isUser
}
Text {
color: palette.text
text: model.mxid
visible: model.isUser
}
Text {
color: palette.text
text: {
if (editingModel.adminLevel == model.powerlevel)
return qsTr("Administrator (%1)").arg(model.powerlevel);
else if (editingModel.moderatorLevel == model.powerlevel)
return qsTr("Moderator (%1)").arg(model.powerlevel);
else
return qsTr("Custom (%1)").arg(model.powerlevel);
}
visible: !model.isUser
}
}
ImageButton {
Layout.alignment: Qt.AlignRight
Layout.rightMargin: 2
ToolTip.text: model.isUser ? qsTr("Remove user") : qsTr("Add user")
ToolTip.visible: hovered
hoverEnabled: true
image: model.isUser ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg"
visible: !model.isUser || model.removeable
onClicked: {
if (model.isUser) {
editingModel.users.remove(index);
} else {
userEntryCompleter.y = offset;
userEntryCompleter.visible = true;
userEntryCompleter.index = index;
userEntry.forceActiveFocus();
}
}
}
}
Column { Column {
id: userEntryCompleter id: userEntryCompleter
property int index: 0 property int index: 0
visible: false
width: parent.width
spacing: 1 spacing: 1
visible: false
width: parent.width
z: 5 z: 5
MatrixTextField { MatrixTextField {
id: userEntry id: userEntry
width: parent.width
//font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6) //font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
color: palette.text color: palette.text
onTextEdited: { width: parent.width
userCompleter.completer.searchString = text;
}
Keys.onPressed: { Keys.onPressed: {
if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) { if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
event.accepted = true; event.accepted = true;
@ -264,130 +345,45 @@ ApplicationWindow {
event.accepted = true; event.accepted = true;
} }
} }
onTextEdited: {
userCompleter.completer.searchString = text;
}
} }
Completer { Completer {
id: userCompleter id: userCompleter
visible: userEntry.text.length > 0
width: parent.width
roomId: plEditorW.roomSettings.roomId
completerName: "user"
bottomToTop: false
fullWidth: true
avatarHeight: Nheko.avatarSize / 2 avatarHeight: Nheko.avatarSize / 2
avatarWidth: Nheko.avatarSize / 2 avatarWidth: Nheko.avatarSize / 2
bottomToTop: false
centerRowContent: false centerRowContent: false
completerName: "user"
fullWidth: true
roomId: plEditorW.roomSettings.roomId
rowMargin: 2 rowMargin: 2
rowSpacing: 2 rowSpacing: 2
visible: userEntry.text.length > 0
width: parent.width
} }
} }
Connections { Connections {
id: userCompletionConnections
function onCompletionSelected(id) { function onCompletionSelected(id) {
console.log("selected: " + id); console.log("selected: " + id);
editingModel.users.add(userEntryCompleter.index, id); editingModel.users.add(userEntryCompleter.index, id);
userEntry.clear(); userEntry.clear();
userEntryCompleter.visible = false; userEntryCompleter.visible = false;
} }
function onCountChanged() { function onCountChanged() {
if (userCompleter.count > 0 && (userCompleter.currentIndex < 0 || userCompleter.currentIndex >= userCompleter.count)) if (userCompleter.count > 0 && (userCompleter.currentIndex < 0 || userCompleter.currentIndex >= userCompleter.count))
userCompleter.currentIndex = 0; userCompleter.currentIndex = 0;
} }
target: userCompleter target: userCompleter
id: userCompletionConnections
}
delegate: RowLayout {
//anchors { fill: parent; margins: 2 }
id: row
Avatar {
id: avatar
Layout.preferredHeight: Nheko.avatarSize / 2
Layout.preferredWidth: Nheko.avatarSize / 2
Layout.leftMargin: 2
userid: model.mxid
url: {
if (model.isUser)
return model.avatarUrl.replace("mxc://", "image://MxcImage/")
else if (editingModel.adminLevel >= model.powerlevel)
return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?" + palette.buttonText;
else if (editingModel.moderatorLevel >= model.powerlevel)
return "image://colorimage/:/icons/icons/ui/ribbon.svg?" + palette.buttonText;
else
return "image://colorimage/:/icons/icons/ui/person.svg?" + palette.buttonText;
}
displayName: model.displayName
enabled: false
}
Column {
Layout.fillWidth: true
Text { visible: model.isUser; text: model.displayName; color: palette.text}
Text { visible: model.isUser; text: model.mxid; color: palette.text}
Text {
visible: !model.isUser;
text: {
if (editingModel.adminLevel == model.powerlevel)
return qsTr("Administrator (%1)").arg(model.powerlevel)
else if (editingModel.moderatorLevel == model.powerlevel)
return qsTr("Moderator (%1)").arg(model.powerlevel)
else
return qsTr("Custom (%1)").arg(model.powerlevel)
}
color: palette.text
}
}
ImageButton {
Layout.alignment: Qt.AlignRight
Layout.rightMargin: 2
image: model.isUser ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg"
visible: !model.isUser || model.removeable
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: model.isUser ? qsTr("Remove user") : qsTr("Add user")
onClicked: {
if (model.isUser) {
editingModel.users.remove(index);
} else {
userEntryCompleter.y = offset
userEntryCompleter.visible = true
userEntryCompleter.index = index;
userEntry.forceActiveFocus()
} }
} }
} }
} }
} }
} }
} }
}
}
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
if (editingModel.isSpace) {
// TODO(Nico): Replace with showing a list of spaces to apply to
editingModel.updateSpacesModel();
plEditorW.close();
timelineRoot.showSpacePLApplyPrompt(roomSettings, editingModel)
} else {
editingModel.commit();
plEditorW.close();
}
}
onRejected: plEditorW.close();
}
}

View file

@ -12,124 +12,23 @@ import im.nheko
ApplicationWindow { ApplicationWindow {
id: applyDialog id: applyDialog
property RoomSettings roomSettings
property PowerlevelEditingModels editingModel property PowerlevelEditingModels editingModel
property RoomSettings roomSettings
minimumWidth: 340
minimumHeight: 450
width: 450
height: 680
color: palette.window color: palette.window
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 680
minimumHeight: 450
minimumWidth: 340
modality: Qt.NonModal
title: qsTr("Apply permission changes") title: qsTr("Apply permission changes")
width: 450
Shortcut {
sequence: StandardKey.Cancel
onActivated: roomSettingsDialog.close()
}
ColumnLayout {
anchors.margins: Nheko.paddingMedium
anchors.fill: parent
spacing: Nheko.paddingLarge
MatrixText {
text: qsTr("Which of the subcommunities and rooms should these permissions be applied to?")
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
Layout.fillWidth: true
Layout.fillHeight: false
color: palette.text
Layout.bottomMargin: Nheko.paddingMedium
}
GridLayout {
Layout.fillWidth: true
Layout.fillHeight: false
columns: 2
Label {
text: qsTr("Apply permissions recursively")
Layout.fillWidth: true
color: palette.text
}
ToggleButton {
checked: editingModel.spaces.applyToChildren
Layout.alignment: Qt.AlignRight
onCheckedChanged: editingModel.spaces.applyToChildren = checked
}
Label {
text: qsTr("Overwrite exisiting modifications in rooms")
Layout.fillWidth: true
color: palette.text
}
ToggleButton {
checked: editingModel.spaces.overwriteDiverged
Layout.alignment: Qt.AlignRight
onCheckedChanged: editingModel.spaces.overwriteDiverged = checked
}
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
id: view
clip: true
model: editingModel.spaces
spacing: 4
cacheBuffer: 50
delegate: RowLayout {
anchors.left: parent.left
anchors.right: parent.right
ColumnLayout {
Layout.fillWidth: true
Text {
Layout.fillWidth: true
text: model.displayName
color: palette.text
textFormat: Text.PlainText
elide: Text.ElideRight
}
Text {
Layout.fillWidth: true
text: {
if (!model.isEditable) return qsTr("No permissions to apply the new permissions here");
if (model.isAlreadyUpToDate) return qsTr("No changes needed");
if (model.isDifferentFromBase) return qsTr("Existing modifications to the permissions in this room will be overwritten");
return qsTr("Permissions synchronized with community")
}
elide: Text.ElideRight
color: palette.buttonText
textFormat: Text.PlainText
}
}
ToggleButton {
checked: model.applyPermissions
Layout.alignment: Qt.AlignRight
onCheckedChanged: model.applyPermissions = checked
enabled: model.isEditable
}
}
}
}
footer: DialogButtonBox { footer: DialogButtonBox {
id: dbb id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: { onAccepted: {
editingModel.spaces.commit(); editingModel.spaces.commit();
applyDialog.close(); applyDialog.close();
@ -137,4 +36,100 @@ ApplicationWindow {
onRejected: applyDialog.close() onRejected: applyDialog.close()
} }
Shortcut {
sequence: StandardKey.Cancel
onActivated: roomSettingsDialog.close()
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingLarge
MatrixText {
Layout.bottomMargin: Nheko.paddingMedium
Layout.fillHeight: false
Layout.fillWidth: true
color: palette.text
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
text: qsTr("Which of the subcommunities and rooms should these permissions be applied to?")
}
GridLayout {
Layout.fillHeight: false
Layout.fillWidth: true
columns: 2
Label {
Layout.fillWidth: true
color: palette.text
text: qsTr("Apply permissions recursively")
}
ToggleButton {
Layout.alignment: Qt.AlignRight
checked: editingModel.spaces.applyToChildren
onCheckedChanged: editingModel.spaces.applyToChildren = checked
}
Label {
Layout.fillWidth: true
color: palette.text
text: qsTr("Overwrite exisiting modifications in rooms")
}
ToggleButton {
Layout.alignment: Qt.AlignRight
checked: editingModel.spaces.overwriteDiverged
onCheckedChanged: editingModel.spaces.overwriteDiverged = checked
}
}
ListView {
id: view
Layout.fillHeight: true
Layout.fillWidth: true
cacheBuffer: 50
clip: true
model: editingModel.spaces
spacing: 4
delegate: RowLayout {
anchors.left: parent.left
anchors.right: parent.right
ColumnLayout {
Layout.fillWidth: true
Text {
Layout.fillWidth: true
color: palette.text
elide: Text.ElideRight
text: model.displayName
textFormat: Text.PlainText
}
Text {
Layout.fillWidth: true
color: palette.buttonText
elide: Text.ElideRight
text: {
if (!model.isEditable)
return qsTr("No permissions to apply the new permissions here");
if (model.isAlreadyUpToDate)
return qsTr("No changes needed");
if (model.isDifferentFromBase)
return qsTr("Existing modifications to the permissions in this room will be overwritten");
return qsTr("Permissions synchronized with community");
}
textFormat: Text.PlainText
}
}
ToggleButton {
Layout.alignment: Qt.AlignRight
checked: model.applyPermissions
enabled: model.isEditable
onCheckedChanged: model.applyPermissions = checked
}
}
}
}
} }

View file

@ -11,42 +11,39 @@ ApplicationWindow {
property alias rawMessage: rawMessageView.text property alias rawMessage: rawMessageView.text
height: 420
width: 420
color: palette.window color: palette.window
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 420
width: 420
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: rawMessageRoot.close()
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: rawMessageRoot.close() onActivated: rawMessageRoot.close()
} }
ScrollView { ScrollView {
anchors.margins: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium
padding: Nheko.paddingMedium padding: Nheko.paddingMedium
TextArea { TextArea {
id: rawMessageView id: rawMessageView
font: Nheko.monospaceFont() anchors.fill: parent
color: palette.text color: palette.text
font: Nheko.monospaceFont()
readOnly: true readOnly: true
textFormat: Text.PlainText textFormat: Text.PlainText
anchors.fill: parent
background: Rectangle { background: Rectangle {
color: palette.base color: palette.base
} }
} }
} }
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: rawMessageRoot.close()
}
} }

View file

@ -15,49 +15,46 @@ ApplicationWindow {
recaptcha.confirm(); recaptcha.confirm();
recaptchaRoot.close(); recaptchaRoot.close();
} }
function reject() { function reject() {
recaptcha.cancel(); recaptcha.cancel();
recaptchaRoot.close(); recaptchaRoot.close();
} }
color: palette.window color: palette.window
title: recaptcha.context
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: msg.implicitHeight + footer.implicitHeight height: msg.implicitHeight + footer.implicitHeight
title: recaptcha.context
width: Math.max(msg.implicitWidth, footer.implicitWidth) width: Math.max(msg.implicitWidth, footer.implicitWidth)
Shortcut {
sequence: StandardKey.Cancel
onActivated: recaptchaRoot.reject()
}
Label {
id: msg
anchors.fill: parent
padding: 8
text: qsTr("Solve the reCAPTCHA and press the confirm button")
}
footer: DialogButtonBox { footer: DialogButtonBox {
onAccepted: recaptchaRoot.accept() onAccepted: recaptchaRoot.accept()
onRejected: recaptchaRoot.reject() onRejected: recaptchaRoot.reject()
Button { Button {
text: qsTr("Open reCAPTCHA") text: qsTr("Open reCAPTCHA")
onClicked: recaptcha.openReCaptcha() onClicked: recaptcha.openReCaptcha()
} }
Button { Button {
text: qsTr("Cancel")
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
text: qsTr("Cancel")
} }
Button { Button {
text: qsTr("Confirm")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
text: qsTr("Confirm")
} }
} }
Shortcut {
sequence: StandardKey.Cancel
onActivated: recaptchaRoot.reject()
}
Label {
id: msg
anchors.fill: parent
padding: 8
text: qsTr("Solve the reCAPTCHA and press the confirm button")
}
} }

View file

@ -14,18 +14,24 @@ ApplicationWindow {
property ReadReceiptsProxy readReceipts property ReadReceiptsProxy readReceipts
property Room room property Room room
height: 380
width: 340
minimumHeight: 380
minimumWidth: headerTitle.width + 2 * Nheko.paddingMedium
color: palette.window color: palette.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 380
minimumHeight: 380
minimumWidth: headerTitle.width + 2 * Nheko.paddingMedium
width: 340
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: readReceiptsRoot.close()
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: readReceiptsRoot.close() onActivated: readReceiptsRoot.close()
} }
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
@ -34,98 +40,84 @@ ApplicationWindow {
Label { Label {
id: headerTitle id: headerTitle
color: palette.text
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
text: qsTr("Read receipts") color: palette.text
font.pointSize: fontMetrics.font.pointSize * 1.5 font.pointSize: fontMetrics.font.pointSize * 1.5
text: qsTr("Read receipts")
} }
ScrollView { ScrollView {
padding: Nheko.paddingMedium
ScrollBar.horizontal.visible: false
Layout.fillHeight: true Layout.fillHeight: true
Layout.minimumHeight: 200
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: 200
ScrollBar.horizontal.visible: false
padding: Nheko.paddingMedium
ListView { ListView {
id: readReceiptsList id: readReceiptsList
clip: true
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
clip: true
model: readReceipts model: readReceipts
delegate: ItemDelegate { delegate: ItemDelegate {
id: del id: del
onClicked: room.openUserProfile(model.mxid) ToolTip.text: model.mxid
padding: Nheko.paddingMedium ToolTip.visible: hovered
width: ListView.view.width
height: receiptLayout.implicitHeight + Nheko.paddingSmall * 2 height: receiptLayout.implicitHeight + Nheko.paddingSmall * 2
hoverEnabled: true hoverEnabled: true
ToolTip.visible: hovered padding: Nheko.paddingMedium
ToolTip.text: model.mxid width: ListView.view.width
background: Rectangle { background: Rectangle {
color: del.hovered ? palette.dark : readReceiptsRoot.color color: del.hovered ? palette.dark : readReceiptsRoot.color
} }
onClicked: room.openUserProfile(model.mxid)
RowLayout { RowLayout {
id: receiptLayout id: receiptLayout
spacing: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingSmall anchors.margins: Nheko.paddingSmall
spacing: Nheko.paddingMedium
Avatar { Avatar {
id: avatar id: avatar
Layout.preferredWidth: Nheko.avatarSize
Layout.preferredHeight: Nheko.avatarSize Layout.preferredHeight: Nheko.avatarSize
userid: model.mxid Layout.preferredWidth: Nheko.avatarSize
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: model.displayName displayName: model.displayName
enabled: false enabled: false
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
userid: model.mxid
} }
ColumnLayout { ColumnLayout {
Layout.fillWidth: true
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
Layout.fillWidth: true
ElidedLabel { ElidedLabel {
fullText: model.displayName Layout.fillWidth: true
color: TimelineManager.userColor(model ? model.mxid : "", palette.window) color: TimelineManager.userColor(model ? model.mxid : "", palette.window)
elideWidth: del.width - Nheko.paddingMedium - avatar.width
font.pointSize: fontMetrics.font.pointSize font.pointSize: fontMetrics.font.pointSize
elideWidth: del.width - Nheko.paddingMedium - avatar.width fullText: model.displayName
Layout.fillWidth: true
} }
ElidedLabel { ElidedLabel {
fullText: model.timestamp
color: palette.buttonText
font.pointSize: fontMetrics.font.pointSize * 0.9
elideWidth: del.width - Nheko.paddingMedium - avatar.width
Layout.fillWidth: true Layout.fillWidth: true
color: palette.buttonText
elideWidth: del.width - Nheko.paddingMedium - avatar.width
font.pointSize: fontMetrics.font.pointSize * 0.9
fullText: model.timestamp
} }
} }
} }
NhekoCursorShape { NhekoCursorShape {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
} }
} }
} }
} }
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: readReceiptsRoot.close()
}
} }

View file

@ -10,71 +10,66 @@ import im.nheko
ApplicationWindow { ApplicationWindow {
required property string eventId required property string eventId
width: 400
height: gl.implicitHeight + 2 * Nheko.paddingMedium height: gl.implicitHeight + 2 * Nheko.paddingMedium
title: qsTr("Report message") title: qsTr("Report message")
width: 400
GridLayout { GridLayout {
id: gl id: gl
columnSpacing: Nheko.paddingMedium
rowSpacing: Nheko.paddingMedium
columns: 2
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
columnSpacing: Nheko.paddingMedium
columns: 2
rowSpacing: Nheko.paddingMedium
Label { Label {
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Label.WordWrap
text: qsTr("This message you are reporting will be sent to your server administrator for review. Please note that not all server administrators review reported content. You should also ask a room moderator to remove the content if necessary.") text: qsTr("This message you are reporting will be sent to your server administrator for review. Please note that not all server administrators review reported content. You should also ask a room moderator to remove the content if necessary.")
wrapMode: Label.WordWrap
} }
Label { Label {
text: qsTr("Enter your reason for reporting:") text: qsTr("Enter your reason for reporting:")
} }
TextField { TextField {
id: reason id: reason
Layout.fillWidth: true Layout.fillWidth: true
} }
Label { Label {
text: qsTr("How bad is the message?") text: qsTr("How bad is the message?")
} }
Slider { Slider {
id: score id: score
from: 0
to: -100
stepSize: 25
snapMode: Slider.SnapAlways
Layout.fillWidth: true Layout.fillWidth: true
from: 0
snapMode: Slider.SnapAlways
stepSize: 25
to: -100
}
Item {
} }
Item {}
Label { Label {
text: { text: {
if (score.value === 0) if (score.value === 0)
return qsTr("Not bad") return qsTr("Not bad");
else if (score.value === -25) else if (score.value === -25)
return qsTr("Mild") return qsTr("Mild");
else if (score.value === -50) else if (score.value === -50)
return qsTr("Bad") return qsTr("Bad");
else if (score.value === -75) else if (score.value === -75)
return qsTr("Serious") return qsTr("Serious");
else if (score.value === -100) else if (score.value === -100)
return qsTr("Extremely serious") return qsTr("Extremely serious");
} }
} }
DialogButtonBox { DialogButtonBox {
Layout.columnSpan: 2
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.columnSpan: 2
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: { onAccepted: {
room.reportEvent(eventId, reason.text, score.value); room.reportEvent(eventId, reason.text, score.value);
close(); close();

View file

@ -13,21 +13,72 @@ import im.nheko 1.0
ApplicationWindow { ApplicationWindow {
id: roomDirectoryWindow id: roomDirectoryWindow
visible: true
minimumWidth: 340
minimumHeight: 340
height: 420
width: 650
color: palette.window color: palette.window
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 420
minimumHeight: 340
minimumWidth: 340
modality: Qt.NonModal
title: qsTr("Explore Public Rooms") title: qsTr("Explore Public Rooms")
visible: true
width: 650
footer: RowLayout {
spacing: Nheko.paddingMedium
width: parent.width
Button {
Layout.alignment: Qt.AlignRight
Layout.margins: Nheko.paddingMedium
text: qsTr("Close")
onClicked: roomDirectoryWindow.close()
}
}
header: RowLayout {
id: searchBarLayout
implicitHeight: roomSearch.height
spacing: Nheko.paddingMedium
width: parent.width
MatrixTextField {
id: roomSearch
Layout.fillWidth: true
color: palette.text
focus: true
font.pixelSize: fontMetrics.font.pixelSize
placeholderText: qsTr("Search for public rooms")
selectByMouse: true
Component.onCompleted: forceActiveFocus()
onTextChanged: searchTimer.restart()
}
MatrixTextField {
id: chooseServer
Layout.maximumWidth: 0.3 * header.width
Layout.minimumWidth: 0.3 * header.width
color: palette.text
placeholderText: qsTr("Choose custom homeserver")
onTextChanged: publicRooms.setMatrixServer(text)
}
Timer {
id: searchTimer
interval: 350
onTriggered: roomDirView.model.setSearchTerm(roomSearch.text)
}
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: roomDirectoryWindow.close() onActivated: roomDirectoryWindow.close()
} }
ListView { ListView {
id: roomDirView id: roomDirView
@ -37,171 +88,108 @@ ApplicationWindow {
delegate: Rectangle { delegate: Rectangle {
id: roomDirDelegate id: roomDirDelegate
property int avatarSize: fontMetrics.height * 3.2
property color background: palette.window property color background: palette.window
property color importantText: palette.text property color importantText: palette.text
property color unimportantText: palette.buttonText property color unimportantText: palette.buttonText
property int avatarSize: fontMetrics.height * 3.2
color: background color: background
height: avatarSize + Nheko.paddingLarge height: avatarSize + Nheko.paddingLarge
width: ListView.view.width width: ListView.view.width
RowLayout { RowLayout {
spacing: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
implicitHeight: textContent.implicitHeight implicitHeight: textContent.implicitHeight
spacing: Nheko.paddingMedium
Avatar { Avatar {
id: roomAvatar id: roomAvatar
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: Nheko.paddingMedium
Layout.preferredWidth: roomDirDelegate.avatarSize
Layout.preferredHeight: roomDirDelegate.avatarSize Layout.preferredHeight: roomDirDelegate.avatarSize
Layout.preferredWidth: roomDirDelegate.avatarSize
url: model.avatarUrl.replace("mxc://", "image://MxcImage/") Layout.rightMargin: Nheko.paddingMedium
roomid: model.roomid
displayName: model.name displayName: model.name
roomid: model.roomid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
} }
GridLayout { GridLayout {
id: textContent id: textContent
rows: 2
columns: 2
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.preferredWidth: parent.width - roomAvatar.width Layout.preferredWidth: parent.width - roomAvatar.width
columns: 2
rows: 2
ElidedLabel { ElidedLabel {
Layout.row: 0
Layout.column: 0 Layout.column: 0
Layout.fillWidth: true Layout.fillWidth: true
Layout.row: 0
color: roomDirDelegate.importantText color: roomDirDelegate.importantText
elideWidth: width elideWidth: width
fullText: model.name fullText: model.name
} }
Label { Label {
id: roomTopic id: roomTopic
color: roomDirDelegate.unimportantText
Layout.row: 1
Layout.column: 0 Layout.column: 0
font.pointSize: fontMetrics.font.pointSize*0.9
elide: Text.ElideRight
maximumLineCount: 2
Layout.fillWidth: true Layout.fillWidth: true
Layout.row: 1
color: roomDirDelegate.unimportantText
elide: Text.ElideRight
font.pointSize: fontMetrics.font.pointSize * 0.9
maximumLineCount: 2
text: model.topic text: model.topic
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Label { Label {
Layout.alignment: Qt.AlignHCenter
Layout.row: 0
Layout.column: 1
id: roomCount id: roomCount
Layout.alignment: Qt.AlignHCenter
Layout.column: 1
Layout.row: 0
color: roomDirDelegate.unimportantText color: roomDirDelegate.unimportantText
font.pointSize: fontMetrics.font.pointSize * 0.9 font.pointSize: fontMetrics.font.pointSize * 0.9
text: model.numMembers.toString() text: model.numMembers.toString()
} }
Button { Button {
Layout.row: 1
Layout.column: 1
id: joinRoomButton id: joinRoomButton
Layout.column: 1
Layout.row: 1
enabled: model.roomid !== "" enabled: model.roomid !== ""
text: model.canJoin ? qsTr("Join") : qsTr("Open") text: model.canJoin ? qsTr("Join") : qsTr("Open")
onClicked: { onClicked: {
if (model.canJoin) if (model.canJoin)
publicRooms.joinRoom(model.index); publicRooms.joinRoom(model.index);
else else {
{
Rooms.setCurrentRoom(model.roomid); Rooms.setCurrentRoom(model.roomid);
roomDirectoryWindow.close(); roomDirectoryWindow.close();
} }
} }
} }
} }
} }
} }
footer: Item { footer: Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
width: parent.width anchors.margins: Nheko.paddingLarge
visible: !publicRooms.reachedEndOfPagination && publicRooms.loadingMoreRooms
// hacky but works // hacky but works
height: loadingSpinner.height + 2 * Nheko.paddingLarge height: loadingSpinner.height + 2 * Nheko.paddingLarge
anchors.margins: Nheko.paddingLarge visible: !publicRooms.reachedEndOfPagination && publicRooms.loadingMoreRooms
width: parent.width
Spinner { Spinner {
id: loadingSpinner id: loadingSpinner
anchors.centerIn: parent anchors.centerIn: parent
anchors.margins: Nheko.paddingLarge anchors.margins: Nheko.paddingLarge
running: visible
foreground: palette.mid foreground: palette.mid
} running: visible
}
}
}
header: RowLayout {
id: searchBarLayout
spacing: Nheko.paddingMedium
width: parent.width
implicitHeight: roomSearch.height
MatrixTextField {
id: roomSearch
focus: true
Layout.fillWidth: true
selectByMouse: true
font.pixelSize: fontMetrics.font.pixelSize
color: palette.text
placeholderText: qsTr("Search for public rooms")
onTextChanged: searchTimer.restart()
Component.onCompleted: forceActiveFocus()
}
MatrixTextField {
id: chooseServer
Layout.minimumWidth: 0.3 * header.width
Layout.maximumWidth: 0.3 * header.width
color: palette.text
placeholderText: qsTr("Choose custom homeserver")
onTextChanged: publicRooms.setMatrixServer(text)
}
Timer {
id: searchTimer
interval: 350
onTriggered: roomDirView.model.setSearchTerm(roomSearch.text)
}
}
footer: RowLayout {
spacing: Nheko.paddingMedium
width: parent.width
Button {
text: qsTr("Close")
onClicked: roomDirectoryWindow.close()
Layout.alignment: Qt.AlignRight
Layout.margins: Nheko.paddingMedium
} }
} }
} }

View file

@ -17,18 +17,24 @@ ApplicationWindow {
property MemberList members property MemberList members
property Room room property Room room
title: qsTr("Members of %1").arg(members.roomName)
height: 650
width: 420
minimumHeight: 420
color: palette.window color: palette.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 650
minimumHeight: 420
title: qsTr("Members of %1").arg(members.roomName)
width: 420
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: roomMembersRoot.close()
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: roomMembersRoot.close() onActivated: roomMembersRoot.close()
} }
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
@ -37,148 +43,146 @@ ApplicationWindow {
Avatar { Avatar {
id: roomAvatar id: roomAvatar
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: 130 Layout.preferredHeight: 130
Layout.preferredWidth: 130 Layout.preferredWidth: 130
roomid: members.roomId
displayName: members.roomName displayName: members.roomName
Layout.alignment: Qt.AlignHCenter roomid: members.roomId
url: members.avatarUrl.replace("mxc://", "image://MxcImage/") url: members.avatarUrl.replace("mxc://", "image://MxcImage/")
onClicked: TimelineManager.openRoomSettings(members.roomId) onClicked: TimelineManager.openRoomSettings(members.roomId)
} }
ElidedLabel { ElidedLabel {
font.pixelSize: fontMetrics.font.pixelSize * 2
fullText: qsTr("%n people in %1", "Summary above list of members", members.memberCount).arg(members.roomName)
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
elideWidth: parent.width - Nheko.paddingMedium elideWidth: parent.width - Nheko.paddingMedium
font.pixelSize: fontMetrics.font.pixelSize * 2
fullText: qsTr("%n people in %1", "Summary above list of members", members.memberCount).arg(members.roomName)
} }
ImageButton { ImageButton {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
image: ":/icons/icons/ui/add-square-button.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Invite more people") ToolTip.text: qsTr("Invite more people")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/add-square-button.svg"
onClicked: TimelineManager.openInviteUsers(members.roomId) onClicked: TimelineManager.openInviteUsers(members.roomId)
} }
MatrixTextField { MatrixTextField {
id: searchBar id: searchBar
Layout.fillWidth: true Layout.fillWidth: true
placeholderText: qsTr("Search...") placeholderText: qsTr("Search...")
onTextChanged: members.setFilterString(text)
Component.onCompleted: forceActiveFocus() Component.onCompleted: forceActiveFocus()
onTextChanged: members.setFilterString(text)
} }
RowLayout { RowLayout {
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
Label { Label {
text: qsTr("Sort by: ")
color: palette.text color: palette.text
text: qsTr("Sort by: ")
} }
ComboBox { ComboBox {
model: ListModel { Layout.fillWidth: true
ListElement { data: MemberList.Mxid; text: qsTr("User ID") }
ListElement { data: MemberList.DisplayName; text: qsTr("Display name") }
ListElement { data: MemberList.Powerlevel; text: qsTr("Power level") }
}
textRole: "text" textRole: "text"
valueRole: "data" valueRole: "data"
onCurrentValueChanged: members.sortBy(currentValue)
Layout.fillWidth: true model: ListModel {
ListElement {
data: MemberList.Mxid
text: qsTr("User ID")
}
ListElement {
data: MemberList.DisplayName
text: qsTr("Display name")
}
ListElement {
data: MemberList.Powerlevel
text: qsTr("Power level")
} }
} }
onCurrentValueChanged: members.sortBy(currentValue)
}
}
ScrollView { ScrollView {
padding: Nheko.paddingMedium
ScrollBar.horizontal.visible: false
Layout.fillHeight: true Layout.fillHeight: true
Layout.minimumHeight: 200
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: 200
ScrollBar.horizontal.visible: false
padding: Nheko.paddingMedium
ListView { ListView {
id: memberList id: memberList
clip: true
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
clip: true
model: members model: members
delegate: ItemDelegate { delegate: ItemDelegate {
id: del id: del
onClicked: room.openUserProfile(model.mxid)
padding: Nheko.paddingMedium
width: ListView.view.width
height: memberLayout.implicitHeight + Nheko.paddingSmall * 2 height: memberLayout.implicitHeight + Nheko.paddingSmall * 2
hoverEnabled: true hoverEnabled: true
padding: Nheko.paddingMedium
width: ListView.view.width
background: Rectangle { background: Rectangle {
color: del.hovered ? palette.dark : roomMembersRoot.color color: del.hovered ? palette.dark : roomMembersRoot.color
} }
onClicked: room.openUserProfile(model.mxid)
RowLayout { RowLayout {
id: memberLayout id: memberLayout
spacing: Nheko.paddingMedium
anchors.centerIn: parent anchors.centerIn: parent
spacing: Nheko.paddingMedium
width: parent.width - Nheko.paddingSmall * 2 width: parent.width - Nheko.paddingSmall * 2
Avatar { Avatar {
id: avatar id: avatar
Layout.preferredWidth: Nheko.avatarSize
Layout.preferredHeight: Nheko.avatarSize Layout.preferredHeight: Nheko.avatarSize
userid: model.mxid Layout.preferredWidth: Nheko.avatarSize
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: model.displayName displayName: model.displayName
enabled: false enabled: false
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
userid: model.mxid
} }
ColumnLayout { ColumnLayout {
Layout.fillWidth: true
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
Layout.fillWidth: true
ElidedLabel { ElidedLabel {
fullText: model.displayName Layout.fillWidth: true
color: TimelineManager.userColor(model ? model.mxid : "", del.background.color) color: TimelineManager.userColor(model ? model.mxid : "", del.background.color)
elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width
font.pixelSize: fontMetrics.font.pixelSize font.pixelSize: fontMetrics.font.pixelSize
elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width fullText: model.displayName
Layout.fillWidth: true
} }
ElidedLabel { ElidedLabel {
fullText: model.mxid
color: del.hovered ? palette.brightText : palette.buttonText
font.pixelSize: Math.ceil(fontMetrics.font.pixelSize * 0.9)
elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width
Layout.fillWidth: true Layout.fillWidth: true
color: del.hovered ? palette.brightText : palette.buttonText
elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width
font.pixelSize: Math.ceil(fontMetrics.font.pixelSize * 0.9)
fullText: model.mxid
} }
} }
PowerlevelIndicator { PowerlevelIndicator {
powerlevel: model.powerlevel
permissions: room.permissions permissions: room.permissions
powerlevel: model.powerlevel
} }
EncryptionIndicator { EncryptionIndicator {
id: encryptInd id: encryptInd
Layout.preferredWidth: 16
Layout.preferredHeight: 16
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
visible: room.isEncrypted Layout.preferredHeight: 16
encrypted: room.isEncrypted Layout.preferredWidth: 16
trust: encrypted ? model.trustlevel : Crypto.Unverified
ToolTip.text: { ToolTip.text: {
if (!encrypted) if (!encrypted)
return qsTr("This room is not encrypted!"); return qsTr("This room is not encrypted!");
switch (trust) { switch (trust) {
case Crypto.Verified: case Crypto.Verified:
return qsTr("This user is verified."); return qsTr("This user is verified.");
@ -188,23 +192,22 @@ ApplicationWindow {
return qsTr("This user has unverified devices!"); return qsTr("This user has unverified devices!");
} }
} }
encrypted: room.isEncrypted
trust: encrypted ? model.trustlevel : Crypto.Unverified
visible: room.isEncrypted
} }
} }
NhekoCursorShape { NhekoCursorShape {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
} }
footer: Item { footer: Item {
width: parent.width anchors.margins: Nheko.paddingMedium
visible: (members.numUsersLoaded < members.memberCount) && members.loadingMoreMembers
// use the default height if it's visible, otherwise no height at all // use the default height if it's visible, otherwise no height at all
height: membersLoadingSpinner.implicitHeight height: membersLoadingSpinner.implicitHeight
anchors.margins: Nheko.paddingMedium visible: (members.numUsersLoaded < members.memberCount) && members.loadingMoreMembers
width: parent.width
Spinner { Spinner {
id: membersLoadingSpinner id: membersLoadingSpinner
@ -212,18 +215,8 @@ ApplicationWindow {
anchors.centerIn: parent anchors.centerIn: parent
implicitHeight: parent.visible ? 35 : 0 implicitHeight: parent.visible ? 35 : 0
} }
} }
} }
} }
} }
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: roomMembersRoot.close()
}
} }

View file

@ -16,80 +16,87 @@ ApplicationWindow {
property var roomSettings property var roomSettings
minimumWidth: 340
minimumHeight: 450
width: 450
height: 680
color: palette.window color: palette.window
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 680
minimumHeight: 450
minimumWidth: 340
modality: Qt.NonModal
title: qsTr("Room Settings") title: qsTr("Room Settings")
width: 450
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: close()
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: roomSettingsDialog.close() onActivated: roomSettingsDialog.close()
} }
Flickable { Flickable {
id: flickable id: flickable
boundsBehavior: Flickable.StopAtBounds
anchors.fill: parent anchors.fill: parent
boundsBehavior: Flickable.StopAtBounds
clip: true clip: true
flickableDirection: Flickable.VerticalFlick
contentWidth: roomSettingsDialog.width
contentHeight: contentLayout1.height contentHeight: contentLayout1.height
contentWidth: roomSettingsDialog.width
flickableDirection: Flickable.VerticalFlick
ColumnLayout { ColumnLayout {
id: contentLayout1 id: contentLayout1
width: parent.width
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
width: parent.width
Avatar { Avatar {
id: displayAvatar id: displayAvatar
Layout.topMargin: Nheko.paddingMedium Layout.alignment: Qt.AlignHCenter
url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
roomid: roomSettings.roomId
displayName: roomSettings.roomName
Layout.preferredHeight: 130 Layout.preferredHeight: 130
Layout.preferredWidth: 130 Layout.preferredWidth: 130
Layout.alignment: Qt.AlignHCenter Layout.topMargin: Nheko.paddingMedium
displayName: roomSettings.roomName
roomid: roomSettings.roomId
url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
onClicked: TimelineManager.openImageOverlay(null, roomSettings.roomAvatarUrl, "", 0, 0) onClicked: TimelineManager.openImageOverlay(null, roomSettings.roomAvatarUrl, "", 0, 0)
ImageButton { ImageButton {
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Change room avatar.") ToolTip.text: qsTr("Change room avatar.")
ToolTip.visible: hovered
anchors.left: displayAvatar.left anchors.left: displayAvatar.left
anchors.top: displayAvatar.top
anchors.leftMargin: Nheko.paddingMedium anchors.leftMargin: Nheko.paddingMedium
anchors.top: displayAvatar.top
anchors.topMargin: Nheko.paddingMedium anchors.topMargin: Nheko.paddingMedium
visible: roomSettings.canChangeAvatar hoverEnabled: true
image: ":/icons/icons/ui/edit.svg" image: ":/icons/icons/ui/edit.svg"
visible: roomSettings.canChangeAvatar
onClicked: { onClicked: {
roomSettings.updateAvatar(); roomSettings.updateAvatar();
} }
} }
} }
Spinner { Spinner {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
visible: roomSettings.isLoading
foreground: palette.mid foreground: palette.mid
running: roomSettings.isLoading running: roomSettings.isLoading
visible: roomSettings.isLoading
} }
Text { Text {
id: errorText id: errorText
color: "red"
visible: opacity > 0
opacity: 0
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
wrapMode: Text.Wrap // somehow still doesn't wrap
Layout.fillWidth: true Layout.fillWidth: true
color: "red"
opacity: 0
visible: opacity > 0
wrapMode: Text.Wrap // somehow still doesn't wrap
} }
SequentialAnimation { SequentialAnimation {
id: hideErrorAnimation id: hideErrorAnimation
@ -98,43 +105,38 @@ ApplicationWindow {
PauseAnimation { PauseAnimation {
duration: 4000 duration: 4000
} }
NumberAnimation { NumberAnimation {
target: errorText
property: 'opacity'
to: 0
duration: 1000 duration: 1000
property: 'opacity'
target: errorText
to: 0
} }
} }
Connections { Connections {
target: roomSettings
function onDisplayError(errorMessage) { function onDisplayError(errorMessage) {
errorText.text = errorMessage; errorText.text = errorMessage;
errorText.opacity = 1; errorText.opacity = 1;
hideErrorAnimation.restart(); hideErrorAnimation.restart();
} }
}
target: roomSettings
}
TextEdit { TextEdit {
id: roomName id: roomName
property bool isNameEditingAllowed: false property bool isNameEditingAllowed: false
readOnly: !isNameEditingAllowed
textFormat: isNameEditingAllowed ? TextEdit.PlainText : TextEdit.RichText
text: isNameEditingAllowed ? roomSettings.plainRoomName : roomSettings.roomName
font.pixelSize: fontMetrics.font.pixelSize * 2
color: palette.text
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: parent.width - (Nheko.paddingSmall * 2) - nameChangeButton.anchors.leftMargin - (nameChangeButton.width * 2) Layout.maximumWidth: parent.width - (Nheko.paddingSmall * 2) - nameChangeButton.anchors.leftMargin - (nameChangeButton.width * 2)
color: palette.text
font.pixelSize: fontMetrics.font.pixelSize * 2
horizontalAlignment: TextEdit.AlignHCenter horizontalAlignment: TextEdit.AlignHCenter
wrapMode: TextEdit.Wrap readOnly: !isNameEditingAllowed
selectByMouse: true selectByMouse: true
text: isNameEditingAllowed ? roomSettings.plainRoomName : roomSettings.roomName
textFormat: isNameEditingAllowed ? TextEdit.PlainText : TextEdit.RichText
wrapMode: TextEdit.Wrap
Keys.onShortcutOverride: event.key === Qt.Key_Enter
Keys.onPressed: { Keys.onPressed: {
if (event.matches(StandardKey.InsertLineSeparator) || event.matches(StandardKey.InsertParagraphSeparator)) { if (event.matches(StandardKey.InsertLineSeparator) || event.matches(StandardKey.InsertParagraphSeparator)) {
roomSettings.changeName(roomName.text); roomSettings.changeName(roomName.text);
@ -142,18 +144,21 @@ ApplicationWindow {
event.accepted = true; event.accepted = true;
} }
} }
Keys.onShortcutOverride: event.key === Qt.Key_Enter
ImageButton { ImageButton {
id: nameChangeButton id: nameChangeButton
visible: roomSettings.canChangeName
anchors.leftMargin: Nheko.paddingSmall ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Change name of this room")
ToolTip.visible: hovered
anchors.left: roomName.right anchors.left: roomName.right
anchors.leftMargin: Nheko.paddingSmall
anchors.verticalCenter: roomName.verticalCenter anchors.verticalCenter: roomName.verticalCenter
hoverEnabled: true hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Change name of this room")
ToolTip.delay: Nheko.tooltipDelay
image: roomName.isNameEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg" image: roomName.isNameEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
visible: roomSettings.canChangeName
onClicked: { onClicked: {
if (roomName.isNameEditingAllowed) { if (roomName.isNameEditingAllowed) {
roomSettings.changeName(roomName.text); roomSettings.changeName(roomName.text);
@ -165,69 +170,64 @@ ApplicationWindow {
} }
} }
} }
} }
RowLayout { RowLayout {
spacing: Nheko.paddingMedium
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
spacing: Nheko.paddingMedium
Label { Label {
text: qsTr("%n member(s)", "", roomSettings.memberCount)
color: palette.text color: palette.text
text: qsTr("%n member(s)", "", roomSettings.memberCount)
} }
ImageButton { ImageButton {
image: ":/icons/icons/ui/people.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("View members of %1").arg(roomSettings.roomName) ToolTip.text: qsTr("View members of %1").arg(roomSettings.roomName)
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/people.svg"
onClicked: TimelineManager.openRoomMembers(Rooms.getRoomById(roomSettings.roomId)) onClicked: TimelineManager.openRoomMembers(Rooms.getRoomById(roomSettings.roomId))
} }
} }
TextArea { TextArea {
id: roomTopic id: roomTopic
property bool cut: implicitHeight > 100 property bool cut: implicitHeight > 100
property bool isTopicEditingAllowed: false
property bool showMore: false property bool showMore: false
clip: true
Layout.maximumHeight: showMore? Number.POSITIVE_INFINITY : 100
Layout.preferredHeight: implicitHeight
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: Nheko.paddingLarge Layout.leftMargin: Nheko.paddingLarge
Layout.maximumHeight: showMore ? Number.POSITIVE_INFINITY : 100
Layout.preferredHeight: implicitHeight
Layout.rightMargin: Nheko.paddingLarge Layout.rightMargin: Nheko.paddingLarge
property bool isTopicEditingAllowed: false
readOnly: !isTopicEditingAllowed
textFormat: isTopicEditingAllowed ? TextEdit.PlainText : TextEdit.RichText
text: isTopicEditingAllowed
? roomSettings.plainRoomTopic
: (roomSettings.plainRoomTopic === "" ? ("<i>" + qsTr("No topic set") + "</i>") : roomSettings.roomTopic)
wrapMode: TextEdit.WordWrap
background: null background: null
clip: true
color: palette.text color: palette.text
horizontalAlignment: TextEdit.AlignHCenter horizontalAlignment: TextEdit.AlignHCenter
readOnly: !isTopicEditingAllowed
text: isTopicEditingAllowed ? roomSettings.plainRoomTopic : (roomSettings.plainRoomTopic === "" ? ("<i>" + qsTr("No topic set") + "</i>") : roomSettings.roomTopic)
textFormat: isTopicEditingAllowed ? TextEdit.PlainText : TextEdit.RichText
wrapMode: TextEdit.WordWrap
onLinkActivated: Nheko.openLink(link) onLinkActivated: Nheko.openLink(link)
NhekoCursorShape { NhekoCursorShape {
anchors.fill: parent anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
} }
} }
ImageButton { ImageButton {
id: topicChangeButton id: topicChangeButton
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
visible: roomSettings.canChangeTopic
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Change topic of this room")
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Change topic of this room")
ToolTip.visible: hovered
hoverEnabled: true
image: roomTopic.isTopicEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg" image: roomTopic.isTopicEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
visible: roomSettings.canChangeTopic
onClicked: { onClicked: {
if (roomTopic.isTopicEditingAllowed) { if (roomTopic.isTopicEditingAllowed) {
roomSettings.changeTopic(roomTopic.text); roomSettings.changeTopic(roomTopic.text);
@ -240,234 +240,219 @@ ApplicationWindow {
} }
} }
} }
Item { Item {
Layout.alignment: Qt.AlignHCenter
id: showMorePlaceholder id: showMorePlaceholder
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: showMoreButton.height Layout.preferredHeight: showMoreButton.height
Layout.preferredWidth: showMoreButton.width Layout.preferredWidth: showMoreButton.width
visible: roomTopic.cut visible: roomTopic.cut
} }
GridLayout { GridLayout {
Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium
columns: 2 columns: 2
rowSpacing: Nheko.paddingMedium rowSpacing: Nheko.paddingMedium
Layout.margins: Nheko.paddingMedium
Layout.fillWidth: true
Label { Label {
text: qsTr("NOTIFICATIONS")
font.bold: true
color: palette.text
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Nheko.paddingLarge Layout.topMargin: Nheko.paddingLarge
color: palette.text
font.bold: true
text: qsTr("NOTIFICATIONS")
} }
Label { Label {
text: qsTr("Notifications")
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text color: palette.text
text: qsTr("Notifications")
} }
ComboBox { ComboBox {
model: [qsTr("Muted"), qsTr("Mentions only"), qsTr("All messages")] Layout.fillWidth: true
currentIndex: roomSettings.notifications currentIndex: roomSettings.notifications
model: [qsTr("Muted"), qsTr("Mentions only"), qsTr("All messages")]
onActivated: { onActivated: {
roomSettings.changeNotifications(index); roomSettings.changeNotifications(index);
} }
Layout.fillWidth: true
WheelHandler{} // suppress scrolling changing values
}
WheelHandler {
} // suppress scrolling changing values
}
Label { Label {
text: qsTr("ENTRY PERMISSIONS")
font.bold: true
color: palette.text
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Nheko.paddingLarge Layout.topMargin: Nheko.paddingLarge
color: palette.text
font.bold: true
text: qsTr("ENTRY PERMISSIONS")
} }
Label { Label {
text: qsTr("Anyone can join")
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text color: palette.text
text: qsTr("Anyone can join")
} }
ToggleButton { ToggleButton {
id: publicRoomButton id: publicRoomButton
enabled: roomSettings.canChangeJoinRules
checked: !roomSettings.privateAccess
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: !roomSettings.privateAccess
enabled: roomSettings.canChangeJoinRules
} }
Label { Label {
text: qsTr("Allow knocking")
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text color: palette.text
text: qsTr("Allow knocking")
visible: knockingButton.visible visible: knockingButton.visible
} }
ToggleButton { ToggleButton {
id: knockingButton id: knockingButton
visible: !publicRoomButton.checked
enabled: roomSettings.canChangeJoinRules && roomSettings.supportsKnocking
checked: roomSettings.knockingEnabled
onCheckedChanged: {
if (checked && !roomSettings.supportsKnockRestricted) restrictedButton.checked = false;
}
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
} checked: roomSettings.knockingEnabled
enabled: roomSettings.canChangeJoinRules && roomSettings.supportsKnocking
visible: !publicRoomButton.checked
onCheckedChanged: {
if (checked && !roomSettings.supportsKnockRestricted)
restrictedButton.checked = false;
}
}
Label { Label {
text: qsTr("Allow joining via other rooms")
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text color: palette.text
text: qsTr("Allow joining via other rooms")
visible: restrictedButton.visible visible: restrictedButton.visible
} }
ToggleButton { ToggleButton {
id: restrictedButton id: restrictedButton
visible: !publicRoomButton.checked
enabled: roomSettings.canChangeJoinRules && roomSettings.supportsRestricted
checked: roomSettings.restrictedEnabled
onCheckedChanged: {
if (checked && !roomSettings.supportsKnockRestricted) knockingButton.checked = false;
}
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
} checked: roomSettings.restrictedEnabled
enabled: roomSettings.canChangeJoinRules && roomSettings.supportsRestricted
visible: !publicRoomButton.checked
onCheckedChanged: {
if (checked && !roomSettings.supportsKnockRestricted)
knockingButton.checked = false;
}
}
Label { Label {
text: qsTr("Rooms to join via")
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text color: palette.text
text: qsTr("Rooms to join via")
visible: allowedRoomsButton.visible visible: allowedRoomsButton.visible
} }
Button { Button {
id: allowedRoomsButton id: allowedRoomsButton
visible: restrictedButton.checked && restrictedButton.visible
enabled: roomSettings.canChangeJoinRules && roomSettings.supportsRestricted
text: qsTr("Change")
ToolTip.text: qsTr("Change the list of rooms users can join this room via. Usually this is the official community of this room.")
onClicked: timelineRoot.showAllowedRoomsEditor(roomSettings)
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
} ToolTip.text: qsTr("Change the list of rooms users can join this room via. Usually this is the official community of this room.")
enabled: roomSettings.canChangeJoinRules && roomSettings.supportsRestricted
text: qsTr("Change")
visible: restrictedButton.checked && restrictedButton.visible
onClicked: timelineRoot.showAllowedRoomsEditor(roomSettings)
}
Label { Label {
text: qsTr("Allow guests to join")
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text color: palette.text
text: qsTr("Allow guests to join")
} }
ToggleButton { ToggleButton {
id: guestAccessButton id: guestAccessButton
enabled: roomSettings.canChangeJoinRules
checked: roomSettings.guestAccess
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
} checked: roomSettings.guestAccess
Button {
visible: publicRoomButton.checked == roomSettings.privateAccess || knockingButton.checked != roomSettings.knockingEnabled || restrictedButton.checked != roomSettings.restrictedEnabled || guestAccessButton.checked != roomSettings.guestAccess || roomSettings.allowedRoomsModified
enabled: roomSettings.canChangeJoinRules enabled: roomSettings.canChangeJoinRules
}
text: qsTr("Apply access rules") Button {
onClicked: roomSettings.changeAccessRules(!publicRoomButton.checked, guestAccessButton.checked, knockingButton.checked, restrictedButton.checked)
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
} enabled: roomSettings.canChangeJoinRules
text: qsTr("Apply access rules")
visible: publicRoomButton.checked == roomSettings.privateAccess || knockingButton.checked != roomSettings.knockingEnabled || restrictedButton.checked != roomSettings.restrictedEnabled || guestAccessButton.checked != roomSettings.guestAccess || roomSettings.allowedRoomsModified
onClicked: roomSettings.changeAccessRules(!publicRoomButton.checked, guestAccessButton.checked, knockingButton.checked, restrictedButton.checked)
}
Label { Label {
text: qsTr("MESSAGE VISIBILITY")
font.bold: true
color: palette.text
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Nheko.paddingLarge Layout.topMargin: Nheko.paddingLarge
}
Label {
text: qsTr("Allow viewing history without joining")
Layout.fillWidth: true
color: palette.text color: palette.text
font.bold: true
text: qsTr("MESSAGE VISIBILITY")
}
Label {
Layout.fillWidth: true
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("This is useful to see previews of the room or view it on public websites.") ToolTip.text: qsTr("This is useful to see previews of the room or view it on public websites.")
ToolTip.visible: publicHistoryHover.hovered ToolTip.visible: publicHistoryHover.hovered
ToolTip.delay: Nheko.tooltipDelay color: palette.text
text: qsTr("Allow viewing history without joining")
HoverHandler { HoverHandler {
id: publicHistoryHover id: publicHistoryHover
} }
} }
ToggleButton { ToggleButton {
id: publicHistoryButton id: publicHistoryButton
enabled: roomSettings.canChangeHistoryVisibility
checked: roomSettings.historyVisibility == RoomSettings.WorldReadable
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: roomSettings.historyVisibility == RoomSettings.WorldReadable
enabled: roomSettings.canChangeHistoryVisibility
} }
Label { Label {
visible: !publicHistoryButton.checked
text: qsTr("Members can see messages since")
Layout.fillWidth: true
color: palette.text
Layout.alignment: Qt.AlignTop | Qt.AlignLeft Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Layout.fillWidth: true
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("How much of the history is visible to joined members. Changing this won't affect the visibility of already sent messages. It only applies to new messages.") ToolTip.text: qsTr("How much of the history is visible to joined members. Changing this won't affect the visibility of already sent messages. It only applies to new messages.")
ToolTip.visible: privateHistoryHover.hovered ToolTip.visible: privateHistoryHover.hovered
ToolTip.delay: Nheko.tooltipDelay color: palette.text
text: qsTr("Members can see messages since")
visible: !publicHistoryButton.checked
HoverHandler { HoverHandler {
id: privateHistoryHover id: privateHistoryHover
} }
} }
ColumnLayout { ColumnLayout {
Layout.fillWidth: true
visible: !publicHistoryButton.checked
enabled: roomSettings.canChangeHistoryVisibility
Layout.alignment: Qt.AlignTop | Qt.AlignRight Layout.alignment: Qt.AlignTop | Qt.AlignRight
Layout.fillWidth: true
enabled: roomSettings.canChangeHistoryVisibility
visible: !publicHistoryButton.checked
RadioButton { RadioButton {
id: sharedHistory id: sharedHistory
checked: roomSettings.historyVisibility == RoomSettings.Shared
text: qsTr("Everything") ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("As long as the user joined, they can see all previous messages.") ToolTip.text: qsTr("As long as the user joined, they can see all previous messages.")
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay checked: roomSettings.historyVisibility == RoomSettings.Shared
text: qsTr("Everything")
} }
RadioButton { RadioButton {
id: invitedHistory id: invitedHistory
checked: roomSettings.historyVisibility == RoomSettings.Invited
text: qsTr("They got invited") ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Members can only see messages from when they got invited going forward.") ToolTip.text: qsTr("Members can only see messages from when they got invited going forward.")
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay checked: roomSettings.historyVisibility == RoomSettings.Invited
text: qsTr("They got invited")
} }
RadioButton { RadioButton {
id: joinedHistory id: joinedHistory
checked: roomSettings.historyVisibility == RoomSettings.Joined || roomSettings.historyVisibility == RoomSettings.WorldReadable
text: qsTr("They joined") ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Members can only see messages since after they joined.") ToolTip.text: qsTr("Members can only see messages since after they joined.")
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay checked: roomSettings.historyVisibility == RoomSettings.Joined || roomSettings.historyVisibility == RoomSettings.WorldReadable
text: qsTr("They joined")
} }
} }
Button { Button {
visible: roomSettings.historyVisibility != selectedVisibility
enabled: roomSettings.canChangeHistoryVisibility
text: qsTr("Apply visibility changes")
property int selectedVisibility: { property int selectedVisibility: {
if (publicHistoryButton.checked) if (publicHistoryButton.checked)
return RoomSettings.WorldReadable; return RoomSettings.WorldReadable;
@ -477,65 +462,67 @@ ApplicationWindow {
return RoomSettings.Invited; return RoomSettings.Invited;
return RoomSettings.Joined; return RoomSettings.Joined;
} }
onClicked: roomSettings.changeHistoryVisibility(selectedVisibility)
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
} enabled: roomSettings.canChangeHistoryVisibility
text: qsTr("Apply visibility changes")
visible: roomSettings.historyVisibility != selectedVisibility
onClicked: roomSettings.changeHistoryVisibility(selectedVisibility)
}
Label { Label {
text: qsTr("Locally hidden events")
color: palette.text color: palette.text
text: qsTr("Locally hidden events")
} }
HiddenEventsDialog { HiddenEventsDialog {
id: hiddenEventsDialog id: hiddenEventsDialog
roomid: roomSettings.roomId
roomName: roomSettings.roomName roomName: roomSettings.roomName
roomid: roomSettings.roomId
} }
Button { Button {
text: qsTr("Configure")
ToolTip.text: qsTr("Select events to hide in this room")
onClicked: hiddenEventsDialog.show()
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
} ToolTip.text: qsTr("Select events to hide in this room")
text: qsTr("Configure")
onClicked: hiddenEventsDialog.show()
}
Label { Label {
text: qsTr("Automatic event deletion")
color: palette.text color: palette.text
text: qsTr("Automatic event deletion")
} }
EventExpirationDialog { EventExpirationDialog {
id: eventExpirationDialog id: eventExpirationDialog
roomid: roomSettings.roomId
roomName: roomSettings.roomName roomName: roomSettings.roomName
roomid: roomSettings.roomId
} }
Button { Button {
text: qsTr("Configure")
ToolTip.text: qsTr("Select if your events get automatically deleted in this room.")
onClicked: eventExpirationDialog.show()
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
} ToolTip.text: qsTr("Select if your events get automatically deleted in this room.")
text: qsTr("Configure")
onClicked: eventExpirationDialog.show()
}
Label { Label {
text: qsTr("GENERAL SETTINGS")
font.bold: true
color: palette.text
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Nheko.paddingLarge Layout.topMargin: Nheko.paddingLarge
}
Label {
text: qsTr("Encryption")
color: palette.text color: palette.text
font.bold: true
text: qsTr("GENERAL SETTINGS")
}
Label {
color: palette.text
text: qsTr("Encryption")
} }
ToggleButton { ToggleButton {
id: encryptionToggle id: encryptionToggle
Layout.alignment: Qt.AlignRight
checked: roomSettings.isEncryptionEnabled checked: roomSettings.isEncryptionEnabled
onCheckedChanged: { onCheckedChanged: {
if (roomSettings.isEncryptionEnabled) { if (roomSettings.isEncryptionEnabled) {
checked = true; checked = true;
@ -544,135 +531,131 @@ ApplicationWindow {
if (checked === true) if (checked === true)
confirmEncryptionDialog.open(); confirmEncryptionDialog.open();
} }
Layout.alignment: Qt.AlignRight
} }
Platform.MessageDialog { Platform.MessageDialog {
id: confirmEncryptionDialog id: confirmEncryptionDialog
title: qsTr("End-to-End Encryption") buttons: Platform.MessageDialog.Ok | Platform.MessageDialog.Cancel
modality: Qt.NonModal
text: qsTr(`Encryption is currently experimental and things might break unexpectedly. <br> text: qsTr(`Encryption is currently experimental and things might break unexpectedly. <br>
Please take note that it can't be disabled afterwards.`) Please take note that it can't be disabled afterwards.`)
modality: Qt.NonModal title: qsTr("End-to-End Encryption")
onAccepted: { onAccepted: {
if (roomSettings.isEncryptionEnabled) if (roomSettings.isEncryptionEnabled)
return; return;
roomSettings.enableEncryption(); roomSettings.enableEncryption();
} }
onRejected: { onRejected: {
encryptionToggle.checked = false; encryptionToggle.checked = false;
} }
buttons: Platform.MessageDialog.Ok | Platform.MessageDialog.Cancel
} }
Label { Label {
color: palette.text
text: qsTr("Permission") text: qsTr("Permission")
color: palette.text
} }
Button { Button {
text: qsTr("Configure") Layout.alignment: Qt.AlignRight
ToolTip.text: qsTr("View and change the permissions in this room") ToolTip.text: qsTr("View and change the permissions in this room")
onClicked: timelineRoot.showPLEditor(roomSettings)
Layout.alignment: Qt.AlignRight
}
Label {
text: qsTr("Aliases")
color: palette.text
}
Button {
text: qsTr("Configure") text: qsTr("Configure")
ToolTip.text: qsTr("View and change the addresses/aliases of this room")
onClicked: timelineRoot.showAliasEditor(roomSettings)
Layout.alignment: Qt.AlignRight
}
onClicked: timelineRoot.showPLEditor(roomSettings)
}
Label { Label {
text: qsTr("Sticker & Emote Settings")
color: palette.text color: palette.text
text: qsTr("Aliases")
} }
Button { Button {
text: qsTr("Change")
ToolTip.text: qsTr("Change what packs are enabled, remove packs, or create new ones")
onClicked: TimelineManager.openImagePackSettings(roomSettings.roomId)
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
} ToolTip.text: qsTr("View and change the addresses/aliases of this room")
text: qsTr("Configure")
onClicked: timelineRoot.showAliasEditor(roomSettings)
}
Label { Label {
text: qsTr("INFO")
font.bold: true
color: palette.text color: palette.text
text: qsTr("Sticker & Emote Settings")
}
Button {
Layout.alignment: Qt.AlignRight
ToolTip.text: qsTr("Change what packs are enabled, remove packs, or create new ones")
text: qsTr("Change")
onClicked: TimelineManager.openImagePackSettings(roomSettings.roomId)
}
Label {
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.topMargin: Nheko.paddingLarge
Layout.fillWidth: true Layout.fillWidth: true
} Layout.topMargin: Nheko.paddingLarge
Label {
text: qsTr("Internal ID")
color: palette.text color: palette.text
font.bold: true
text: qsTr("INFO")
} }
Label {
AbstractButton { // AbstractButton does not allow setting text color color: palette.text
text: qsTr("Internal ID")
}
AbstractButton {
// AbstractButton does not allow setting text color
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: idLabel.height Layout.preferredHeight: idLabel.height
Label { // TextEdit does not trigger onClicked
onClicked: {
textEdit.selectAll();
textEdit.copy();
toolTipTimer.start();
}
Label {
// TextEdit does not trigger onClicked
id: idLabel id: idLabel
text: roomSettings.roomId
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 0.8)
color: palette.text
width: parent.width
horizontalAlignment: Text.AlignRight
wrapMode: Text.WrapAnywhere
ToolTip.text: qsTr("Copied to clipboard") ToolTip.text: qsTr("Copied to clipboard")
ToolTip.visible: toolTipTimer.running ToolTip.visible: toolTipTimer.running
} color: palette.text
TextEdit{ // label does not allow selection font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 0.8)
id: textEdit horizontalAlignment: Text.AlignRight
visible: false
text: roomSettings.roomId text: roomSettings.roomId
width: parent.width
wrapMode: Text.WrapAnywhere
} }
onClicked: { TextEdit {
textEdit.selectAll() // label does not allow selection
textEdit.copy() id: textEdit
toolTipTimer.start()
text: roomSettings.roomId
visible: false
} }
Timer { Timer {
id: toolTipTimer id: toolTipTimer
}
}
}
}
Label { Label {
text: qsTr("Room Version")
color: palette.text color: palette.text
text: qsTr("Room Version")
} }
Label { Label {
text: roomSettings.roomVersion
font.pixelSize: fontMetrics.font.pixelSize
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
color: palette.text color: palette.text
font.pixelSize: fontMetrics.font.pixelSize
text: roomSettings.roomVersion
} }
} }
} }
} }
Button { Button {
id: showMoreButton id: showMoreButton
anchors.horizontalCenter: flickable.horizontalCenter anchors.horizontalCenter: flickable.horizontalCenter
y: Math.min(showMorePlaceholder.y+contentLayout1.y-flickable.contentY,flickable.height-height)
visible: roomTopic.cut
text: roomTopic.showMore ? qsTr("show less") : qsTr("show more") text: roomTopic.showMore ? qsTr("show less") : qsTr("show more")
onClicked: {roomTopic.showMore = !roomTopic.showMore visible: roomTopic.cut
console.log(flickable.visibleArea) y: Math.min(showMorePlaceholder.y + contentLayout1.y - flickable.contentY, flickable.height - height)
onClicked: {
roomTopic.showMore = !roomTopic.showMore;
console.log(flickable.visibleArea);
} }
} }
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: close()
}
} }

View file

@ -17,20 +17,20 @@ ApplicationWindow {
property var profile property var profile
height: 650
width: 420
minimumWidth: 150
minimumHeight: 150
color: palette.window color: palette.window
title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile")
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 650
minimumHeight: 150
minimumWidth: 150
modality: Qt.NonModal
title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile")
width: 420
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: userProfileDialog.close() onActivated: userProfileDialog.close()
} }
ListView { ListView {
id: devicelist id: devicelist
@ -38,61 +38,73 @@ ApplicationWindow {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
clip: true
spacing: 8
boundsBehavior: Flickable.StopAtBounds
anchors.fill: parent anchors.fill: parent
anchors.margins: 10 anchors.margins: 10
boundsBehavior: Flickable.StopAtBounds
clip: true
footerPositioning: ListView.OverlayFooter footerPositioning: ListView.OverlayFooter
model: (selectedTab == 0) ? devicesModel : sharedRoomsModel
spacing: 8
footer: DialogButtonBox {
alignment: Qt.AlignRight
standardButtons: DialogButtonBox.Ok
width: devicelist.width
z: 2
background: Rectangle {
anchors.fill: parent
color: palette.window
}
onAccepted: userProfileDialog.close()
}
header: ColumnLayout { header: ColumnLayout {
id: contentL id: contentL
width: devicelist.width
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
width: devicelist.width
Avatar { Avatar {
id: displayAvatar id: displayAvatar
url: profile.avatarUrl.replace("mxc://", "image://MxcImage/") Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: 130 Layout.preferredHeight: 130
Layout.preferredWidth: 130 Layout.preferredWidth: 130
displayName: profile.displayName displayName: profile.displayName
url: profile.avatarUrl.replace("mxc://", "image://MxcImage/")
userid: profile.userid userid: profile.userid
Layout.alignment: Qt.AlignHCenter
onClicked: TimelineManager.openImageOverlay(null, profile.avatarUrl, "", 0, 0) onClicked: TimelineManager.openImageOverlay(null, profile.avatarUrl, "", 0, 0)
ImageButton { ImageButton {
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: profile.isGlobalUserProfile ? qsTr("Change avatar globally.") : qsTr("Change avatar. Will only apply to this room.") ToolTip.text: profile.isGlobalUserProfile ? qsTr("Change avatar globally.") : qsTr("Change avatar. Will only apply to this room.")
ToolTip.visible: hovered
anchors.left: displayAvatar.left anchors.left: displayAvatar.left
anchors.top: displayAvatar.top
anchors.leftMargin: Nheko.paddingMedium anchors.leftMargin: Nheko.paddingMedium
anchors.top: displayAvatar.top
anchors.topMargin: Nheko.paddingMedium anchors.topMargin: Nheko.paddingMedium
visible: profile.isSelf hoverEnabled: true
image: ":/icons/icons/ui/edit.svg" image: ":/icons/icons/ui/edit.svg"
visible: profile.isSelf
onClicked: profile.changeAvatar() onClicked: profile.changeAvatar()
} }
} }
Spinner { Spinner {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
foreground: palette.mid
running: profile.isLoading running: profile.isLoading
visible: profile.isLoading visible: profile.isLoading
foreground: palette.mid
} }
Text { Text {
id: errorText id: errorText
color: "red"
visible: opacity > 0
opacity: 0
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
color: "red"
opacity: 0
visible: opacity > 0
} }
SequentialAnimation { SequentialAnimation {
id: hideErrorAnimation id: hideErrorAnimation
@ -101,16 +113,13 @@ ApplicationWindow {
PauseAnimation { PauseAnimation {
duration: 4000 duration: 4000
} }
NumberAnimation { NumberAnimation {
target: errorText
property: 'opacity'
to: 0
duration: 1000 duration: 1000
property: 'opacity'
target: errorText
to: 0
} }
} }
Connections { Connections {
function onDisplayError(errorMessage) { function onDisplayError(errorMessage) {
errorText.text = errorMessage; errorText.text = errorMessage;
@ -120,22 +129,22 @@ ApplicationWindow {
target: profile target: profile
} }
TextInput { TextInput {
id: displayUsername id: displayUsername
property bool isUsernameEditingAllowed property bool isUsernameEditingAllowed
readOnly: !isUsernameEditingAllowed
text: profile.displayName
font.pixelSize: 20
color: TimelineManager.userColor(profile.userid, palette.window)
font.bold: true
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: parent.width - (Nheko.paddingSmall * 2) - usernameChangeButton.anchors.leftMargin - (usernameChangeButton.width * 2) Layout.maximumWidth: parent.width - (Nheko.paddingSmall * 2) - usernameChangeButton.anchors.leftMargin - (usernameChangeButton.width * 2)
color: TimelineManager.userColor(profile.userid, palette.window)
font.bold: true
font.pixelSize: 20
horizontalAlignment: TextInput.AlignHCenter horizontalAlignment: TextInput.AlignHCenter
wrapMode: TextInput.Wrap readOnly: !isUsernameEditingAllowed
selectByMouse: true selectByMouse: true
text: profile.displayName
wrapMode: TextInput.Wrap
onAccepted: { onAccepted: {
profile.changeUsername(displayUsername.text); profile.changeUsername(displayUsername.text);
displayUsername.isUsernameEditingAllowed = false; displayUsername.isUsernameEditingAllowed = false;
@ -143,14 +152,16 @@ ApplicationWindow {
ImageButton { ImageButton {
id: usernameChangeButton id: usernameChangeButton
visible: profile.isSelf
anchors.leftMargin: Nheko.paddingSmall ToolTip.text: profile.isGlobalUserProfile ? qsTr("Change display name globally.") : qsTr("Change display name. Will only apply to this room.")
ToolTip.visible: hovered
anchors.left: displayUsername.right anchors.left: displayUsername.right
anchors.leftMargin: Nheko.paddingSmall
anchors.verticalCenter: displayUsername.verticalCenter anchors.verticalCenter: displayUsername.verticalCenter
hoverEnabled: true hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: profile.isGlobalUserProfile ? qsTr("Change display name globally.") : qsTr("Change display name. Will only apply to this room.")
image: displayUsername.isUsernameEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg" image: displayUsername.isUsernameEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
visible: profile.isSelf
onClicked: { onClicked: {
if (displayUsername.isUsernameEditingAllowed) { if (displayUsername.isUsernameEditingAllowed) {
profile.changeUsername(displayUsername.text); profile.changeUsername(displayUsername.text);
@ -162,82 +173,79 @@ ApplicationWindow {
} }
} }
} }
} }
MatrixText { MatrixText {
text: profile.userid
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: profile.userid
} }
MatrixText { MatrixText {
id: statusMsg id: statusMsg
text: qsTr("<i><b>Status:</b> %1</i>").arg(userStatus)
visible: userStatus != "" property string userStatus: Presence.userStatus(profile.userid)
Layout.fillWidth: true Layout.fillWidth: true
horizontalAlignment: TextEdit.AlignHCenter
Layout.leftMargin: Nheko.paddingMedium Layout.leftMargin: Nheko.paddingMedium
Layout.rightMargin: Nheko.paddingMedium Layout.rightMargin: Nheko.paddingMedium
font.pointSize: Math.floor(fontMetrics.font.pointSize * 0.9) font.pointSize: Math.floor(fontMetrics.font.pointSize * 0.9)
horizontalAlignment: TextEdit.AlignHCenter
text: qsTr("<i><b>Status:</b> %1</i>").arg(userStatus)
visible: userStatus != ""
property string userStatus: Presence.userStatus(profile.userid)
Connections { Connections {
target: Presence
function onPresenceChanged(id) { function onPresenceChanged(id) {
if (id == profile.userid) statusMsg.userStatus = Presence.userStatus(profile.userid); if (id == profile.userid)
} statusMsg.userStatus = Presence.userStatus(profile.userid);
}
} }
target: Presence
}
}
RowLayout { RowLayout {
visible: !profile.isGlobalUserProfile
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
visible: !profile.isGlobalUserProfile
MatrixText { MatrixText {
id: displayRoomname id: displayRoomname
text: qsTr("Room: %1").arg(profile.room ? profile.room.roomName : "") Layout.maximumWidth: parent.parent.width - (parent.spacing * 3) - 16
ToolTip.text: qsTr("This is a room-specific profile. The user's name and avatar may be different from their global versions.") ToolTip.text: qsTr("This is a room-specific profile. The user's name and avatar may be different from their global versions.")
ToolTip.visible: ma.hovered ToolTip.visible: ma.hovered
Layout.maximumWidth: parent.parent.width - (parent.spacing * 3) - 16
horizontalAlignment: TextEdit.AlignHCenter horizontalAlignment: TextEdit.AlignHCenter
text: qsTr("Room: %1").arg(profile.room ? profile.room.roomName : "")
HoverHandler { HoverHandler {
id: ma id: ma
}
} }
}
ImageButton { ImageButton {
image: ":/icons/icons/ui/world.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Open the global profile for this user.") ToolTip.text: qsTr("Open the global profile for this user.")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/world.svg"
onClicked: profile.openGlobalProfile() onClicked: profile.openGlobalProfile()
} }
} }
Button { Button {
id: verifyUserButton id: verifyUserButton
text: qsTr("Verify")
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
enabled: profile.userVerified != Crypto.Verified enabled: profile.userVerified != Crypto.Verified
text: qsTr("Verify")
visible: profile.userVerified != Crypto.Verified && !profile.isSelf && profile.userVerificationEnabled visible: profile.userVerified != Crypto.Verified && !profile.isSelf && profile.userVerificationEnabled
onClicked: profile.verify() onClicked: profile.verify()
} }
EncryptionIndicator { EncryptionIndicator {
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: 32 Layout.preferredHeight: 32
Layout.preferredWidth: 32 Layout.preferredWidth: 32
ToolTip.visible: false
encrypted: profile.userVerificationEnabled encrypted: profile.userVerificationEnabled
trust: profile.userVerified trust: profile.userVerified
Layout.alignment: Qt.AlignHCenter
ToolTip.visible: false
} }
RowLayout { RowLayout {
// ImageButton{ // ImageButton{
// image:":/icons/icons/ui/volume-off-indicator.svg" // image:":/icons/icons/ui/volume-off-indicator.svg"
@ -259,113 +267,108 @@ ApplicationWindow {
ImageButton { ImageButton {
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24 Layout.preferredWidth: 24
image: ":/icons/icons/ui/chat.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Start a private chat.") ToolTip.text: qsTr("Start a private chat.")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/chat.svg"
onClicked: profile.startChat() onClicked: profile.startChat()
} }
ImageButton { ImageButton {
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24 Layout.preferredWidth: 24
image: ":/icons/icons/ui/round-remove-button.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Kick the user.") ToolTip.text: qsTr("Kick the user.")
onClicked: profile.kickUser() ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/round-remove-button.svg"
visible: !profile.isGlobalUserProfile && profile.room.permissions.canKick() visible: !profile.isGlobalUserProfile && profile.room.permissions.canKick()
}
onClicked: profile.kickUser()
}
ImageButton { ImageButton {
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24 Layout.preferredWidth: 24
image: ":/icons/icons/ui/ban.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Ban the user.") ToolTip.text: qsTr("Ban the user.")
onClicked: profile.banUser() ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/ban.svg"
visible: !profile.isGlobalUserProfile && profile.room.permissions.canBan() visible: !profile.isGlobalUserProfile && profile.room.permissions.canBan()
}
onClicked: profile.banUser()
}
ImageButton { ImageButton {
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24 Layout.preferredWidth: 24
image: ":/icons/icons/ui/volume-off-indicator.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: profile.ignored ? qsTr("Unignore the user.") : qsTr("Ignore the user.") ToolTip.text: profile.ignored ? qsTr("Unignore the user.") : qsTr("Ignore the user.")
ToolTip.visible: hovered
buttonTextColor: profile.ignored ? Nheko.theme.red : palette.buttonText buttonTextColor: profile.ignored ? Nheko.theme.red : palette.buttonText
onClicked: profile.ignored = !profile.ignored hoverEnabled: true
image: ":/icons/icons/ui/volume-off-indicator.svg"
visible: !profile.isSelf visible: !profile.isSelf
}
onClicked: profile.ignored = !profile.ignored
}
ImageButton { ImageButton {
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24 Layout.preferredWidth: 24
image: ":/icons/icons/ui/refresh.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Refresh device list.") ToolTip.text: qsTr("Refresh device list.")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/refresh.svg"
onClicked: profile.refreshDevices() onClicked: profile.refreshDevices()
} }
} }
TabBar { TabBar {
id: tabbar id: tabbar
visible: !profile.isSelf
Layout.bottomMargin: Nheko.paddingMedium
Layout.fillWidth: true Layout.fillWidth: true
visible: !profile.isSelf
onCurrentIndexChanged: devicelist.selectedTab = currentIndex onCurrentIndexChanged: devicelist.selectedTab = currentIndex
NhekoTabButton { NhekoTabButton {
text: qsTr("Devices") text: qsTr("Devices")
} }
NhekoTabButton { NhekoTabButton {
text: qsTr("Shared Rooms") text: qsTr("Shared Rooms")
} }
Layout.bottomMargin: Nheko.paddingMedium
} }
} }
model: (selectedTab == 0) ? devicesModel : sharedRoomsModel
DelegateModel { DelegateModel {
id: devicesModel id: devicesModel
model: profile.deviceList model: profile.deviceList
delegate: RowLayout { delegate: RowLayout {
required property int verificationStatus
required property string deviceId required property string deviceId
required property string deviceName required property string deviceName
required property string lastIp required property string lastIp
required property var lastTs required property var lastTs
required property int verificationStatus
width: devicelist.width
spacing: 4 spacing: 4
width: devicelist.width
ColumnLayout { ColumnLayout {
spacing: 0
Layout.leftMargin: Nheko.paddingMedium Layout.leftMargin: Nheko.paddingMedium
Layout.rightMargin: Nheko.paddingMedium Layout.rightMargin: Nheko.paddingMedium
spacing: 0
RowLayout { RowLayout {
Text { Text {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
color: palette.text
elide: Text.ElideRight elide: Text.ElideRight
font.bold: true font.bold: true
color: palette.text
text: deviceId text: deviceId
} }
Image { Image {
Layout.preferredHeight: 16 Layout.preferredHeight: 16
Layout.preferredWidth: 16 Layout.preferredWidth: 16
visible: profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE
sourceSize.height: 16 * Screen.devicePixelRatio
sourceSize.width: 16 * Screen.devicePixelRatio
source: { source: {
switch (verificationStatus) { switch (verificationStatus) {
case VerificationStatus.VERIFIED: case VerificationStatus.VERIFIED:
@ -378,20 +381,21 @@ ApplicationWindow {
return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?" + Nheko.theme.orange; return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?" + Nheko.theme.orange;
} }
} }
sourceSize.height: 16 * Screen.devicePixelRatio
sourceSize.width: 16 * Screen.devicePixelRatio
visible: profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE
} }
ImageButton { ImageButton {
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
image: ":/icons/icons/ui/power-off.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Sign out this device.") ToolTip.text: qsTr("Sign out this device.")
onClicked: profile.signOutDevice(deviceId) ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/power-off.svg"
visible: profile.isSelf visible: profile.isSelf
}
onClicked: profile.signOutDevice(deviceId)
}
} }
RowLayout { RowLayout {
id: deviceNameRow id: deviceNameRow
@ -400,24 +404,25 @@ ApplicationWindow {
TextInput { TextInput {
id: deviceNameField id: deviceNameField
readOnly: !deviceNameRow.isEditingAllowed
text: deviceName
color: palette.text
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text
readOnly: !deviceNameRow.isEditingAllowed
selectByMouse: true selectByMouse: true
text: deviceName
onAccepted: { onAccepted: {
profile.changeDeviceName(deviceId, deviceNameField.text); profile.changeDeviceName(deviceId, deviceNameField.text);
deviceNameRow.isEditingAllowed = false; deviceNameRow.isEditingAllowed = false;
} }
} }
ImageButton { ImageButton {
visible: profile.isSelf
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Change device name.") ToolTip.text: qsTr("Change device name.")
ToolTip.visible: hovered
hoverEnabled: true
image: deviceNameRow.isEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg" image: deviceNameRow.isEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
visible: profile.isSelf
onClicked: { onClicked: {
if (deviceNameRow.isEditingAllowed) { if (deviceNameRow.isEditingAllowed) {
profile.changeDeviceName(deviceId, deviceNameField.text); profile.changeDeviceName(deviceId, deviceNameField.text);
@ -429,24 +434,19 @@ ApplicationWindow {
} }
} }
} }
} }
Text { Text {
visible: profile.isSelf
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
elide: Text.ElideRight Layout.fillWidth: true
color: palette.text color: palette.text
elide: Text.ElideRight
text: qsTr("Last seen %1 from %2").arg(new Date(lastTs).toLocaleString(Locale.ShortFormat)).arg(lastIp ? lastIp : "???") text: qsTr("Last seen %1 from %2").arg(new Date(lastTs).toLocaleString(Locale.ShortFormat)).arg(lastIp ? lastIp : "???")
visible: profile.isSelf
} }
} }
Image { Image {
Layout.preferredHeight: 16 Layout.preferredHeight: 16
Layout.preferredWidth: 16 Layout.preferredWidth: 16
visible: !profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE
source: { source: {
switch (verificationStatus) { switch (verificationStatus) {
case VerificationStatus.VERIFIED: case VerificationStatus.VERIFIED:
@ -459,13 +459,14 @@ ApplicationWindow {
return "image://colorimage/:/icons/icons/ui/shield-filled.svg?" + Nheko.theme.red; return "image://colorimage/:/icons/icons/ui/shield-filled.svg?" + Nheko.theme.red;
} }
} }
visible: !profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE
} }
Button { Button {
id: verifyButton id: verifyButton
visible: verificationStatus == VerificationStatus.UNVERIFIED && (profile.isSelf || !profile.userVerificationEnabled)
text: (verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify") text: (verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify")
visible: verificationStatus == VerificationStatus.UNVERIFIED && (profile.isSelf || !profile.userVerificationEnabled)
onClicked: { onClicked: {
if (verificationStatus == VerificationStatus.VERIFIED) if (verificationStatus == VerificationStatus.VERIFIED)
profile.unverify(deviceId); profile.unverify(deviceId);
@ -473,67 +474,48 @@ ApplicationWindow {
profile.verify(deviceId); profile.verify(deviceId);
} }
} }
} }
} }
DelegateModel { DelegateModel {
id: sharedRoomsModel id: sharedRoomsModel
model: profile.sharedRooms model: profile.sharedRooms
delegate: RowLayout { delegate: RowLayout {
required property string avatarUrl
required property string roomId required property string roomId
required property string roomName required property string roomName
required property string avatarUrl
width: devicelist.width
spacing: 4 spacing: 4
width: devicelist.width
Avatar { Avatar {
id: avatar id: avatar
enabled: false property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 1.6)
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Nheko.paddingMedium Layout.leftMargin: Nheko.paddingMedium
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 1.6)
Layout.preferredHeight: avatarSize Layout.preferredHeight: avatarSize
Layout.preferredWidth: avatarSize Layout.preferredWidth: avatarSize
url: avatarUrl.replace("mxc://", "image://MxcImage/")
roomid: roomId
displayName: roomName displayName: roomName
enabled: false
roomid: roomId
url: avatarUrl.replace("mxc://", "image://MxcImage/")
} }
ElidedLabel { ElidedLabel {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
color: palette.text
Layout.fillWidth: true Layout.fillWidth: true
Layout.rightMargin: Nheko.paddingMedium
color: palette.text
elideWidth: width elideWidth: width
fullText: roomName fullText: roomName
textFormat: Text.PlainText textFormat: Text.PlainText
Layout.rightMargin: Nheko.paddingMedium
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
} }
footer: DialogButtonBox {
z: 2
width: devicelist.width
alignment: Qt.AlignRight
standardButtons: DialogButtonBox.Ok
onAccepted: userProfileDialog.close()
background: Rectangle {
anchors.fill: parent
color: palette.window
} }
}
}
} }

View file

@ -12,17 +12,17 @@ Menu {
id: stickerPopup id: stickerPopup
property var callback property var callback
property string roomid
property alias model: gridView.model
required property bool emoji required property bool emoji
property var textArea
property real highlightHue: palette.highlight.hslHue property real highlightHue: palette.highlight.hslHue
property real highlightSat: palette.highlight.hslSaturation
property real highlightLight: palette.highlight.hslLightness property real highlightLight: palette.highlight.hslLightness
property real highlightSat: palette.highlight.hslSaturation
property alias model: gridView.model
property string roomid
readonly property int sidebarAvatarSize: 24
readonly property int stickerDim: emoji ? 48 : 128 readonly property int stickerDim: emoji ? 48 : 128
readonly property int stickerDimPad: stickerDim + Nheko.paddingSmall readonly property int stickerDimPad: stickerDim + Nheko.paddingSmall
readonly property int stickersPerRow: emoji ? 7 : 3 readonly property int stickersPerRow: emoji ? 7 : 3
readonly property int sidebarAvatarSize: 24 property var textArea
function show(showAt, roomid_, callback) { function show(showAt, roomid_, callback) {
console.debug("Showing sticker picker"); console.debug("Showing sticker picker");
@ -31,14 +31,14 @@ Menu {
popup(showAt ? showAt : null); popup(showAt ? showAt : null);
} }
margins: 2
bottomPadding: 0 bottomPadding: 0
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
focus: true
leftPadding: 0 leftPadding: 0
margins: 2
modal: true
rightPadding: 0 rightPadding: 0
topPadding: 0 topPadding: 0
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
width: sidebarAvatarSize + Nheko.paddingSmall + stickersPerRow * stickerDimPad + 20 width: sidebarAvatarSize + Nheko.paddingSmall + stickersPerRow * stickerDimPad + 20
Rectangle { Rectangle {
@ -49,11 +49,11 @@ Menu {
GridLayout { GridLayout {
id: columnView id: columnView
anchors.leftMargin: Nheko.paddingSmall
anchors.rightMargin: Nheko.paddingSmall
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Nheko.paddingSmall
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Nheko.paddingSmall
columns: 2 columns: 2
rows: 2 rows: 2
@ -61,14 +61,15 @@ Menu {
TextField { TextField {
id: emojiSearch id: emojiSearch
Layout.column: 1
Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - Nheko.paddingSmall Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - Nheko.paddingSmall
Layout.row: 0 Layout.row: 0
Layout.column: 1
background: null background: null
placeholderTextColor: palette.buttonText
placeholderText: qsTr("Search") placeholderText: qsTr("Search")
selectByMouse: true placeholderTextColor: palette.buttonText
rightPadding: clearSearch.width rightPadding: clearSearch.width
selectByMouse: true
onTextChanged: searchTimer.restart() onTextChanged: searchTimer.restart()
onVisibleChanged: { onVisibleChanged: {
if (visible) if (visible)
@ -81,23 +82,24 @@ Menu {
id: searchTimer id: searchTimer
interval: 350 // tweak as needed? interval: 350 // tweak as needed?
onTriggered: stickerPopup.model.searchString = emojiSearch.text onTriggered: stickerPopup.model.searchString = emojiSearch.text
} }
ImageButton { ImageButton {
id: clearSearch id: clearSearch
focusPolicy: Qt.NoFocus
hoverEnabled: true
image: ":/icons/icons/ui/round-remove-button.svg"
visible: emojiSearch.text !== '' visible: emojiSearch.text !== ''
image: ":/icons/icons/ui/round-remove-button.svg"
focusPolicy: Qt.NoFocus
onClicked: emojiSearch.clear() onClicked: emojiSearch.clear()
hoverEnabled: true
anchors { anchors {
top: parent.top
bottom: parent.bottom bottom: parent.bottom
right: parent.right right: parent.right
rightMargin: Nheko.paddingSmall rightMargin: Nheko.paddingSmall
top: parent.top
} }
} }
} }
@ -106,39 +108,30 @@ Menu {
ListView { ListView {
id: gridView id: gridView
model: roomid ? TimelineManager.completerFor(stickerPopup.emoji ? "emojigrid" : "stickergrid", roomid) : null property int cellHeight: stickerDimPad
Layout.row: 1
Layout.column: 1 Layout.column: 1
Layout.preferredHeight: cellHeight * (stickersPerRow + 0.5) Layout.preferredHeight: cellHeight * (stickersPerRow + 0.5)
Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - Nheko.paddingSmall Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - Nheko.paddingSmall
property int cellHeight: stickerDimPad Layout.row: 1
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
clip: true clip: true
currentIndex: -1 // prevent sorting from stealing focus currentIndex: -1 // prevent sorting from stealing focus
section.property: "packname" model: roomid ? TimelineManager.completerFor(stickerPopup.emoji ? "emojigrid" : "stickergrid", roomid) : null
section.criteria: ViewSection.FullString section.criteria: ViewSection.FullString
section.delegate: Rectangle {
width: gridView.width
height: childrenRect.height
color: palette.alternateBase
required property string section
Text {
anchors.left: parent.left
anchors.right: parent.right
text: parent.section
font.bold: true
}
}
section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart
section.property: "packname"
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
ScrollBar.vertical: ScrollBar {
id: emojiScroll
}
// Individual emoji // Individual emoji
delegate: Row { delegate: Row {
required property var row; required property var row
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
@ -150,11 +143,45 @@ Menu {
required property var modelData required property var modelData
width: stickerDim
height: stickerDim
hoverEnabled: true
ToolTip.text: ":" + modelData.shortcode + ": - " + (modelData.unicode ? modelData.unicodeName : modelData.body) ToolTip.text: ":" + modelData.shortcode + ": - " + (modelData.unicode ? modelData.unicodeName : modelData.body)
ToolTip.visible: hovered ToolTip.visible: hovered
height: stickerDim
hoverEnabled: true
width: stickerDim
background: Rectangle {
anchors.fill: parent
color: hovered ? palette.highlight : 'transparent'
radius: 5
}
contentItem: DelegateChooser {
roleValue: del.modelData.unicode != undefined
DelegateChoice {
roleValue: true
Text {
font.family: Settings.emojiFont
font.pixelSize: 36
height: stickerDim
horizontalAlignment: Text.AlignHCenter
text: del.modelData.unicode.replace('\ufe0f', '')
verticalAlignment: Text.AlignVCenter
width: stickerDim
}
}
DelegateChoice {
roleValue: false
Image {
fillMode: Image.PreserveAspectFit
height: stickerDim
source: del.modelData.url.replace("mxc://", "image://MxcImage/") + "?scale"
width: stickerDim
}
}
}
// TODO: maybe add favorites at some point? // TODO: maybe add favorites at some point?
onClicked: { onClicked: {
console.debug("Picked " + modelData); console.debug("Picked " + modelData);
@ -170,95 +197,63 @@ Menu {
callback(modelData.url, modelData.markdown); callback(modelData.url, modelData.markdown);
} }
} }
}
}
}
section.delegate: Rectangle {
required property string section
contentItem: DelegateChooser { color: palette.alternateBase
roleValue: del.modelData.unicode != undefined height: childrenRect.height
width: gridView.width
DelegateChoice {
roleValue: true
Text { Text {
width: stickerDim anchors.left: parent.left
height: stickerDim anchors.right: parent.right
horizontalAlignment: Text.AlignHCenter font.bold: true
verticalAlignment: Text.AlignVCenter text: parent.section
font.family: Settings.emojiFont
font.pixelSize: 36
text: del.modelData.unicode.replace('\ufe0f', '')
}
}
DelegateChoice {
roleValue: false
Image {
height: stickerDim
width: stickerDim
source: del.modelData.url.replace("mxc://", "image://MxcImage/") + "?scale"
fillMode: Image.PreserveAspectFit
} }
} }
} }
background: Rectangle {
anchors.fill: parent
color: hovered ? palette.highlight : 'transparent'
radius: 5
}
}
}
}
ScrollBar.vertical: ScrollBar {
id: emojiScroll
}
}
ListView { ListView {
Layout.row: 1
Layout.column: 0 Layout.column: 0
Layout.preferredWidth: sidebarAvatarSize
Layout.fillHeight: true Layout.fillHeight: true
Layout.preferredWidth: sidebarAvatarSize
Layout.rightMargin: Nheko.paddingSmall Layout.rightMargin: Nheko.paddingSmall
Layout.row: 1
clip: true
model: gridView.model ? gridView.model.sections : null model: gridView.model ? gridView.model.sections : null
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
clip: true
delegate: Avatar { delegate: Avatar {
height: sidebarAvatarSize
width: sidebarAvatarSize
url: modelData.url.replace("mxc://", "image://MxcImage/")
textColor: modelData.url.startsWith("mxc://") ? palette.text : palette.buttonText
displayName: modelData.name
roomid: modelData.name
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: modelData.name ToolTip.text: modelData.name
ToolTip.visible: hovered
displayName: modelData.name
height: sidebarAvatarSize
hoverEnabled: true
roomid: modelData.name
textColor: modelData.url.startsWith("mxc://") ? palette.text : palette.buttonText
url: modelData.url.replace("mxc://", "image://MxcImage/")
width: sidebarAvatarSize
onClicked: gridView.positionViewAtIndex(modelData.firstRowWith, ListView.Beginning) onClicked: gridView.positionViewAtIndex(modelData.firstRowWith, ListView.Beginning)
} }
} }
ImageButton { ImageButton {
Layout.row: 0
Layout.column: 0 Layout.column: 0
Layout.preferredWidth: sidebarAvatarSize
Layout.preferredHeight: sidebarAvatarSize Layout.preferredHeight: sidebarAvatarSize
Layout.preferredWidth: sidebarAvatarSize
Layout.rightMargin: Nheko.paddingSmall Layout.rightMargin: Nheko.paddingSmall
Layout.row: 0
image: ":/icons/icons/ui/settings.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Change what packs are enabled, remove packs, or create new ones") ToolTip.text: qsTr("Change what packs are enabled, remove packs, or create new ones")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/settings.svg"
onClicked: TimelineManager.openImagePackSettings(stickerPopup.roomid) onClicked: TimelineManager.openImagePackSettings(stickerPopup.roomid)
} }
} }
} }
} }

View file

@ -13,147 +13,139 @@ import "../"
Item { Item {
id: loginPage id: loginPage
property int maxExpansion: 400
property string error: login.error property string error: login.error
property int maxExpansion: 400
Login { Login {
id: login id: login
}
}
ScrollView { ScrollView {
id: scroll id: scroll
clip: false
ScrollBar.horizontal.visible: false ScrollBar.horizontal.visible: false
anchors.left: parent.left anchors.left: parent.left
anchors.margins: Nheko.paddingLarge
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
height: Math.min(loginPage.height, col.implicitHeight) clip: false
anchors.margins: Nheko.paddingLarge
contentWidth: availableWidth contentWidth: availableWidth
height: Math.min(loginPage.height, col.implicitHeight)
ColumnLayout { ColumnLayout {
id: col id: col
spacing: Nheko.paddingMedium
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
spacing: Nheko.paddingMedium
width: Math.min(loginPage.maxExpansion, scroll.width - Nheko.paddingLarge * 2) width: Math.min(loginPage.maxExpansion, scroll.width - Nheko.paddingLarge * 2)
Image { Image {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
source: "qrc:/logos/login.png"
Layout.preferredHeight: 128 Layout.preferredHeight: 128
Layout.preferredWidth: 128 Layout.preferredWidth: 128
source: "qrc:/logos/login.png"
} }
RowLayout { RowLayout {
Layout.fillWidth: true
spacing: Nheko.paddingLarge spacing: Nheko.paddingLarge
Layout.fillWidth: true
MatrixTextField { MatrixTextField {
id: matrixIdLabel id: matrixIdLabel
Keys.forwardTo: [pwBtn, ssoRepeater]
ToolTip.text: qsTr("Your login name. A mxid should start with @ followed by the user ID. After the user ID you need to include your server name after a :.\nYou can also put your homeserver address there if your server doesn't support .well-known lookup.\nExample: @user:yourserver.example.com\nIf Nheko fails to discover your homeserver, it will show you a field to enter the server manually.")
label: qsTr("Matrix ID") label: qsTr("Matrix ID")
placeholderText: qsTr("e.g @user:yourserver.example.com") placeholderText: qsTr("e.g @user:yourserver.example.com")
onEditingFinished: login.mxid = text onEditingFinished: login.mxid = text
ToolTip.text: qsTr("Your login name. A mxid should start with @ followed by the user ID. After the user ID you need to include your server name after a :.\nYou can also put your homeserver address there if your server doesn't support .well-known lookup.\nExample: @user:yourserver.example.com\nIf Nheko fails to discover your homeserver, it will show you a field to enter the server manually.")
Keys.forwardTo: [pwBtn, ssoRepeater]
} }
Spinner { Spinner {
Layout.preferredHeight: matrixIdLabel.height/2
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
Layout.preferredHeight: matrixIdLabel.height / 2
visible: running
running: login.lookingUpHs
foreground: palette.mid foreground: palette.mid
running: login.lookingUpHs
visible: running
} }
} }
MatrixText { MatrixText {
Layout.fillWidth: true Layout.fillWidth: true
textFormat: Text.PlainText
color: Nheko.theme.error color: Nheko.theme.error
text: login.mxidError text: login.mxidError
textFormat: Text.PlainText
visible: text visible: text
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
} }
MatrixTextField { MatrixTextField {
id: passwordLabel id: passwordLabel
Layout.fillWidth: true
label: qsTr("Password")
echoMode: TextInput.Password
ToolTip.text: qsTr("Your password.")
visible: login.passwordSupported
Keys.forwardTo: [pwBtn, ssoRepeater]
}
Keys.forwardTo: [pwBtn, ssoRepeater]
Layout.fillWidth: true
ToolTip.text: qsTr("Your password.")
echoMode: TextInput.Password
label: qsTr("Password")
visible: login.passwordSupported
}
MatrixTextField { MatrixTextField {
id: deviceNameLabel id: deviceNameLabel
Keys.forwardTo: [pwBtn, ssoRepeater]
Layout.fillWidth: true Layout.fillWidth: true
ToolTip.text: qsTr("A name for this device which will be shown to others when verifying your devices. If nothing is provided, a default is used.")
label: qsTr("Device name") label: qsTr("Device name")
placeholderText: login.initialDeviceName() placeholderText: login.initialDeviceName()
ToolTip.text: qsTr("A name for this device which will be shown to others when verifying your devices. If nothing is provided, a default is used.")
Keys.forwardTo: [pwBtn, ssoRepeater]
} }
MatrixTextField { MatrixTextField {
id: hsLabel id: hsLabel
enabled: visible
visible: login.homeserverNeeded
Keys.forwardTo: [pwBtn, ssoRepeater]
Layout.fillWidth: true Layout.fillWidth: true
ToolTip.text: qsTr("The address that can be used to contact your homeserver's client API.\nExample: https://yourserver.example.com:8787")
enabled: visible
label: qsTr("Homeserver address") label: qsTr("Homeserver address")
placeholderText: qsTr("yourserver.example.com:8787") placeholderText: qsTr("yourserver.example.com:8787")
text: login.homeserver text: login.homeserver
onEditingFinished: login.homeserver = text visible: login.homeserverNeeded
ToolTip.text: qsTr("The address that can be used to contact your homeserver's client API.\nExample: https://yourserver.example.com:8787")
Keys.forwardTo: [pwBtn, ssoRepeater]
}
onEditingFinished: login.homeserver = text
}
Item { Item {
Layout.preferredHeight: Nheko.avatarSize
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Nheko.avatarSize
Spinner { Spinner {
height: parent.height
anchors.centerIn: parent anchors.centerIn: parent
visible: running
running: login.loggingIn
foreground: palette.mid foreground: palette.mid
height: parent.height
running: login.loggingIn
visible: running
} }
} }
MatrixText { MatrixText {
Layout.fillWidth: true Layout.fillWidth: true
textFormat: Text.PlainText
color: Nheko.theme.error color: Nheko.theme.error
text: loginPage.error text: loginPage.error
textFormat: Text.PlainText
visible: text visible: text
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
} }
FlatButton { FlatButton {
id: pwBtn id: pwBtn
visible: login.passwordSupported
enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text
Layout.alignment: Qt.AlignHCenter
text: qsTr("LOGIN")
function pwLogin() { function pwLogin() {
login.onLoginButtonClicked(Login.Password, matrixIdLabel.text, passwordLabel.text, deviceNameLabel.text) login.onLoginButtonClicked(Login.Password, matrixIdLabel.text, passwordLabel.text, deviceNameLabel.text);
}
onClicked: pwBtn.pwLogin()
Keys.onEnterPressed: pwBtn.pwLogin()
Keys.onReturnPressed: pwBtn.pwLogin()
Keys.enabled: pwBtn.enabled && login.passwordSupported
} }
Keys.enabled: pwBtn.enabled && login.passwordSupported
Layout.alignment: Qt.AlignHCenter
enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text
text: qsTr("LOGIN")
visible: login.passwordSupported
Keys.onEnterPressed: pwBtn.pwLogin()
Keys.onReturnPressed: pwBtn.pwLogin()
onClicked: pwBtn.pwLogin()
}
Repeater { Repeater {
id: ssoRepeater id: ssoRepeater
@ -161,32 +153,35 @@ Item {
delegate: FlatButton { delegate: FlatButton {
id: ssoBtn id: ssoBtn
visible: login.ssoSupported
enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text
Layout.alignment: Qt.AlignHCenter
text: modelData.name
iconImage: modelData.avatarUrl.replace("mxc://", "image://MxcImage/")
function ssoLogin() { function ssoLogin() {
login.onLoginButtonClicked(Login.SSO, matrixIdLabel.text, modelData.id, deviceNameLabel.text) login.onLoginButtonClicked(Login.SSO, matrixIdLabel.text, modelData.id, deviceNameLabel.text);
}
onClicked: ssoBtn.ssoLogin()
Keys.onEnterPressed: ssoBtn.ssoLogin()
Keys.onReturnPressed: ssoBtn.ssoLogin()
Keys.enabled: ssoBtn.enabled && !login.passwordSupported
}
}
}
} }
Keys.enabled: ssoBtn.enabled && !login.passwordSupported
Layout.alignment: Qt.AlignHCenter
enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text
iconImage: modelData.avatarUrl.replace("mxc://", "image://MxcImage/")
text: modelData.name
visible: login.ssoSupported
Keys.onEnterPressed: ssoBtn.ssoLogin()
Keys.onReturnPressed: ssoBtn.ssoLogin()
onClicked: ssoBtn.ssoLogin()
}
}
}
}
ImageButton { ImageButton {
anchors.top: parent.top ToolTip.text: qsTr("Back")
ToolTip.visible: hovered
anchors.left: parent.left anchors.left: parent.left
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
width: Nheko.avatarSize anchors.top: parent.top
height: Nheko.avatarSize height: Nheko.avatarSize
image: ":/icons/icons/ui/angle-arrow-left.svg" image: ":/icons/icons/ui/angle-arrow-left.svg"
ToolTip.visible: hovered width: Nheko.avatarSize
ToolTip.text: qsTr("Back")
onClicked: mainWindow.pop() onClicked: mainWindow.pop()
} }
} }

View file

@ -13,208 +13,197 @@ import "../"
Item { Item {
id: registrationPage id: registrationPage
property int maxExpansion: 400
property string error: regis.error property string error: regis.error
property int maxExpansion: 400
Registration { Registration {
id: regis id: regis
}
}
ScrollView { ScrollView {
id: scroll id: scroll
clip: false
ScrollBar.horizontal.visible: false ScrollBar.horizontal.visible: false
anchors.left: parent.left anchors.left: parent.left
anchors.margins: Nheko.paddingLarge
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
height: Math.min(registrationPage.height, col.implicitHeight) clip: false
anchors.margins: Nheko.paddingLarge
contentWidth: availableWidth contentWidth: availableWidth
height: Math.min(registrationPage.height, col.implicitHeight)
ColumnLayout { ColumnLayout {
id: col id: col
spacing: Nheko.paddingMedium
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
spacing: Nheko.paddingMedium
width: Math.min(registrationPage.maxExpansion, scroll.width - Nheko.paddingLarge * 2) width: Math.min(registrationPage.maxExpansion, scroll.width - Nheko.paddingLarge * 2)
Image { Image {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
source: "qrc:/logos/login.png"
Layout.preferredHeight: 128 Layout.preferredHeight: 128
Layout.preferredWidth: 128 Layout.preferredWidth: 128
source: "qrc:/logos/login.png"
} }
RowLayout { RowLayout {
Layout.fillWidth: true
spacing: Nheko.paddingLarge spacing: Nheko.paddingLarge
Layout.fillWidth: true
MatrixTextField { MatrixTextField {
id: hsLabel id: hsLabel
label: qsTr("Homeserver")
placeholderText: qsTr("your.server")
onEditingFinished: regis.setServer(text)
ToolTip.text: qsTr("A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own.") ToolTip.text: qsTr("A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own.")
label: qsTr("Homeserver")
placeholderText: qsTr("your.server")
onEditingFinished: regis.setServer(text)
} }
Spinner { Spinner {
Layout.preferredHeight: hsLabel.height/2
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
Layout.preferredHeight: hsLabel.height / 2
visible: running
running: regis.lookingUpHs
foreground: palette.mid foreground: palette.mid
running: regis.lookingUpHs
visible: running
} }
} }
MatrixText { MatrixText {
Layout.fillWidth: true Layout.fillWidth: true
textFormat: Text.PlainText
color: Nheko.theme.error color: Nheko.theme.error
text: regis.hsError text: regis.hsError
textFormat: Text.PlainText
visible: text visible: text
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
} }
RowLayout { RowLayout {
Layout.fillWidth: true
spacing: Nheko.paddingLarge spacing: Nheko.paddingLarge
visible: regis.supported visible: regis.supported
Layout.fillWidth: true
MatrixTextField { MatrixTextField {
id: usernameLabel id: usernameLabel
Layout.fillWidth: true Layout.fillWidth: true
label: qsTr("Username")
ToolTip.text: qsTr("The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /.") ToolTip.text: qsTr("The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /.")
label: qsTr("Username")
onEditingFinished: regis.checkUsername(text) onEditingFinished: regis.checkUsername(text)
} }
Spinner { Spinner {
Layout.preferredHeight: usernameLabel.height/2
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
Layout.preferredHeight: usernameLabel.height / 2
visible: running
running: regis.lookingUpUsername
foreground: palette.mid foreground: palette.mid
running: regis.lookingUpUsername
visible: running
} }
Image { Image {
Layout.alignment: Qt.AlignBottom
Layout.preferredHeight: usernameLabel.height / 2 Layout.preferredHeight: usernameLabel.height / 2
Layout.preferredWidth: usernameLabel.height / 2 Layout.preferredWidth: usernameLabel.height / 2
Layout.alignment: Qt.AlignBottom
source: regis.usernameAvailable ? ("image://colorimage/:/icons/icons/ui/checkmark.svg?green") : ("image://colorimage/:/icons/icons/ui/dismiss.svg?"+Nheko.theme.error)
visible: regis.usernameAvailable || regis.usernameUnavailable
ToolTip.visible: ma.hovered
ToolTip.text: qsTr("Back") ToolTip.text: qsTr("Back")
ToolTip.visible: ma.hovered
source: regis.usernameAvailable ? ("image://colorimage/:/icons/icons/ui/checkmark.svg?green") : ("image://colorimage/:/icons/icons/ui/dismiss.svg?" + Nheko.theme.error)
sourceSize.height: height * Screen.devicePixelRatio sourceSize.height: height * Screen.devicePixelRatio
sourceSize.width: width * Screen.devicePixelRatio sourceSize.width: width * Screen.devicePixelRatio
visible: regis.usernameAvailable || regis.usernameUnavailable
HoverHandler { HoverHandler {
id: ma id: ma
}
}
}
}
}
}
MatrixText { MatrixText {
Layout.fillWidth: true Layout.fillWidth: true
textFormat: Text.PlainText
color: Nheko.theme.error color: Nheko.theme.error
text: regis.usernameError text: regis.usernameError
textFormat: Text.PlainText
visible: text && regis.supported visible: text && regis.supported
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
} }
MatrixTextField { MatrixTextField {
visible: regis.supported
id: passwordLabel id: passwordLabel
Layout.fillWidth: true Layout.fillWidth: true
label: qsTr("Password")
echoMode: TextInput.Password
ToolTip.text: qsTr("Please choose a secure password. The exact requirements for password strength may depend on your server.") ToolTip.text: qsTr("Please choose a secure password. The exact requirements for password strength may depend on your server.")
}
MatrixTextField {
visible: regis.supported
id: passwordConfirmationLabel
Layout.fillWidth: true
label: qsTr("Password confirmation")
echoMode: TextInput.Password echoMode: TextInput.Password
label: qsTr("Password")
visible: regis.supported
} }
MatrixTextField {
id: passwordConfirmationLabel
Layout.fillWidth: true
echoMode: TextInput.Password
label: qsTr("Password confirmation")
visible: regis.supported
}
MatrixText { MatrixText {
Layout.fillWidth: true Layout.fillWidth: true
visible: regis.supported
textFormat: Text.PlainText
color: Nheko.theme.error color: Nheko.theme.error
text: passwordLabel.text != passwordConfirmationLabel.text ? qsTr("Your passwords do not match!") : "" text: passwordLabel.text != passwordConfirmationLabel.text ? qsTr("Your passwords do not match!") : ""
textFormat: Text.PlainText
visible: regis.supported
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
} }
MatrixTextField { MatrixTextField {
visible: regis.supported
id: deviceNameLabel id: deviceNameLabel
Layout.fillWidth: true Layout.fillWidth: true
ToolTip.text: qsTr("A name for this device which will be shown to others when verifying your devices. If nothing is provided a default is used.")
label: qsTr("Device name") label: qsTr("Device name")
placeholderText: regis.initialDeviceName() placeholderText: regis.initialDeviceName()
ToolTip.text: qsTr("A name for this device which will be shown to others when verifying your devices. If nothing is provided a default is used.") visible: regis.supported
} }
Item { Item {
Layout.preferredHeight: Nheko.avatarSize
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Nheko.avatarSize
Spinner { Spinner {
height: parent.height
anchors.centerIn: parent anchors.centerIn: parent
visible: running
running: regis.registering
foreground: palette.mid foreground: palette.mid
height: parent.height
running: regis.registering
visible: running
} }
} }
MatrixText { MatrixText {
Layout.fillWidth: true Layout.fillWidth: true
textFormat: Text.PlainText
color: Nheko.theme.error color: Nheko.theme.error
text: registrationPage.error text: registrationPage.error
textFormat: Text.PlainText
visible: text visible: text
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
} }
FlatButton { FlatButton {
id: regisBtn id: regisBtn
visible: regis.supported
enabled: usernameLabel.text && passwordLabel.text && passwordLabel.text == passwordConfirmationLabel.text
Layout.alignment: Qt.AlignHCenter
text: qsTr("REGISTER")
function register() { function register() {
regis.startRegistration(usernameLabel.text, passwordLabel.text, deviceNameLabel.text) regis.startRegistration(usernameLabel.text, passwordLabel.text, deviceNameLabel.text);
}
onClicked: regisBtn.register()
Keys.onEnterPressed: regisBtn.register()
Keys.onReturnPressed: regisBtn.register()
Keys.enabled: regisBtn.enabled && regis.supported
}
}
} }
Keys.enabled: regisBtn.enabled && regis.supported
Layout.alignment: Qt.AlignHCenter
enabled: usernameLabel.text && passwordLabel.text && passwordLabel.text == passwordConfirmationLabel.text
text: qsTr("REGISTER")
visible: regis.supported
Keys.onEnterPressed: regisBtn.register()
Keys.onReturnPressed: regisBtn.register()
onClicked: regisBtn.register()
}
}
}
ImageButton { ImageButton {
anchors.top: parent.top ToolTip.text: qsTr("Back")
ToolTip.visible: hovered
anchors.left: parent.left anchors.left: parent.left
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
width: Nheko.avatarSize anchors.top: parent.top
height: Nheko.avatarSize height: Nheko.avatarSize
image: ":/icons/icons/ui/angle-arrow-left.svg" image: ":/icons/icons/ui/angle-arrow-left.svg"
ToolTip.visible: hovered width: Nheko.avatarSize
ToolTip.text: qsTr("Back")
onClicked: mainWindow.pop() onClicked: mainWindow.pop()
} }
} }

View file

@ -16,6 +16,7 @@ Rectangle {
property int collapsePoint: 600 property int collapsePoint: 600
property bool collapsed: width < collapsePoint property bool collapsed: width < collapsePoint
color: palette.window color: palette.window
ScrollView { ScrollView {
@ -24,86 +25,90 @@ Rectangle {
ScrollBar.horizontal.visible: false ScrollBar.horizontal.visible: false
anchors.fill: parent anchors.fill: parent
anchors.topMargin: (collapsed ? backButton.height : 0) + Nheko.paddingLarge anchors.topMargin: (collapsed ? backButton.height : 0) + Nheko.paddingLarge
leftPadding: collapsed? Nheko.paddingMedium : Nheko.paddingLarge
bottomPadding: Nheko.paddingLarge bottomPadding: Nheko.paddingLarge
contentWidth: availableWidth contentWidth: availableWidth
leftPadding: collapsed ? Nheko.paddingMedium : Nheko.paddingLarge
ColumnLayout { ColumnLayout {
id: grid id: grid
spacing: Nheko.paddingMedium
width: scroll.availableWidth
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: userSettingsDialog.collapsed ? 0 : (userSettingsDialog.width - userSettingsDialog.collapsePoint) * 0.4 + Nheko.paddingLarge anchors.leftMargin: userSettingsDialog.collapsed ? 0 : (userSettingsDialog.width - userSettingsDialog.collapsePoint) * 0.4 + Nheko.paddingLarge
anchors.rightMargin: anchors.leftMargin anchors.rightMargin: anchors.leftMargin
spacing: Nheko.paddingMedium
width: scroll.availableWidth
Repeater { Repeater {
model: UserSettingsModel model: UserSettingsModel
delegate: GridLayout { delegate: GridLayout {
width: scroll.availableWidth id: r
required property var model
columns: collapsed ? 1 : 2 columns: collapsed ? 1 : 2
rows: collapsed ? 2 : 1 rows: collapsed ? 2 : 1
required property var model width: scroll.availableWidth
id: r
Label { Label {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
color: palette.text
text: model.name
//Layout.column: 0 //Layout.column: 0
Layout.columnSpan: (model.type == UserSettingsModel.SectionTitle && !userSettingsDialog.collapsed) ? 2 : 1 Layout.columnSpan: (model.type == UserSettingsModel.SectionTitle && !userSettingsDialog.collapsed) ? 2 : 1
Layout.fillWidth: true
//Layout.row: model.index //Layout.row: model.index
//Layout.minimumWidth: implicitWidth //Layout.minimumWidth: implicitWidth
Layout.leftMargin: model.type == UserSettingsModel.SectionTitle ? 0 : Nheko.paddingMedium Layout.leftMargin: model.type == UserSettingsModel.SectionTitle ? 0 : Nheko.paddingMedium
Layout.topMargin: model.type == UserSettingsModel.SectionTitle ? Nheko.paddingLarge : 0 Layout.topMargin: model.type == UserSettingsModel.SectionTitle ? Nheko.paddingLarge : 0
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: model.description ?? ""
ToolTip.visible: hovered.hovered && model.description
color: palette.text
font.pointSize: 1.1 * fontMetrics.font.pointSize font.pointSize: 1.1 * fontMetrics.font.pointSize
text: model.name
wrapMode: Text.Wrap
HoverHandler { HoverHandler {
id: hovered id: hovered
enabled: model.description ?? false enabled: model.description ?? false
} }
ToolTip.visible: hovered.hovered && model.description
ToolTip.text: model.description ?? ""
ToolTip.delay: Nheko.tooltipDelay
wrapMode: Text.Wrap
} }
DelegateChooser { DelegateChooser {
id: chooser id: chooser
roleValue: model.type
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.columnSpan: (model.type == UserSettingsModel.SectionTitle && !userSettingsDialog.collapsed) ? 2 : 1 Layout.columnSpan: (model.type == UserSettingsModel.SectionTitle && !userSettingsDialog.collapsed) ? 2 : 1
Layout.fillWidth: model.type == UserSettingsModel.SectionTitle || model.type == UserSettingsModel.Options || model.type == UserSettingsModel.Number
Layout.maximumWidth: model.type == UserSettingsModel.SectionTitle ? Number.POSITIVE_INFINITY : 400
Layout.preferredHeight: child.height Layout.preferredHeight: child.height
Layout.preferredWidth: child.implicitWidth Layout.preferredWidth: child.implicitWidth
Layout.maximumWidth: model.type == UserSettingsModel.SectionTitle ? Number.POSITIVE_INFINITY : 400
Layout.fillWidth: model.type == UserSettingsModel.SectionTitle || model.type == UserSettingsModel.Options || model.type == UserSettingsModel.Number
Layout.rightMargin: model.type == UserSettingsModel.SectionTitle ? 0 : Nheko.paddingMedium Layout.rightMargin: model.type == UserSettingsModel.SectionTitle ? 0 : Nheko.paddingMedium
roleValue: model.type
DelegateChoice { DelegateChoice {
roleValue: UserSettingsModel.Toggle roleValue: UserSettingsModel.Toggle
ToggleButton { ToggleButton {
checked: model.value checked: model.value
onCheckedChanged: model.value = checked
enabled: model.enabled enabled: model.enabled
onCheckedChanged: model.value = checked
} }
} }
DelegateChoice { DelegateChoice {
roleValue: UserSettingsModel.Options roleValue: UserSettingsModel.Options
ComboBox { ComboBox {
anchors.right: parent.right anchors.right: parent.right
model: r.model.values
currentIndex: r.model.value currentIndex: r.model.value
width: Math.min(implicitWidth, scroll.availableWidth - Nheko.paddingMedium)
onCurrentIndexChanged: r.model.value = currentIndex
implicitContentWidthPolicy: ComboBox.WidestTextWhenCompleted implicitContentWidthPolicy: ComboBox.WidestTextWhenCompleted
model: r.model.values
width: Math.min(implicitWidth, scroll.availableWidth - Nheko.paddingMedium)
WheelHandler{} // suppress scrolling changing values onCurrentIndexChanged: r.model.value = currentIndex
WheelHandler {
} // suppress scrolling changing values
} }
} }
DelegateChoice { DelegateChoice {
@ -111,14 +116,16 @@ Rectangle {
SpinBox { SpinBox {
anchors.right: parent.right anchors.right: parent.right
from: model.valueLowerBound
to: model.valueUpperBound
stepSize: model.valueStep
value: model.value
onValueChanged: model.value = value
editable: true editable: true
from: model.valueLowerBound
stepSize: model.valueStep
to: model.valueUpperBound
value: model.value
WheelHandler{} // suppress scrolling changing values onValueChanged: model.value = value
WheelHandler {
} // suppress scrolling changing values
} }
} }
DelegateChoice { DelegateChoice {
@ -127,54 +134,56 @@ Rectangle {
SpinBox { SpinBox {
id: spinbox id: spinbox
readonly property double div: 100
readonly property int decimals: 2 readonly property int decimals: 2
readonly property double div: 100
property real realValue: value / div
anchors.right: parent.right anchors.right: parent.right
from: model.valueLowerBound * div
to: model.valueUpperBound * div
stepSize: model.valueStep * div
value: model.value * div
onValueChanged: model.value = value/div
editable: true editable: true
from: model.valueLowerBound * div
property real realValue: value / div stepSize: model.valueStep * div
textFromValue: function (value, locale) {
return Number(value / spinbox.div).toLocaleString(locale, 'f', spinbox.decimals);
}
to: model.valueUpperBound * div
value: model.value * div
valueFromText: function (text, locale) {
return Number.fromLocaleString(locale, text) * spinbox.div;
}
validator: DoubleValidator { validator: DoubleValidator {
bottom: Math.min(spinbox.from / spinbox.div, spinbox.to / spinbox.div) bottom: Math.min(spinbox.from / spinbox.div, spinbox.to / spinbox.div)
top: Math.max(spinbox.from / spinbox.div, spinbox.to / spinbox.div) top: Math.max(spinbox.from / spinbox.div, spinbox.to / spinbox.div)
} }
textFromValue: function(value, locale) { onValueChanged: model.value = value / div
return Number(value / spinbox.div).toLocaleString(locale, 'f', spinbox.decimals)
}
valueFromText: function(text, locale) { WheelHandler {
return Number.fromLocaleString(locale, text) * spinbox.div } // suppress scrolling changing values
}
WheelHandler{} // suppress scrolling changing values
} }
} }
DelegateChoice { DelegateChoice {
roleValue: UserSettingsModel.ReadOnlyText roleValue: UserSettingsModel.ReadOnlyText
TextEdit { TextEdit {
color: palette.text color: palette.text
text: model.value
readOnly: true readOnly: true
text: model.value
textFormat: Text.PlainText textFormat: Text.PlainText
} }
} }
DelegateChoice { DelegateChoice {
roleValue: UserSettingsModel.SectionTitle roleValue: UserSettingsModel.SectionTitle
Item { Item {
width: grid.width
height: fontMetrics.lineSpacing height: fontMetrics.lineSpacing
width: grid.width
Rectangle { Rectangle {
anchors.topMargin: Nheko.paddingSmall
anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: Nheko.paddingSmall
color: palette.buttonText color: palette.buttonText
height: 1 height: 1
} }
@ -182,6 +191,7 @@ Rectangle {
} }
DelegateChoice { DelegateChoice {
roleValue: UserSettingsModel.KeyStatus roleValue: UserSettingsModel.KeyStatus
Text { Text {
color: model.good ? "green" : Nheko.theme.error color: model.good ? "green" : Nheko.theme.error
text: model.value ? qsTr("CACHED") : qsTr("NOT CACHED") text: model.value ? qsTr("CACHED") : qsTr("NOT CACHED")
@ -189,34 +199,42 @@ Rectangle {
} }
DelegateChoice { DelegateChoice {
roleValue: UserSettingsModel.SessionKeyImportExport roleValue: UserSettingsModel.SessionKeyImportExport
RowLayout { RowLayout {
Button { Button {
text: qsTr("IMPORT") text: qsTr("IMPORT")
onClicked: UserSettingsModel.importSessionKeys() onClicked: UserSettingsModel.importSessionKeys()
} }
Button { Button {
text: qsTr("EXPORT") text: qsTr("EXPORT")
onClicked: UserSettingsModel.exportSessionKeys() onClicked: UserSettingsModel.exportSessionKeys()
} }
} }
} }
DelegateChoice { DelegateChoice {
roleValue: UserSettingsModel.XSignKeysRequestDownload roleValue: UserSettingsModel.XSignKeysRequestDownload
RowLayout { RowLayout {
Button { Button {
text: qsTr("DOWNLOAD") text: qsTr("DOWNLOAD")
onClicked: UserSettingsModel.downloadCrossSigningSecrets() onClicked: UserSettingsModel.downloadCrossSigningSecrets()
} }
Button { Button {
text: qsTr("REQUEST") text: qsTr("REQUEST")
onClicked: UserSettingsModel.requestCrossSigningSecrets() onClicked: UserSettingsModel.requestCrossSigningSecrets()
} }
} }
} }
DelegateChoice { DelegateChoice {
roleValue: UserSettingsModel.ConfigureHiddenEvents roleValue: UserSettingsModel.ConfigureHiddenEvents
Button { Button {
text: qsTr("CONFIGURE") text: qsTr("CONFIGURE")
onClicked: { onClicked: {
var dialog = hiddenEventsDialog.createObject(); var dialog = hiddenEventsDialog.createObject();
dialog.show(); dialog.show();
@ -226,15 +244,17 @@ Rectangle {
Component { Component {
id: hiddenEventsDialog id: hiddenEventsDialog
HiddenEventsDialog {} HiddenEventsDialog {
}
} }
} }
} }
DelegateChoice { DelegateChoice {
roleValue: UserSettingsModel.ManageIgnoredUsers roleValue: UserSettingsModel.ManageIgnoredUsers
Button { Button {
text: qsTr("MANAGE") text: qsTr("MANAGE")
onClicked: { onClicked: {
var dialog = ignoredUsersDialog.createObject(); var dialog = ignoredUsersDialog.createObject();
dialog.show(); dialog.show();
@ -244,11 +264,11 @@ Rectangle {
Component { Component {
id: ignoredUsersDialog id: ignoredUsersDialog
IgnoredUsers {} IgnoredUsers {
}
} }
} }
} }
DelegateChoice { DelegateChoice {
Text { Text {
text: model.value text: model.value
@ -259,19 +279,18 @@ Rectangle {
} }
} }
} }
ImageButton { ImageButton {
id: backButton id: backButton
anchors.top: parent.top
ToolTip.text: qsTr("Back")
ToolTip.visible: hovered
anchors.left: parent.left anchors.left: parent.left
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
width: Nheko.avatarSize anchors.top: parent.top
height: Nheko.avatarSize height: Nheko.avatarSize
image: ":/icons/icons/ui/angle-arrow-left.svg" image: ":/icons/icons/ui/angle-arrow-left.svg"
ToolTip.visible: hovered width: Nheko.avatarSize
ToolTip.text: qsTr("Back")
onClicked: mainWindow.pop() onClicked: mainWindow.pop()
} }
} }

View file

@ -14,86 +14,83 @@ ColumnLayout {
Item { Item {
Layout.fillHeight: true Layout.fillHeight: true
} }
Image { Image {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
source: "qrc:/logos/splash.png"
Layout.preferredHeight: 256 Layout.preferredHeight: 256
Layout.preferredWidth: 256 Layout.preferredWidth: 256
source: "qrc:/logos/splash.png"
} }
Label { Label {
Layout.margins: Nheko.paddingLarge
Layout.bottomMargin: 0
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: 0
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Welcome to nheko! The desktop client for the Matrix protocol.") Layout.margins: Nheko.paddingLarge
color: palette.text color: palette.text
font.pointSize: fontMetrics.font.pointSize * 2 font.pointSize: fontMetrics.font.pointSize * 2
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: qsTr("Welcome to nheko! The desktop client for the Matrix protocol.")
wrapMode: Text.Wrap
} }
Label { Label {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Enjoy your stay!") Layout.margins: Nheko.paddingLarge
color: palette.text color: palette.text
font.pointSize: fontMetrics.font.pointSize * 1.5 font.pointSize: fontMetrics.font.pointSize * 1.5
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: qsTr("Enjoy your stay!")
wrapMode: Text.Wrap
} }
RowLayout { RowLayout {
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
FlatButton { FlatButton {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.margins: Nheko.paddingLarge
text: qsTr("REGISTER") text: qsTr("REGISTER")
onClicked: { onClicked: {
mainWindow.push(registerPage); mainWindow.push(registerPage);
} }
} }
FlatButton { FlatButton {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.margins: Nheko.paddingLarge
text: qsTr("LOGIN") text: qsTr("LOGIN")
onClicked: { onClicked: {
mainWindow.push(loginPage); mainWindow.push(loginPage);
} }
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
RowLayout { RowLayout {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.margins: Nheko.paddingLarge Layout.margins: Nheko.paddingLarge
ToggleButton { ToggleButton {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.margins: Nheko.paddingLarge
checked: Settings.reducedMotion checked: Settings.reducedMotion
onCheckedChanged: Settings.reducedMotion = checked onCheckedChanged: Settings.reducedMotion = checked
} }
Label { Label {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.margins: Nheko.paddingLarge Layout.margins: Nheko.paddingLarge
text: qsTr("Reduce animations") ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Nheko uses animations in several places to make stuff pretty. This allows you to turn those off if they make you feel unwell.")
ToolTip.visible: hovered.hovered
color: palette.text color: palette.text
text: qsTr("Reduce animations")
HoverHandler { HoverHandler {
id: hovered id: hovered
} }
ToolTip.visible: hovered.hovered
ToolTip.text: qsTr("Nheko uses animations in several places to make stuff pretty. This allows you to turn those off if they make you feel unwell.")
ToolTip.delay: Nheko.tooltipDelay
} }
} }
Item { Item {

View file

@ -9,42 +9,39 @@ import im.nheko 1.0
Slider { Slider {
id: control id: control
property color progressColor: palette.highlight
property bool alwaysShowSlider: true property bool alwaysShowSlider: true
property color progressColor: palette.highlight
property int sliderRadius: 16 property int sliderRadius: 16
value: 0
implicitHeight: sliderRadius implicitHeight: sliderRadius
padding: 0 padding: 0
value: 0
background: Rectangle { background: Rectangle {
color: palette.buttonText
height: implicitHeight
implicitHeight: control.sliderRadius / 4
implicitWidth: 200
radius: height / 2
width: control.availableWidth - handle.width
x: control.leftPadding + handle.width / 2 x: control.leftPadding + handle.width / 2
y: control.topPadding + control.availableHeight / 2 - height / 2 y: control.topPadding + control.availableHeight / 2 - height / 2
implicitWidth: 200
implicitHeight: control.sliderRadius / 4
width: control.availableWidth - handle.width
height: implicitHeight
radius: height / 2
color: palette.buttonText
Rectangle { Rectangle {
width: control.visualPosition * parent.width
height: parent.height
color: control.progressColor color: control.progressColor
height: parent.height
radius: 2 radius: 2
width: control.visualPosition * parent.width
} }
} }
handle: Rectangle { handle: Rectangle {
border.color: control.progressColor
color: control.progressColor
implicitHeight: control.sliderRadius
implicitWidth: control.sliderRadius
radius: control.sliderRadius / 2
visible: Settings.mobileMode || control.alwaysShowSlider || control.hovered || control.pressed
x: control.leftPadding + control.visualPosition * background.width x: control.leftPadding + control.visualPosition * background.width
y: control.topPadding + control.availableHeight / 2 - height / 2 y: control.topPadding + control.availableHeight / 2 - height / 2
implicitWidth: control.sliderRadius
implicitHeight: control.sliderRadius
radius: control.sliderRadius / 2
color: control.progressColor
visible: Settings.mobileMode || control.alwaysShowSlider || control.hovered || control.pressed
border.color: control.progressColor
} }
} }

View file

@ -9,9 +9,9 @@ Item {
property color color: "#22000000" property color color: "#22000000"
property real maxRadius: Math.max(width, height) property real maxRadius: Math.max(width, height)
readonly property real opacityAnimationDuration: 300
readonly property real radiusAnimationRate: 0.05 readonly property real radiusAnimationRate: 0.05
readonly property real radiusTailAnimationRate: 0.5 readonly property real radiusTailAnimationRate: 0.5
readonly property real opacityAnimationDuration: 300
property var rippleTarget: parent property var rippleTarget: parent
anchors.fill: parent anchors.fill: parent
@ -19,18 +19,13 @@ Item {
PointHandler { PointHandler {
id: ph id: ph
onGrabChanged: (_, point) => {
circle.centerX = point.position.x
circle.centerY = point.position.y
}
target: Rectangle { target: Rectangle {
id: backgroundLayer id: backgroundLayer
parent: rippleTarget
anchors.fill: parent anchors.fill: parent
color: "transparent"
clip: true clip: true
color: "transparent"
parent: rippleTarget
Rectangle { Rectangle {
id: circle id: circle
@ -38,15 +33,14 @@ Item {
property real centerX property real centerX
property real centerY property real centerY
color: ripple.color
height: radius * 2
radius: 0
state: ph.active ? "ACTIVE" : "NORMAL"
width: radius * 2
x: centerX - radius x: centerX - radius
y: centerY - radius y: centerY - radius
height: radius*2
width: radius*2
radius: 0
color: ripple.color
state: ph.active ? "ACTIVE" : "NORMAL"
states: [ states: [
State { State {
name: "NORMAL" name: "NORMAL"
@ -63,26 +57,30 @@ Item {
SequentialAnimation { SequentialAnimation {
//PropertyAction { target: circle; property: "centerX"; value: ph.point.position.x } //PropertyAction { target: circle; property: "centerX"; value: ph.point.position.x }
//PropertyAction { target: circle; property: "centerY"; value: ph.point.position.y } //PropertyAction { target: circle; property: "centerY"; value: ph.point.position.y }
PropertyAction { target: circle; property: "visible"; value: true } PropertyAction {
PropertyAction { target: circle; property: "opacity"; value: 1 } property: "visible"
target: circle
value: true
}
PropertyAction {
property: "opacity"
target: circle
value: 1
}
NumberAnimation { NumberAnimation {
id: radius_animation id: radius_animation
target: circle
properties: "radius"
from: 0
to: ripple.maxRadius
duration: ripple.maxRadius / ripple.radiusAnimationRate duration: ripple.maxRadius / ripple.radiusAnimationRate
from: 0
properties: "radius"
target: circle
to: ripple.maxRadius
easing { easing {
type: Easing.OutQuad type: Easing.OutQuad
} }
} }
} }
}, },
Transition { Transition {
from: "ACTIVE" from: "ACTIVE"
@ -93,37 +91,42 @@ Item {
NumberAnimation { NumberAnimation {
id: radius_tail_animation id: radius_tail_animation
target: circle
properties: "radius"
to: ripple.maxRadius
duration: ripple.maxRadius / ripple.radiusTailAnimationRate duration: ripple.maxRadius / ripple.radiusTailAnimationRate
properties: "radius"
target: circle
to: ripple.maxRadius
easing { easing {
type: Easing.Linear type: Easing.Linear
} }
} }
NumberAnimation { NumberAnimation {
id: opacity_animation id: opacity_animation
target: circle
properties: "opacity"
to: 0
duration: ripple.opacityAnimationDuration duration: ripple.opacityAnimationDuration
properties: "opacity"
target: circle
to: 0
easing { easing {
type: Easing.InQuad type: Easing.InQuad
} }
} }
} }
PropertyAction { target: circle; property: "visible"; value: false } PropertyAction {
property: "visible"
target: circle
value: false
}
} }
} }
] ]
} }
} }
onGrabChanged: (_, point) => {
circle.centerX = point.position.x;
circle.centerY = point.position.y;
}
} }
} }

View file

@ -9,11 +9,8 @@ import im.nheko 1.0
Popup { Popup {
id: snackbar id: snackbar
// Workaround palettes not inheriting for popups
palette: timelineRoot.palette
property var messages: []
property string currentMessage: "" property string currentMessage: ""
property var messages: []
function showNotification(msg) { function showNotification(msg) {
messages.push(msg); messages.push(msg);
@ -24,10 +21,61 @@ Popup {
} }
} }
Timer { opacity: 0
id: dismissTimer padding: Nheko.paddingLarge
interval: 10000
onTriggered: snackbar.close() // Workaround palettes not inheriting for popups
palette: timelineRoot.palette
parent: Overlay.overlay
x: (parent.width - width) / 2
y: -100
background: Rectangle {
color: palette.dark
opacity: 0.8
radius: Nheko.paddingLarge
}
contentItem: Label {
color: palette.light
font.bold: true
text: snackbar.currentMessage
width: Math.max(snackbar.Overlay.overlay ? snackbar.Overlay.overlay.width / 2 : 0, 400)
}
enter: Transition {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
from: 0.0
property: "opacity"
target: snackbar
to: 1.0
}
NumberAnimation {
duration: 1000
easing.type: Easing.OutCubic
from: -100
properties: "y"
target: snackbar
to: 100
}
}
exit: Transition {
NumberAnimation {
duration: 300
easing.type: Easing.InCubic
from: 1.0
property: "opacity"
target: snackbar
to: 0.0
}
NumberAnimation {
duration: 300
easing.type: Easing.InCubic
from: 100
properties: "y"
target: snackbar
to: -100
}
} }
onAboutToHide: { onAboutToHide: {
@ -41,61 +89,11 @@ Popup {
} }
} }
parent: Overlay.overlay Timer {
opacity: 0 id: dismissTimer
y: -100
x: (parent.width - width)/2
padding: Nheko.paddingLarge
contentItem: Label { interval: 10000
color: palette.light
width: Math.max(snackbar.Overlay.overlay? snackbar.Overlay.overlay.width/2 : 0, 400)
text: snackbar.currentMessage
font.bold: true
}
background: Rectangle { onTriggered: snackbar.close()
radius: Nheko.paddingLarge
color: palette.dark
opacity: 0.8
}
enter: Transition {
NumberAnimation {
target: snackbar
property: "opacity"
from: 0.0
to: 1.0
duration: 200
easing.type: Easing.OutCubic
}
NumberAnimation {
target: snackbar
properties: "y"
from: -100
to: 100
duration: 1000
easing.type: Easing.OutCubic
} }
} }
exit: Transition {
NumberAnimation {
target: snackbar
property: "opacity"
from: 1.0
to: 0.0
duration: 300
easing.type: Easing.InCubic
}
NumberAnimation {
target: snackbar
properties: "y"
to: -100
from: 100
duration: 300
easing.type: Easing.InCubic
}
}
}

View file

@ -9,15 +9,15 @@ import QtQuick.Effects
Item { Item {
id: spinner id: spinner
property int spacing: 0
property bool running: true
property var foreground: "#333"
readonly property int barCount: 6
readonly property real a: Math.PI / 6 readonly property real a: Math.PI / 6
readonly property var colors: ["#c0def5", "#87aade", "white"]
readonly property var anims: [anim1, anim2, anim3, anim4, anim5, anim6] readonly property var anims: [anim1, anim2, anim3, anim4, anim5, anim6]
readonly property int pauseDuration: barCount * 150 readonly property int barCount: 6
readonly property var colors: ["#c0def5", "#87aade", "white"]
property var foreground: "#333"
readonly property int glowDuration: 300 readonly property int glowDuration: 300
readonly property int pauseDuration: barCount * 150
property bool running: true
property int spacing: 0
height: 40 height: 40
width: barCount * (height * 0.375) width: barCount * (height * 0.375)
@ -25,131 +25,116 @@ Item {
Row { Row {
id: row id: row
Rectangle {
id: rect1
width: ((spinner.width / spinner.barCount) - (spinner.spacing)) * 1.5
height: spinner.height / 3.5
color: "white"
}
Rectangle {
id: rect2
width: (spinner.width / spinner.barCount) - spinner.spacing
height: spinner.height
color: spinner.colors[0]
}
Rectangle {
id: rect3
width: (spinner.width / spinner.barCount) - spinner.spacing
height: spinner.height
color: spinner.colors[1]
}
Rectangle {
id: rect4
width: (spinner.width / spinner.barCount) - spinner.spacing
height: spinner.height
color: spinner.colors[2]
}
Rectangle {
id: rect5
width: (spinner.width / (spinner.barCount + 1)) - spinner.spacing
height: spinner.height / 3.5
color: "white"
}
Rectangle {
id: rect6
width: (spinner.width / spinner.barCount) - spinner.spacing
height: spinner.height
color: "white"
}
BlinkAnimation {
id: anim1
target: rect1
running: spinner.running
pauseDuration: spinner.pauseDuration
glowDuration: spinner.glowDuration
offset: 0 / spinner.barCount
}
BlinkAnimation {
id: anim2
target: rect2
running: spinner.running
pauseDuration: spinner.pauseDuration
glowDuration: spinner.glowDuration
offset: 1 / spinner.barCount
}
BlinkAnimation {
id: anim3
target: rect3
running: spinner.running
pauseDuration: spinner.pauseDuration
glowDuration: spinner.glowDuration
offset: 2 / spinner.barCount
}
BlinkAnimation {
id: anim4
target: rect4
running: spinner.running
pauseDuration: spinner.pauseDuration
glowDuration: spinner.glowDuration
offset: 3 / spinner.barCount
}
BlinkAnimation {
id: anim5
target: rect5
running: spinner.running
pauseDuration: spinner.pauseDuration
glowDuration: spinner.glowDuration
offset: 4 / spinner.barCount
}
BlinkAnimation {
id: anim6
target: rect6
running: spinner.running
pauseDuration: spinner.pauseDuration
glowDuration: spinner.glowDuration
offset: 5 / spinner.barCount
}
transform: Matrix4x4 { transform: Matrix4x4 {
matrix: Qt.matrix4x4(Math.cos(spinner.a), -Math.sin(spinner.a), 0, 0, 0, Math.cos(spinner.a), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) matrix: Qt.matrix4x4(Math.cos(spinner.a), -Math.sin(spinner.a), 0, 0, 0, Math.cos(spinner.a), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)
} }
} Rectangle {
id: rect1
color: "white"
height: spinner.height / 3.5
width: ((spinner.width / spinner.barCount) - (spinner.spacing)) * 1.5
}
Rectangle {
id: rect2
color: spinner.colors[0]
height: spinner.height
width: (spinner.width / spinner.barCount) - spinner.spacing
}
Rectangle {
id: rect3
color: spinner.colors[1]
height: spinner.height
width: (spinner.width / spinner.barCount) - spinner.spacing
}
Rectangle {
id: rect4
color: spinner.colors[2]
height: spinner.height
width: (spinner.width / spinner.barCount) - spinner.spacing
}
Rectangle {
id: rect5
color: "white"
height: spinner.height / 3.5
width: (spinner.width / (spinner.barCount + 1)) - spinner.spacing
}
Rectangle {
id: rect6
color: "white"
height: spinner.height
width: (spinner.width / spinner.barCount) - spinner.spacing
}
BlinkAnimation {
id: anim1
glowDuration: spinner.glowDuration
offset: 0 / spinner.barCount
pauseDuration: spinner.pauseDuration
running: spinner.running
target: rect1
}
BlinkAnimation {
id: anim2
glowDuration: spinner.glowDuration
offset: 1 / spinner.barCount
pauseDuration: spinner.pauseDuration
running: spinner.running
target: rect2
}
BlinkAnimation {
id: anim3
glowDuration: spinner.glowDuration
offset: 2 / spinner.barCount
pauseDuration: spinner.pauseDuration
running: spinner.running
target: rect3
}
BlinkAnimation {
id: anim4
glowDuration: spinner.glowDuration
offset: 3 / spinner.barCount
pauseDuration: spinner.pauseDuration
running: spinner.running
target: rect4
}
BlinkAnimation {
id: anim5
glowDuration: spinner.glowDuration
offset: 4 / spinner.barCount
pauseDuration: spinner.pauseDuration
running: spinner.running
target: rect5
}
BlinkAnimation {
id: anim6
glowDuration: spinner.glowDuration
offset: 5 / spinner.barCount
pauseDuration: spinner.pauseDuration
running: spinner.running
target: rect6
}
}
MultiEffect { MultiEffect {
anchors.fill: row anchors.fill: row
shadowBlur: 14 shadowBlur: 14
shadowEnabled: true
shadowColor: spinner.foreground shadowColor: spinner.foreground
shadowEnabled: true
source: row source: row
transform: Matrix4x4 { transform: Matrix4x4 {
matrix: Qt.matrix4x4(Math.cos(spinner.a), -Math.sin(spinner.a), 0, 0, 0, Math.cos(spinner.a), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) matrix: Qt.matrix4x4(Math.cos(spinner.a), -Math.sin(spinner.a), 0, 0, 0, Math.cos(spinner.a), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)
} }
} }
} }

View file

@ -7,102 +7,99 @@ import QtQuick.Particles 2.15
Item { Item {
id: effectRoot id: effectRoot
readonly property int maxLifespan: Math.max(confettiEmitter.lifeSpan, rainfallEmitter.lifeSpan) readonly property int maxLifespan: Math.max(confettiEmitter.lifeSpan, rainfallEmitter.lifeSpan)
required property bool shouldEffectsRun required property bool shouldEffectsRun
function pulseConfetti() {
confettiEmitter.pulse(effectRoot.height * 2);
}
function pulseRainfall() {
rainfallEmitter.pulse(effectRoot.height * 3.3);
}
function removeParticles() {
particleSystem.reset();
}
visible: effectRoot.shouldEffectsRun visible: effectRoot.shouldEffectsRun
function pulseConfetti()
{
confettiEmitter.pulse(effectRoot.height * 2)
}
function pulseRainfall()
{
rainfallEmitter.pulse(effectRoot.height * 3.3)
}
function removeParticles()
{
particleSystem.reset()
}
ParticleSystem { ParticleSystem {
id: particleSystem id: particleSystem
Component.onCompleted: stop();
paused: !effectRoot.shouldEffectsRun paused: !effectRoot.shouldEffectsRun
running: effectRoot.shouldEffectsRun running: effectRoot.shouldEffectsRun
}
Component.onCompleted: stop()
}
Emitter { Emitter {
id: confettiEmitter id: confettiEmitter
group: "confetti"
width: effectRoot.width * 3/4
enabled: false
anchors.horizontalCenter: effectRoot.horizontalCenter anchors.horizontalCenter: effectRoot.horizontalCenter
y: effectRoot.height
emitRate: Math.min(400 * Math.sqrt(effectRoot.width * effectRoot.height) / 870, 1000) emitRate: Math.min(400 * Math.sqrt(effectRoot.width * effectRoot.height) / 870, 1000)
enabled: false
group: "confetti"
lifeSpan: 15000 lifeSpan: 15000
system: particleSystem
maximumEmitted: 500 maximumEmitted: 500
velocityFromMovement: 8
size: 16 size: 16
sizeVariation: 4 sizeVariation: 4
system: particleSystem
velocityFromMovement: 8
width: effectRoot.width * 3 / 4
y: effectRoot.height
velocity: PointDirection { velocity: PointDirection {
x: 0 x: 0
y: -Math.min(450 * effectRoot.height / 700, 1000)
xVariation: Math.min(4 * effectRoot.width / 7, 450) xVariation: Math.min(4 * effectRoot.width / 7, 450)
y: -Math.min(450 * effectRoot.height / 700, 1000)
yVariation: 250 yVariation: 250
} }
} }
ImageParticle { ImageParticle {
system: particleSystem color: "white"
colorVariation: 1
entryEffect: ImageParticle.None
groups: ["confetti"] groups: ["confetti"]
source: "qrc:/confettiparticle.svg"
rotationVelocity: 0 rotationVelocity: 0
rotationVelocityVariation: 360 rotationVelocityVariation: 360
colorVariation: 1 source: "qrc:/confettiparticle.svg"
color: "white" system: particleSystem
entryEffect: ImageParticle.None
xVector: PointDirection { xVector: PointDirection {
x: 1 x: 1
y: 0
xVariation: 0.2 xVariation: 0.2
y: 0
yVariation: 0.2 yVariation: 0.2
} }
yVector: PointDirection { yVector: PointDirection {
x: 0 x: 0
y: 0.5
xVariation: 0.2 xVariation: 0.2
y: 0.5
yVariation: 0.2 yVariation: 0.2
} }
} }
Gravity { Gravity {
system: particleSystem
groups: ["confetti"]
anchors.fill: effectRoot anchors.fill: effectRoot
magnitude: 350
angle: 90 angle: 90
groups: ["confetti"]
magnitude: 350
system: particleSystem
} }
Emitter { Emitter {
id: rainfallEmitter id: rainfallEmitter
group: "rain"
width: effectRoot.width
enabled: false
anchors.horizontalCenter: effectRoot.horizontalCenter anchors.horizontalCenter: effectRoot.horizontalCenter
y: -60
emitRate: effectRoot.width / 30 emitRate: effectRoot.width / 30
enabled: false
group: "rain"
lifeSpan: 10000 lifeSpan: 10000
system: particleSystem system: particleSystem
width: effectRoot.width
y: -60
velocity: PointDirection { velocity: PointDirection {
x: 0 x: 0
y: 400
xVariation: 0 xVariation: 0
y: 400
yVariation: 75 yVariation: 75
} }
@ -121,14 +118,15 @@ Item {
//} //}
ImageParticle { ImageParticle {
system: particleSystem color: "#0099ff"
colorVariation: 0
entryEffect: ImageParticle.None
groups: ["rain"] groups: ["rain"]
source: "qrc:/confettiparticle.svg"
rotationVelocity: 0 rotationVelocity: 0
rotationVelocityVariation: 0 rotationVelocityVariation: 0
colorVariation: 0 source: "qrc:/confettiparticle.svg"
color: "#0099ff" system: particleSystem
entryEffect: ImageParticle.None
xVector: PointDirection { xVector: PointDirection {
x: 0.01 x: 0.01
y: 0 y: 0

View file

@ -5,27 +5,24 @@
import QtQuick import QtQuick
SequentialAnimation { SequentialAnimation {
property alias target: numberAnimation.target
property alias glowDuration: numberAnimation.duration property alias glowDuration: numberAnimation.duration
property int pauseDuration: 150
property double offset: 0 property double offset: 0
property int pauseDuration: 150
property alias target: numberAnimation.target
loops: Animation.Infinite loops: Animation.Infinite
PauseAnimation { PauseAnimation {
duration: pauseDuration * offset duration: pauseDuration * offset
} }
NumberAnimation { NumberAnimation {
id: numberAnimation id: numberAnimation
property: "opacity"
from: 0 from: 0
property: "opacity"
to: 1 to: 1
} }
PauseAnimation { PauseAnimation {
duration: pauseDuration * (1 - offset) duration: pauseDuration * (1 - offset)
} }
} }

View file

@ -14,27 +14,22 @@ Rectangle {
id: control id: control
property alias desiredVolume: volumeSlider.desiredVolume property alias desiredVolume: volumeSlider.desiredVolume
property var duration
property bool mediaLoaded: false
property var mediaState
property bool muted: false property bool muted: false
property bool playingVideo: false property bool playingVideo: false
property var mediaState
property bool mediaLoaded: false
property var duration
property var positionValue: 0
property var position property var position
property var positionValue: 0
property bool shouldShowControls: !playingVideo || playerMouseArea.shouldShowControls || volumeSlider.state == "shown" property bool shouldShowControls: !playingVideo || playerMouseArea.shouldShowControls || volumeSlider.state == "shown"
signal playPauseActivated() signal loadActivated
signal loadActivated() signal playPauseActivated
function showControls() {
controlHideTimer.restart();
}
function durationToString(duration) { function durationToString(duration) {
function maybeZeroPrepend(time) { function maybeZeroPrepend(time) {
return (time < 10) ? "0" + time.toString() : time.toString(); return (time < 10) ? "0" + time.toString() : time.toString();
} }
var totalSeconds = Math.floor(duration / 1000); var totalSeconds = Math.floor(duration / 1000);
var seconds = totalSeconds % 60; var seconds = totalSeconds % 60;
var minutes = (Math.floor(totalSeconds / 60)) % 60; var minutes = (Math.floor(totalSeconds / 60)) % 60;
@ -45,16 +40,25 @@ Rectangle {
var hh = hours.toString(); var hh = hours.toString();
if (hours < 1) if (hours < 1)
return mm + ":" + ss; return mm + ":" + ss;
return hh + ":" + mm + ":" + ss; return hh + ":" + mm + ":" + ss;
} }
function showControls() {
controlHideTimer.restart();
}
color: { color: {
var wc = palette.alternateBase; var wc = palette.alternateBase;
return Qt.rgba(wc.r, wc.g, wc.b, 0.5); return Qt.rgba(wc.r, wc.g, wc.b, 0.5);
} }
opacity: control.shouldShowControls ? 1 : 0
height: controlLayout.implicitHeight height: controlLayout.implicitHeight
opacity: control.shouldShowControls ? 1 : 0
// Fade controls in/out
Behavior on opacity {
OpacityAnimator {
duration: 100
}
}
HoverHandler { HoverHandler {
id: playerMouseArea id: playerMouseArea
@ -63,41 +67,40 @@ Rectangle {
onHoveredChanged: showControls() onHoveredChanged: showControls()
} }
ColumnLayout { ColumnLayout {
id: controlLayout id: controlLayout
enabled: control.shouldShowControls
spacing: 0
anchors.bottom: control.bottom anchors.bottom: control.bottom
anchors.left: control.left anchors.left: control.left
anchors.right: control.right anchors.right: control.right
enabled: control.shouldShowControls
spacing: 0
NhekoSlider { NhekoSlider {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: Nheko.paddingSmall Layout.leftMargin: Nheko.paddingSmall
Layout.rightMargin: Nheko.paddingSmall Layout.rightMargin: Nheko.paddingSmall
alwaysShowSlider: false
enabled: control.mediaLoaded enabled: control.mediaLoaded
value: control.positionValue
onMoved: control.position = value
from: 0 from: 0
to: control.duration to: control.duration
alwaysShowSlider: false value: control.positionValue
}
onMoved: control.position = value
}
RowLayout { RowLayout {
Layout.fillWidth: true
Layout.margins: Nheko.paddingSmall Layout.margins: Nheko.paddingSmall
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
Layout.fillWidth: true
// Cache/Play/pause button // Cache/Play/pause button
ImageButton { ImageButton {
id: playbackStateImage id: playbackStateImage
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
buttonTextColor: palette.text
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24 Layout.preferredWidth: 24
buttonTextColor: palette.text
image: { image: {
if (control.mediaLoaded) { if (control.mediaLoaded) {
if (control.mediaState == MediaPlayer.PlayingState) if (control.mediaState == MediaPlayer.PlayingState)
@ -108,38 +111,47 @@ Rectangle {
return ":/icons/icons/ui/download.svg"; return ":/icons/icons/ui/download.svg";
} }
} }
onClicked: control.mediaLoaded ? control.playPauseActivated() : control.loadActivated() onClicked: control.mediaLoaded ? control.playPauseActivated() : control.loadActivated()
} }
ImageButton { ImageButton {
id: volumeButton id: volumeButton
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
buttonTextColor: palette.text
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24 Layout.preferredWidth: 24
buttonTextColor: palette.text
image: { image: {
if (control.muted || control.desiredVolume <= 0) if (control.muted || control.desiredVolume <= 0)
return ":/icons/icons/ui/volume-off-indicator.svg"; return ":/icons/icons/ui/volume-off-indicator.svg";
else else
return ":/icons/icons/ui/volume-up.svg"; return ":/icons/icons/ui/volume-up.svg";
} }
onClicked: control.muted = !control.muted onClicked: control.muted = !control.muted
} }
NhekoSlider { NhekoSlider {
id: volumeSlider id: volumeSlider
property real desiredVolume: volumeSlider.value property real desiredVolume: volumeSlider.value
state: ""
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.preferredWidth: 0 Layout.preferredWidth: 0
opacity: 0 opacity: 0
orientation: Qt.Horizontal orientation: Qt.Horizontal
state: ""
value: 1 value: 1
onDesiredVolumeChanged: {
control.muted = !(desiredVolume > 0); states: State {
name: "shown"
when: Settings.mobileMode || volumeButton.hovered || volumeSlider.hovered || volumeSlider.pressed
PropertyChanges {
volumeSlider.implicitWidth: 100
}
PropertyChanges {
volumeSlider.opacity: 1
}
} }
transitions: [ transitions: [
Transition { Transition {
@ -150,20 +162,16 @@ Rectangle {
PauseAnimation { PauseAnimation {
duration: 50 duration: 50
} }
NumberAnimation { NumberAnimation {
duration: 100 duration: 100
properties: "opacity"
easing.type: Easing.InQuad easing.type: Easing.InQuad
properties: "opacity"
} }
} }
NumberAnimation { NumberAnimation {
properties: "Layout.preferredWidth"
duration: 150 duration: 150
properties: "Layout.preferredWidth"
} }
}, },
Transition { Transition {
from: "shown" from: "shown"
@ -173,54 +181,34 @@ Rectangle {
PauseAnimation { PauseAnimation {
duration: 100 duration: 100
} }
ParallelAnimation { ParallelAnimation {
NumberAnimation { NumberAnimation {
duration: 100 duration: 100
properties: "opacity"
easing.type: Easing.InQuad easing.type: Easing.InQuad
properties: "opacity"
} }
NumberAnimation { NumberAnimation {
properties: "Layout.preferredWidth"
duration: 150 duration: 150
properties: "Layout.preferredWidth"
} }
} }
} }
} }
] ]
states: State { onDesiredVolumeChanged: {
name: "shown" control.muted = !(desiredVolume > 0);
when: Settings.mobileMode || volumeButton.hovered || volumeSlider.hovered || volumeSlider.pressed
PropertyChanges {
volumeSlider.implicitWidth: 100
} }
PropertyChanges {
volumeSlider.opacity: 1
} }
}
}
Label { Label {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
text: (!control.mediaLoaded ? "-- " : durationToString(control.positionValue)) + " / " + durationToString(control.duration)
color: palette.text color: palette.text
text: (!control.mediaLoaded ? "-- " : durationToString(control.positionValue)) + " / " + durationToString(control.duration)
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
} }
// For hiding controls on stationary cursor // For hiding controls on stationary cursor
@ -230,13 +218,4 @@ Rectangle {
interval: 1500 //ms interval: 1500 //ms
repeat: false repeat: false
} }
// Fade controls in/out
Behavior on opacity {
OpacityAnimator {
duration: 100
}
}
} }

View file

@ -9,51 +9,48 @@ import QtQuick.Layouts 1.2
import im.nheko 1.0 import im.nheko 1.0
Rectangle { Rectangle {
visible: CallManager.isOnCall
color: callInviteBar.color color: callInviteBar.color
implicitHeight: visible ? rowLayout.height + 8 : 0 implicitHeight: visible ? rowLayout.height + 8 : 0
visible: CallManager.isOnCall
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
if (CallManager.callType != Voip.VOICE) if (CallManager.callType != Voip.VOICE)
stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1; stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1;
} }
} }
RowLayout { RowLayout {
id: rowLayout id: rowLayout
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 8
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 8
Avatar { Avatar {
implicitWidth: Nheko.avatarSize displayName: CallManager.callPartyDisplayName
implicitHeight: Nheko.avatarSize implicitHeight: Nheko.avatarSize
implicitWidth: Nheko.avatarSize
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
userid: CallManager.callParty userid: CallManager.callParty
displayName: CallManager.callPartyDisplayName
onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId) onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId)
} }
Label { Label {
Layout.leftMargin: 8 Layout.leftMargin: 8
color: "#000000"
font.pointSize: fontMetrics.font.pointSize * 1.1 font.pointSize: fontMetrics.font.pointSize * 1.1
text: CallManager.callPartyDisplayName text: CallManager.callPartyDisplayName
color: "#000000"
} }
Image { Image {
id: callTypeIcon id: callTypeIcon
Layout.leftMargin: 4 Layout.leftMargin: 4
Layout.preferredWidth: 24
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24
} }
Item { Item {
states: [ states: [
State { State {
@ -63,7 +60,6 @@ Rectangle {
PropertyChanges { PropertyChanges {
callTypeIcon.source: "qrc:/icons/icons/ui/place-call.svg" callTypeIcon.source: "qrc:/icons/icons/ui/place-call.svg"
} }
}, },
State { State {
name: "VIDEO" name: "VIDEO"
@ -72,7 +68,6 @@ Rectangle {
PropertyChanges { PropertyChanges {
callTypeIcon.source: "qrc:/icons/icons/ui/video.svg" callTypeIcon.source: "qrc:/icons/icons/ui/video.svg"
} }
}, },
State { State {
name: "SCREEN" name: "SCREEN"
@ -81,18 +76,15 @@ Rectangle {
PropertyChanges { PropertyChanges {
callTypeIcon.source: "qrc:/icons/icons/ui/screen-share.svg" callTypeIcon.source: "qrc:/icons/icons/ui/screen-share.svg"
} }
} }
] ]
} }
Label { Label {
id: callStateLabel id: callStateLabel
font.pointSize: fontMetrics.font.pointSize * 1.1
color: "#000000" color: "#000000"
font.pointSize: fontMetrics.font.pointSize * 1.1
} }
Item { Item {
states: [ states: [
State { State {
@ -102,7 +94,6 @@ Rectangle {
PropertyChanges { PropertyChanges {
callStateLabel.text: qsTr("Calling...") callStateLabel.text: qsTr("Calling...")
} }
}, },
State { State {
name: "CONNECTING" name: "CONNECTING"
@ -111,7 +102,6 @@ Rectangle {
PropertyChanges { PropertyChanges {
callStateLabel.text: qsTr("Connecting...") callStateLabel.text: qsTr("Connecting...")
} }
}, },
State { State {
name: "ANSWERSENT" name: "ANSWERSENT"
@ -120,7 +110,6 @@ Rectangle {
PropertyChanges { PropertyChanges {
callStateLabel.text: qsTr("Connecting...") callStateLabel.text: qsTr("Connecting...")
} }
}, },
State { State {
name: "CONNECTED" name: "CONNECTED"
@ -129,15 +118,12 @@ Rectangle {
PropertyChanges { PropertyChanges {
callStateLabel.text: "00:00" callStateLabel.text: "00:00"
} }
PropertyChanges { PropertyChanges {
callTimer.startTime: Math.floor((new Date()).getTime() / 1000) callTimer.startTime: Math.floor((new Date()).getTime() / 1000)
} }
PropertyChanges { PropertyChanges {
stackLayout.currentIndex: CallManager.callType != Voip.VOICE ? 1 : 0 stackLayout.currentIndex: CallManager.callType != Voip.VOICE ? 1 : 0
} }
}, },
State { State {
name: "DISCONNECTED" name: "DISCONNECTED"
@ -152,14 +138,12 @@ Rectangle {
// stackLayout.currentIndex: 0 // stackLayout.currentIndex: 0
//} //}
PropertyChanges { PropertyChanges {
target: stackLayout
currentIndex: 0 // qmllint disable Quick.property-changes-parsed currentIndex: 0 // qmllint disable Quick.property-changes-parsed
target: stackLayout
} }
} }
] ]
} }
Timer { Timer {
id: callTimer id: callTimer
@ -170,8 +154,9 @@ Rectangle {
} }
interval: 1000 interval: 1000
running: CallManager.callState == Voip.CONNECTED
repeat: true repeat: true
running: CallManager.callState == Voip.CONNECTED
onTriggered: { onTriggered: {
var d = new Date(); var d = new Date();
let seconds = Math.floor(d.getTime() / 1000 - startTime); let seconds = Math.floor(d.getTime() / 1000 - startTime);
@ -181,44 +166,40 @@ Rectangle {
callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s); callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s);
} }
} }
Label { Label {
Layout.leftMargin: 16 Layout.leftMargin: 16
visible: CallManager.callType == Voip.SCREEN && CallManager.callState == Voip.CONNECTED
text: qsTr("You are screen sharing")
font.pointSize: fontMetrics.font.pointSize * 1.1
color: "#000000" color: "#000000"
font.pointSize: fontMetrics.font.pointSize * 1.1
text: qsTr("You are screen sharing")
visible: CallManager.callType == Voip.SCREEN && CallManager.callState == Voip.CONNECTED
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
ImageButton { ImageButton {
visible: CallManager.haveLocalPiP
Layout.preferredWidth: 24
Layout.preferredHeight: 24 Layout.preferredHeight: 24
buttonTextColor: "#000000" Layout.preferredWidth: 24
image: ":/icons/icons/ui/picture-in-picture.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Hide/Show Picture-in-Picture") ToolTip.text: qsTr("Hide/Show Picture-in-Picture")
ToolTip.visible: hovered
buttonTextColor: "#000000"
hoverEnabled: true
image: ":/icons/icons/ui/picture-in-picture.svg"
visible: CallManager.haveLocalPiP
onClicked: CallManager.toggleLocalPiP() onClicked: CallManager.toggleLocalPiP()
} }
ImageButton { ImageButton {
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.rightMargin: 16
Layout.preferredWidth: 24
Layout.preferredHeight: 24 Layout.preferredHeight: 24
buttonTextColor: "#000000" Layout.preferredWidth: 24
image: CallManager.isMicMuted ? ":/icons/icons/ui/microphone-unmute.svg" : ":/icons/icons/ui/microphone-mute.svg" Layout.rightMargin: 16
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: CallManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic") ToolTip.text: CallManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic")
ToolTip.visible: hovered
buttonTextColor: "#000000"
hoverEnabled: true
image: CallManager.isMicMuted ? ":/icons/icons/ui/microphone-unmute.svg" : ":/icons/icons/ui/microphone-mute.svg"
onClicked: CallManager.toggleMicMute() onClicked: CallManager.toggleMicMute()
} }
} }
} }

View file

@ -9,79 +9,70 @@ import im.nheko 1.0
Popup { Popup {
modal: true modal: true
background: Rectangle {
border.color: palette.windowText
color: palette.window
}
// only set the anchors on Qt 5.12 or higher // only set the anchors on Qt 5.12 or higher
// see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop // see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop
Component.onCompleted: { Component.onCompleted: {
if (anchors) if (anchors)
anchors.centerIn = parent; anchors.centerIn = parent;
} }
ColumnLayout { ColumnLayout {
spacing: 16 spacing: 16
ColumnLayout { ColumnLayout {
spacing: 8
Layout.topMargin: 8
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.rightMargin: 8 Layout.rightMargin: 8
Layout.topMargin: 8
spacing: 8
RowLayout { RowLayout {
Image { Image {
Layout.preferredWidth: 22
Layout.preferredHeight: 22 Layout.preferredHeight: 22
Layout.preferredWidth: 22
source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + palette.windowText source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + palette.windowText
} }
ComboBox { ComboBox {
id: micCombo id: micCombo
Layout.fillWidth: true Layout.fillWidth: true
model: CallManager.mics model: CallManager.mics
} }
} }
RowLayout { RowLayout {
visible: CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0 visible: CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0
Image { Image {
Layout.preferredWidth: 22
Layout.preferredHeight: 22 Layout.preferredHeight: 22
Layout.preferredWidth: 22
source: "image://colorimage/:/icons/icons/ui/video-call.svg?" + palette.windowText source: "image://colorimage/:/icons/icons/ui/video-call.svg?" + palette.windowText
} }
ComboBox { ComboBox {
id: cameraCombo id: cameraCombo
Layout.fillWidth: true Layout.fillWidth: true
model: CallManager.cameras model: CallManager.cameras
} }
} }
} }
DialogButtonBox { DialogButtonBox {
Layout.leftMargin: 128 Layout.leftMargin: 128
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: { onAccepted: {
Settings.microphone = micCombo.currentText; Settings.microphone = micCombo.currentText;
if (cameraCombo.visible) if (cameraCombo.visible)
Settings.camera = cameraCombo.currentText; Settings.camera = cameraCombo.currentText;
close(); close();
} }
onRejected: { onRejected: {
close(); close();
} }
} }
} }
background: Rectangle {
color: palette.window
border.color: palette.windowText
}
} }

View file

@ -12,51 +12,50 @@ Popup {
id: callInv id: callInv
closePolicy: Popup.NoAutoClose closePolicy: Popup.NoAutoClose
width: parent.width
height: parent.height height: parent.height
width: parent.width
background: Rectangle {
border.color: palette.windowText
color: palette.window
}
Component { Component {
id: deviceError id: deviceError
DeviceError { DeviceError {
} }
} }
Connections { Connections {
function onNewInviteState() { function onNewInviteState() {
if (!CallManager.haveCallInvite) if (!CallManager.haveCallInvite)
close(); close();
} }
target: CallManager target: CallManager
} }
ColumnLayout { ColumnLayout {
anchors.top: parent.top
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
Label { Label {
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
Layout.topMargin: callInv.parent.height / 25
Layout.fillWidth: true Layout.fillWidth: true
text: CallManager.callPartyDisplayName Layout.topMargin: callInv.parent.height / 25
font.pointSize: fontMetrics.font.pointSize * 2
color: palette.windowText color: palette.windowText
font.pointSize: fontMetrics.font.pointSize * 2
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: CallManager.callPartyDisplayName
} }
Avatar { Avatar {
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
Layout.preferredHeight: callInv.height / 5 Layout.preferredHeight: callInv.height / 5
Layout.preferredWidth: callInv.height / 5 Layout.preferredWidth: callInv.height / 5
displayName: CallManager.callPartyDisplayName
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
userid: CallManager.callParty userid: CallManager.callParty
displayName: CallManager.callPartyDisplayName
} }
ColumnLayout { ColumnLayout {
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
Layout.bottomMargin: callInv.height / 25 Layout.bottomMargin: callInv.height / 25
@ -65,20 +64,17 @@ Popup {
property string image: CallManager.callType == Voip.VIDEO ? ":/icons/icons/ui/video.svg" : ":/icons/icons/ui/place-call.svg" property string image: CallManager.callType == Voip.VIDEO ? ":/icons/icons/ui/video.svg" : ":/icons/icons/ui/place-call.svg"
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: callInv.height / 10
Layout.preferredHeight: callInv.height / 10 Layout.preferredHeight: callInv.height / 10
Layout.preferredWidth: callInv.height / 10
source: "image://colorimage/" + image + "?" + palette.windowText source: "image://colorimage/" + image + "?" + palette.windowText
} }
Label { Label {
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call")
font.pointSize: fontMetrics.font.pointSize * 2
color: palette.windowText color: palette.windowText
font.pointSize: fontMetrics.font.pointSize * 2
text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call")
} }
} }
ColumnLayout { ColumnLayout {
id: deviceCombos id: deviceCombos
@ -91,41 +87,34 @@ Popup {
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
Image { Image {
Layout.preferredWidth: deviceCombos.imageSize
Layout.preferredHeight: deviceCombos.imageSize Layout.preferredHeight: deviceCombos.imageSize
Layout.preferredWidth: deviceCombos.imageSize
source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + palette.windowText source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + palette.windowText
} }
ComboBox { ComboBox {
id: micCombo id: micCombo
Layout.fillWidth: true Layout.fillWidth: true
model: CallManager.mics model: CallManager.mics
} }
} }
RowLayout { RowLayout {
visible: CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
visible: CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0
Image { Image {
Layout.preferredWidth: deviceCombos.imageSize
Layout.preferredHeight: deviceCombos.imageSize Layout.preferredHeight: deviceCombos.imageSize
Layout.preferredWidth: deviceCombos.imageSize
source: "image://colorimage/:/icons/icons/ui/video.svg?" + palette.windowText source: "image://colorimage/:/icons/icons/ui/video.svg?" + palette.windowText
} }
ComboBox { ComboBox {
id: cameraCombo id: cameraCombo
Layout.fillWidth: true Layout.fillWidth: true
model: CallManager.cameras model: CallManager.cameras
} }
} }
} }
RowLayout { RowLayout {
id: buttonLayout id: buttonLayout
@ -148,60 +137,48 @@ Popup {
spacing: callInv.height / 6 spacing: callInv.height / 6
RoundButton { RoundButton {
implicitWidth: buttonLayout.buttonSize
implicitHeight: buttonLayout.buttonSize implicitHeight: buttonLayout.buttonSize
onClicked: { implicitWidth: buttonLayout.buttonSize
CallManager.rejectInvite();
close();
}
background: Rectangle { background: Rectangle {
radius: buttonLayout.buttonSize / 2
color: "#ff0000" color: "#ff0000"
radius: buttonLayout.buttonSize / 2
} }
contentItem: Image { contentItem: Image {
source: "image://colorimage/:/icons/icons/ui/end-call.svg?#ffffff" source: "image://colorimage/:/icons/icons/ui/end-call.svg?#ffffff"
} }
onClicked: {
CallManager.rejectInvite();
close();
}
} }
RoundButton { RoundButton {
id: acceptButton id: acceptButton
property string image: CallManager.callType == Voip.VIDEO ? ":/icons/icons/ui/video.svg" : ":/icons/icons/ui/place-call.svg" property string image: CallManager.callType == Voip.VIDEO ? ":/icons/icons/ui/video.svg" : ":/icons/icons/ui/place-call.svg"
implicitWidth: buttonLayout.buttonSize
implicitHeight: buttonLayout.buttonSize implicitHeight: buttonLayout.buttonSize
implicitWidth: buttonLayout.buttonSize
background: Rectangle {
color: "#00ff00"
radius: buttonLayout.buttonSize / 2
}
contentItem: Image {
source: "image://colorimage/" + acceptButton.image + "?#ffffff"
}
onClicked: { onClicked: {
if (buttonLayout.validateMic()) { if (buttonLayout.validateMic()) {
Settings.microphone = micCombo.currentText; Settings.microphone = micCombo.currentText;
if (cameraCombo.visible) if (cameraCombo.visible)
Settings.camera = cameraCombo.currentText; Settings.camera = cameraCombo.currentText;
CallManager.acceptInvite(); CallManager.acceptInvite();
close(); close();
} }
} }
background: Rectangle {
radius: buttonLayout.buttonSize / 2
color: "#00ff00"
} }
contentItem: Image {
source: "image://colorimage/" + acceptButton.image + "?#ffffff"
} }
} }
}
}
background: Rectangle {
color: palette.window
border.color: palette.windowText
}
} }

View file

@ -9,87 +9,80 @@ import QtQuick.Layouts
import im.nheko import im.nheko
Rectangle { Rectangle {
visible: CallManager.haveCallInvite && !Settings.mobileMode
color: "#2ECC71" color: "#2ECC71"
implicitHeight: visible ? rowLayout.height + 8 : 0 implicitHeight: visible ? rowLayout.height + 8 : 0
visible: CallManager.haveCallInvite && !Settings.mobileMode
Component { Component {
id: devicesDialog id: devicesDialog
CallDevices { CallDevices {
} }
} }
Component { Component {
id: deviceError id: deviceError
DeviceError { DeviceError {
} }
} }
RowLayout { RowLayout {
id: rowLayout id: rowLayout
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 8
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 8
Avatar { Avatar {
implicitWidth: Nheko.avatarSize displayName: CallManager.callPartyDisplayName
implicitHeight: Nheko.avatarSize implicitHeight: Nheko.avatarSize
implicitWidth: Nheko.avatarSize
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
userid: CallManager.callParty userid: CallManager.callParty
displayName: CallManager.callPartyDisplayName
onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId) onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId)
} }
Label { Label {
Layout.leftMargin: 8 Layout.leftMargin: 8
color: "#000000"
font.pointSize: fontMetrics.font.pointSize * 1.1 font.pointSize: fontMetrics.font.pointSize * 1.1
text: CallManager.callPartyDisplayName text: CallManager.callPartyDisplayName
color: "#000000"
} }
Image { Image {
Layout.leftMargin: 4 Layout.leftMargin: 4
Layout.preferredWidth: 24
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24
source: CallManager.callType == Voip.VIDEO ? "qrc:/icons/icons/ui/video.svg" : "qrc:/icons/icons/ui/place-call.svg" source: CallManager.callType == Voip.VIDEO ? "qrc:/icons/icons/ui/video.svg" : "qrc:/icons/icons/ui/place-call.svg"
} }
Label { Label {
color: "#000000"
font.pointSize: fontMetrics.font.pointSize * 1.1 font.pointSize: fontMetrics.font.pointSize * 1.1
text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call") text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call")
color: "#000000"
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
ImageButton { ImageButton {
Layout.rightMargin: 16
Layout.preferredWidth: 20
Layout.preferredHeight: 20 Layout.preferredHeight: 20
buttonTextColor: "#000000" Layout.preferredWidth: 20
image: ":/icons/icons/ui/settings.svg" Layout.rightMargin: 16
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Devices") ToolTip.text: qsTr("Devices")
ToolTip.visible: hovered
buttonTextColor: "#000000"
hoverEnabled: true
image: ":/icons/icons/ui/settings.svg"
onClicked: { onClicked: {
var dialog = devicesDialog.createObject(timelineRoot); var dialog = devicesDialog.createObject(timelineRoot);
dialog.open(); dialog.open();
timelineRoot.destroyOnClose(dialog); timelineRoot.destroyOnClose(dialog);
} }
} }
Button { Button {
Layout.rightMargin: 4 Layout.rightMargin: 4
icon.source: CallManager.callType == Voip.VIDEO ? "qrc:/icons/icons/ui/video.svg" : "qrc:/icons/icons/ui/place-call.svg" icon.source: CallManager.callType == Voip.VIDEO ? "qrc:/icons/icons/ui/video.svg" : "qrc:/icons/icons/ui/place-call.svg"
text: qsTr("Accept") text: qsTr("Accept")
onClicked: { onClicked: {
if (CallManager.mics.length == 0) { if (CallManager.mics.length == 0) {
var dialog = deviceError.createObject(timelineRoot, { var dialog = deviceError.createObject(timelineRoot, {
@ -120,16 +113,14 @@ Rectangle {
CallManager.acceptInvite(); CallManager.acceptInvite();
} }
} }
Button { Button {
Layout.rightMargin: 16 Layout.rightMargin: 16
icon.source: "qrc:/icons/icons/ui/end-call.svg" icon.source: "qrc:/icons/icons/ui/end-call.svg"
text: qsTr("Decline") text: qsTr("Decline")
onClicked: { onClicked: {
CallManager.rejectInvite(); CallManager.rejectInvite();
} }
} }
} }
} }

View file

@ -8,35 +8,33 @@ import QtQuick.Layouts 1.2
Popup { Popup {
id: r id: r
property string errorString property string errorString
property var image property var image
modal: true modal: true
background: Rectangle {
border.color: palette.windowText
color: palette.window
}
// only set the anchors on Qt 5.12 or higher // only set the anchors on Qt 5.12 or higher
// see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop // see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop
Component.onCompleted: { Component.onCompleted: {
if (anchors) if (anchors)
anchors.centerIn = parent; anchors.centerIn = parent;
} }
RowLayout { RowLayout {
Image { Image {
Layout.preferredWidth: 16
Layout.preferredHeight: 16 Layout.preferredHeight: 16
Layout.preferredWidth: 16
source: "image://colorimage/" + r.image + "?" + palette.windowText source: "image://colorimage/" + r.image + "?" + palette.windowText
} }
Label { Label {
text: r.errorString
color: palette.windowText color: palette.windowText
text: r.errorString
} }
} }
background: Rectangle {
color: palette.window
border.color: palette.windowText
}
} }

View file

@ -10,12 +10,17 @@ import im.nheko 1.0
Popup { Popup {
modal: true modal: true
background: Rectangle {
border.color: palette.windowText
color: palette.window
}
// only set the anchors on Qt 5.12 or higher // only set the anchors on Qt 5.12 or higher
// see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop // see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop
Component.onCompleted: { Component.onCompleted: {
if (anchors) if (anchors)
anchors.centerIn = parent; anchors.centerIn = parent;
} }
Component { Component {
@ -23,29 +28,24 @@ Popup {
DeviceError { DeviceError {
} }
} }
ColumnLayout { ColumnLayout {
id: columnLayout id: columnLayout
spacing: 16 spacing: 16
RowLayout { RowLayout {
Layout.topMargin: 8
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.topMargin: 8
Label { Label {
text: qsTr("Place a call to %1?").arg(room.roomName)
color: palette.windowText color: palette.windowText
text: qsTr("Place a call to %1?").arg(room.roomName)
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
RowLayout { RowLayout {
id: buttonLayout id: buttonLayout
@ -66,18 +66,19 @@ Popup {
Layout.rightMargin: 8 Layout.rightMargin: 8
Avatar { Avatar {
Layout.rightMargin: cameraCombo.visible ? 16 : 64
Layout.preferredWidth: Nheko.avatarSize
Layout.preferredHeight: Nheko.avatarSize Layout.preferredHeight: Nheko.avatarSize
url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/") Layout.preferredWidth: Nheko.avatarSize
Layout.rightMargin: cameraCombo.visible ? 16 : 64
displayName: room.roomName displayName: room.roomName
roomid: room.roomId roomid: room.roomId
url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId) onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId)
} }
Button { Button {
text: qsTr("Voice")
icon.source: "qrc:/icons/icons/ui/place-call.svg" icon.source: "qrc:/icons/icons/ui/place-call.svg"
text: qsTr("Voice")
onClicked: { onClicked: {
if (buttonLayout.validateMic()) { if (buttonLayout.validateMic()) {
Settings.microphone = micCombo.currentText; Settings.microphone = micCombo.currentText;
@ -86,11 +87,11 @@ Popup {
} }
} }
} }
Button { Button {
visible: CallManager.cameras.length > 0
text: qsTr("Video")
icon.source: "qrc:/icons/icons/ui/video.svg" icon.source: "qrc:/icons/icons/ui/video.svg"
text: qsTr("Video")
visible: CallManager.cameras.length > 0
onClicked: { onClicked: {
if (buttonLayout.validateMic()) { if (buttonLayout.validateMic()) {
Settings.microphone = micCombo.currentText; Settings.microphone = micCombo.currentText;
@ -100,15 +101,14 @@ Popup {
} }
} }
} }
Button { Button {
text: qsTr("Screen")
icon.source: "qrc:/icons/icons/ui/screen-share.svg" icon.source: "qrc:/icons/icons/ui/screen-share.svg"
text: qsTr("Screen")
onClicked: { onClicked: {
if (buttonLayout.validateMic()) { if (buttonLayout.validateMic()) {
Settings.microphone = micCombo.currentText; Settings.microphone = micCombo.currentText;
Settings.camera = cameraCombo.currentText; Settings.camera = cameraCombo.currentText;
var dialog = screenShareDialog.createObject(timelineRoot); var dialog = screenShareDialog.createObject(timelineRoot);
dialog.open(); dialog.open();
timelineRoot.destroyOnClose(dialog); timelineRoot.destroyOnClose(dialog);
@ -116,67 +116,52 @@ Popup {
} }
} }
} }
Button { Button {
text: qsTr("Cancel") text: qsTr("Cancel")
onClicked: { onClicked: {
close(); close();
} }
} }
} }
ColumnLayout { ColumnLayout {
spacing: 8 spacing: 8
RowLayout { RowLayout {
Layout.bottomMargin: cameraCombo.visible ? 0 : 8
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.rightMargin: 8 Layout.rightMargin: 8
Layout.bottomMargin: cameraCombo.visible ? 0 : 8
Image { Image {
Layout.preferredWidth: 22
Layout.preferredHeight: 22 Layout.preferredHeight: 22
Layout.preferredWidth: 22
source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + palette.windowText source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + palette.windowText
} }
ComboBox { ComboBox {
id: micCombo id: micCombo
Layout.fillWidth: true Layout.fillWidth: true
model: CallManager.mics model: CallManager.mics
} }
} }
RowLayout { RowLayout {
visible: CallManager.cameras.length > 0 Layout.bottomMargin: 8
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.rightMargin: 8 Layout.rightMargin: 8
Layout.bottomMargin: 8 visible: CallManager.cameras.length > 0
Image { Image {
Layout.preferredWidth: 22
Layout.preferredHeight: 22 Layout.preferredHeight: 22
Layout.preferredWidth: 22
source: "image://colorimage/:/icons/icons/ui/video.svg?" + palette.windowText source: "image://colorimage/:/icons/icons/ui/video.svg?" + palette.windowText
} }
ComboBox { ComboBox {
id: cameraCombo id: cameraCombo
Layout.fillWidth: true Layout.fillWidth: true
model: CallManager.cameras model: CallManager.cameras
} }
} }
} }
} }
background: Rectangle {
color: palette.window
border.color: palette.windowText
}
} }

View file

@ -9,9 +9,13 @@ import QtQuick.Layouts
import im.nheko import im.nheko
Popup { Popup {
anchors.centerIn: parent
modal: true modal: true
anchors.centerIn: parent; background: Rectangle {
border.color: palette.windowText
color: palette.window
}
Component.onCompleted: { Component.onCompleted: {
frameRateCombo.currentIndex = frameRateCombo.find(Settings.screenShareFrameRate); frameRateCombo.currentIndex = frameRateCombo.find(Settings.screenShareFrameRate);
@ -22,176 +26,151 @@ Popup {
ColumnLayout { ColumnLayout {
Label { Label {
Layout.topMargin: 16 Layout.alignment: Qt.AlignLeft
Layout.bottomMargin: 16 Layout.bottomMargin: 16
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.rightMargin: 8 Layout.rightMargin: 8
Layout.alignment: Qt.AlignLeft Layout.topMargin: 16
text: qsTr("Share desktop with %1?").arg(room.roomName)
color: palette.windowText color: palette.windowText
text: qsTr("Share desktop with %1?").arg(room.roomName)
} }
RowLayout { RowLayout {
Layout.bottomMargin: 8
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.rightMargin: 8 Layout.rightMargin: 8
Layout.bottomMargin: 8
Label { Label {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("Method:")
color: palette.windowText color: palette.windowText
text: qsTr("Method:")
} }
ComboBox { ComboBox {
id: screenshareType id: screenshareType
Layout.fillWidth: true Layout.fillWidth: true
model: CallManager.screenShareTypeList() model: CallManager.screenShareTypeList()
onCurrentIndexChanged: CallManager.setScreenShareType(currentIndex);
}
}
onCurrentIndexChanged: CallManager.setScreenShareType(currentIndex)
}
}
RowLayout { RowLayout {
Layout.bottomMargin: 8
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.rightMargin: 8 Layout.rightMargin: 8
Layout.bottomMargin: 8
Label { Label {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("Window:")
color: palette.windowText color: palette.windowText
text: qsTr("Window:")
} }
ComboBox { ComboBox {
visible: CallManager.screenShareType == Voip.X11
id: windowCombo id: windowCombo
Layout.fillWidth: true Layout.fillWidth: true
model: CallManager.windowList() model: CallManager.windowList()
visible: CallManager.screenShareType == Voip.X11
} }
Button { Button {
visible: CallManager.screenShareType == Voip.XDP
highlighted: !CallManager.screenShareReady highlighted: !CallManager.screenShareReady
text: qsTr("Request screencast") text: qsTr("Request screencast")
visible: CallManager.screenShareType == Voip.XDP
onClicked: { onClicked: {
Settings.screenShareHideCursor = hideCursorCheckBox.checked; Settings.screenShareHideCursor = hideCursorCheckBox.checked;
CallManager.setupScreenShareXDP(); CallManager.setupScreenShareXDP();
} }
} }
} }
RowLayout { RowLayout {
Layout.bottomMargin: 8
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.rightMargin: 8 Layout.rightMargin: 8
Layout.bottomMargin: 8
Label { Label {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("Frame rate:")
color: palette.windowText color: palette.windowText
text: qsTr("Frame rate:")
} }
ComboBox { ComboBox {
id: frameRateCombo id: frameRateCombo
Layout.fillWidth: true Layout.fillWidth: true
model: ["25", "20", "15", "10", "5", "2", "1"] model: ["25", "20", "15", "10", "5", "2", "1"]
} }
} }
GridLayout { GridLayout {
Layout.margins: 8
columns: 2 columns: 2
rowSpacing: 10 rowSpacing: 10
Layout.margins: 8
MatrixText { MatrixText {
text: qsTr("Include your camera picture-in-picture") text: qsTr("Include your camera picture-in-picture")
} }
ToggleButton { ToggleButton {
id: pipCheckBox id: pipCheckBox
enabled: CallManager.cameras.length > 0
checked: CallManager.cameras.length > 0 && Settings.screenSharePiP
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: CallManager.cameras.length > 0 && Settings.screenSharePiP
enabled: CallManager.cameras.length > 0
} }
MatrixText { MatrixText {
text: qsTr("Request remote camera")
ToolTip.text: qsTr("View your callee's camera like a regular video call") ToolTip.text: qsTr("View your callee's camera like a regular video call")
ToolTip.visible: hovered ToolTip.visible: hovered
text: qsTr("Request remote camera")
} }
ToggleButton { ToggleButton {
id: remoteVideoCheckBox id: remoteVideoCheckBox
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: Settings.screenShareRemoteVideo
ToolTip.text: qsTr("View your callee's camera like a regular video call") ToolTip.text: qsTr("View your callee's camera like a regular video call")
ToolTip.visible: hovered ToolTip.visible: hovered
checked: Settings.screenShareRemoteVideo
} }
MatrixText { MatrixText {
text: qsTr("Hide mouse cursor") text: qsTr("Hide mouse cursor")
} }
ToggleButton { ToggleButton {
id: hideCursorCheckBox id: hideCursorCheckBox
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: Settings.screenShareHideCursor checked: Settings.screenShareHideCursor
} }
} }
RowLayout { RowLayout {
Layout.margins: 8 Layout.margins: 8
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
Button { Button {
visible: CallManager.screenShareReady
text: qsTr("Share")
icon.source: "qrc:/icons/icons/ui/screen-share.svg" icon.source: "qrc:/icons/icons/ui/screen-share.svg"
text: qsTr("Share")
visible: CallManager.screenShareReady
onClicked: { onClicked: {
Settings.screenShareFrameRate = frameRateCombo.currentText; Settings.screenShareFrameRate = frameRateCombo.currentText;
Settings.screenSharePiP = pipCheckBox.checked; Settings.screenSharePiP = pipCheckBox.checked;
Settings.screenShareRemoteVideo = remoteVideoCheckBox.checked; Settings.screenShareRemoteVideo = remoteVideoCheckBox.checked;
Settings.screenShareHideCursor = hideCursorCheckBox.checked; Settings.screenShareHideCursor = hideCursorCheckBox.checked;
CallManager.sendInvite(room.roomId, Voip.SCREEN, windowCombo.currentIndex); CallManager.sendInvite(room.roomId, Voip.SCREEN, windowCombo.currentIndex);
close(); close();
} }
} }
Button { Button {
visible: CallManager.screenShareReady
text: qsTr("Preview") text: qsTr("Preview")
visible: CallManager.screenShareReady
onClicked: { onClicked: {
CallManager.previewWindow(windowCombo.currentIndex); CallManager.previewWindow(windowCombo.currentIndex);
} }
} }
Button { Button {
text: qsTr("Cancel") text: qsTr("Cancel")
onClicked: { onClicked: {
close(); close();
} }
} }
} }
} }
background: Rectangle {
color: palette.window
border.color: palette.windowText
}
} }