mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-21 10:40:47 +03:00
Enable qmlformat in linting
This commit is contained in:
parent
ee6ee46e7c
commit
814d9c6a17
94 changed files with 4317 additions and 4984 deletions
|
@ -15,6 +15,14 @@ do
|
|||
clang-format -i "$f"
|
||||
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
|
||||
|
||||
if command -v /usr/lib64/qt6/bin/qmllint &> /dev/null; then
|
||||
|
|
|
@ -35,9 +35,9 @@ Page {
|
|||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
height: parent.height
|
||||
model: Communities.filtered()
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
id: scrollbar
|
||||
|
@ -138,10 +138,11 @@ Page {
|
|||
id: avatar
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredHeight: avatarSize
|
||||
Layout.preferredWidth: avatarSize
|
||||
color: communityItem.backgroundColor
|
||||
displayName: model.displayName
|
||||
enabled: false
|
||||
Layout.preferredHeight: avatarSize
|
||||
roomid: model.id
|
||||
textColor: model.avatarUrl?.startsWith(":/") == true ? communityItem.unimportantText : communityItem.importantText
|
||||
url: {
|
||||
|
@ -152,7 +153,6 @@ Page {
|
|||
else
|
||||
return "";
|
||||
}
|
||||
Layout.preferredWidth: avatarSize
|
||||
|
||||
NotificationBubble {
|
||||
anchors.bottom: avatar.bottom
|
||||
|
|
|
@ -149,10 +149,10 @@ Control {
|
|||
spacing: rowSpacing
|
||||
|
||||
Avatar {
|
||||
displayName: model.displayName
|
||||
enabled: false
|
||||
Layout.preferredHeight: popup.avatarHeight
|
||||
Layout.preferredWidth: popup.avatarWidth
|
||||
displayName: model.displayName
|
||||
enabled: false
|
||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
userid: model.userid
|
||||
}
|
||||
|
@ -180,14 +180,14 @@ Control {
|
|||
visible: !!model.unicode
|
||||
}
|
||||
Avatar {
|
||||
Layout.preferredHeight: popup.avatarHeight
|
||||
Layout.preferredWidth: popup.avatarWidth
|
||||
crop: false
|
||||
displayName: model.shortcode
|
||||
enabled: false
|
||||
Layout.preferredHeight: popup.avatarHeight
|
||||
//userid: model.shortcode
|
||||
url: (model.url ? model.url : "").replace("mxc://", "image://MxcImage/")
|
||||
visible: !model.unicode
|
||||
Layout.preferredWidth: popup.avatarWidth
|
||||
}
|
||||
Label {
|
||||
Layout.leftMargin: Nheko.paddingSmall
|
||||
|
@ -227,12 +227,12 @@ Control {
|
|||
spacing: rowSpacing
|
||||
|
||||
Avatar {
|
||||
Layout.preferredHeight: popup.avatarHeight
|
||||
Layout.preferredWidth: popup.avatarWidth
|
||||
displayName: model.roomName
|
||||
enabled: false
|
||||
Layout.preferredHeight: popup.avatarHeight
|
||||
roomid: model.roomid
|
||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
Layout.preferredWidth: popup.avatarWidth
|
||||
}
|
||||
Label {
|
||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
||||
|
@ -251,12 +251,12 @@ Control {
|
|||
spacing: rowSpacing
|
||||
|
||||
Avatar {
|
||||
Layout.preferredHeight: popup.avatarHeight
|
||||
Layout.preferredWidth: popup.avatarWidth
|
||||
displayName: model.roomName
|
||||
enabled: false
|
||||
Layout.preferredHeight: popup.avatarHeight
|
||||
roomid: model.roomid
|
||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
Layout.preferredWidth: popup.avatarWidth
|
||||
}
|
||||
Label {
|
||||
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
|
||||
|
|
|
@ -55,8 +55,8 @@ Popup {
|
|||
id: replyPreview
|
||||
|
||||
eventId: mid
|
||||
userColor: TimelineManager.userColor(replyPreview.userId, palette.window)
|
||||
maxWidth: parent.width
|
||||
userColor: TimelineManager.userColor(replyPreview.userId, palette.window)
|
||||
}
|
||||
MatrixTextField {
|
||||
id: roomTextInput
|
||||
|
@ -64,7 +64,7 @@ Popup {
|
|||
color: palette.text
|
||||
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
Keys.onPressed: event => {
|
||||
if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
|
||||
event.accepted = true;
|
||||
completerPopup.up();
|
||||
|
|
|
@ -140,8 +140,8 @@ ColumnLayout {
|
|||
id: blueBar
|
||||
|
||||
Layout.fillWidth: true
|
||||
color: palette.highlight
|
||||
Layout.preferredHeight: 1
|
||||
color: palette.highlight
|
||||
|
||||
Rectangle {
|
||||
id: blackBar
|
||||
|
|
|
@ -44,14 +44,14 @@ Rectangle {
|
|||
ImageButton {
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
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.visible: hovered
|
||||
Layout.preferredHeight: 22
|
||||
hoverEnabled: true
|
||||
image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.svg" : ":/icons/icons/ui/place-call.svg"
|
||||
opacity: (CallManager.haveCallInvite || CallManager.isOnCallOnOtherDevice) ? 0.3 : 1
|
||||
visible: CallManager.callsSupported && showAllButtons
|
||||
Layout.preferredWidth: 22
|
||||
|
||||
onClicked: {
|
||||
if (room) {
|
||||
|
@ -72,13 +72,13 @@ Rectangle {
|
|||
ImageButton {
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
Layout.margins: 8
|
||||
Layout.preferredHeight: 22
|
||||
Layout.preferredWidth: 22
|
||||
ToolTip.text: qsTr("Send a file")
|
||||
ToolTip.visible: hovered
|
||||
Layout.preferredHeight: 22
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/attach.svg"
|
||||
visible: showAllButtons
|
||||
Layout.preferredWidth: 22
|
||||
|
||||
onClicked: room.input.openFileSelection()
|
||||
|
||||
|
@ -393,13 +393,13 @@ Rectangle {
|
|||
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||
Layout.margins: 8
|
||||
Layout.preferredHeight: 22
|
||||
Layout.preferredWidth: 22
|
||||
ToolTip.text: qsTr("Stickers")
|
||||
ToolTip.visible: hovered
|
||||
Layout.preferredHeight: 22
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/sticky-note-solid.svg"
|
||||
visible: showAllButtons
|
||||
Layout.preferredWidth: 22
|
||||
|
||||
onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId, function (row) {
|
||||
room.input.sticker(row);
|
||||
|
@ -417,12 +417,12 @@ Rectangle {
|
|||
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||
Layout.margins: 8
|
||||
Layout.preferredHeight: 22
|
||||
Layout.preferredWidth: 22
|
||||
ToolTip.text: qsTr("Emoji")
|
||||
ToolTip.visible: hovered
|
||||
Layout.preferredHeight: 22
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/smile.svg"
|
||||
Layout.preferredWidth: 22
|
||||
|
||||
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, room.roomId, function (plaintext, markdown) {
|
||||
messageInput.insert(messageInput.cursorPosition, markdown);
|
||||
|
@ -438,13 +438,13 @@ Rectangle {
|
|||
ImageButton {
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||
Layout.margins: 8
|
||||
Layout.preferredHeight: 22
|
||||
Layout.preferredWidth: 22
|
||||
Layout.rightMargin: 8
|
||||
ToolTip.text: qsTr("Send")
|
||||
ToolTip.visible: hovered
|
||||
Layout.preferredHeight: 22
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/send.svg"
|
||||
Layout.preferredWidth: 22
|
||||
|
||||
onClicked: {
|
||||
room.input.send();
|
||||
|
|
|
@ -16,8 +16,8 @@ Item {
|
|||
|
||||
property int availableWidth: width
|
||||
property int padding: Nheko.paddingMedium
|
||||
property string searchString: ""
|
||||
property Room roommodel: room
|
||||
property string searchString: ""
|
||||
|
||||
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
|
||||
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)
|
||||
readonly property alias filteringInProgress: filteredTimeline.filteringInProgress
|
||||
property int lastScrollPos: 0
|
||||
|
||||
ScrollBar.vertical: scrollbar
|
||||
anchors.fill: parent
|
||||
|
@ -49,6 +50,7 @@ Item {
|
|||
//onModelChanged: if (room) room.sendReset()
|
||||
//reuseItems: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
delegate: Settings.bubbles ? bubbleMessageStyle : defaultMessageStyle
|
||||
displayMarginBeginning: height / 4
|
||||
displayMarginEnd: height / 4
|
||||
model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? filteredTimeline : room
|
||||
|
@ -56,35 +58,6 @@ Item {
|
|||
spacing: 2
|
||||
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 {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.margins: Nheko.paddingLarge
|
||||
|
@ -109,7 +82,32 @@ Item {
|
|||
if (atYEnd && room)
|
||||
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 {
|
||||
id: filteredTimeline
|
||||
|
||||
|
@ -124,13 +122,13 @@ Item {
|
|||
// use comma to update on scroll
|
||||
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.right: attached?.right
|
||||
hoverEnabled: true
|
||||
padding: Nheko.paddingSmall
|
||||
parent: chat.contentItem
|
||||
visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || hovered)
|
||||
z: 10
|
||||
|
||||
background: Rectangle {
|
||||
border.color: palette.buttonText
|
||||
|
@ -200,6 +198,7 @@ Item {
|
|||
}
|
||||
}
|
||||
ImageButton {
|
||||
Layout.preferredWidth: 16
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
ToolTip.text: qsTr("Edit")
|
||||
ToolTip.visible: hovered
|
||||
|
@ -207,7 +206,6 @@ Item {
|
|||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/edit.svg"
|
||||
visible: !!row.model && row.model.isEditable
|
||||
Layout.preferredWidth: 16
|
||||
|
||||
onClicked: {
|
||||
if (row.model.isEditable)
|
||||
|
@ -217,13 +215,13 @@ Item {
|
|||
ImageButton {
|
||||
id: reactButton
|
||||
|
||||
Layout.preferredWidth: 16
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
ToolTip.text: qsTr("React")
|
||||
ToolTip.visible: hovered
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/smile-add.svg"
|
||||
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false
|
||||
Layout.preferredWidth: 16
|
||||
|
||||
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, room.roomId, function (plaintext, markdown) {
|
||||
var event_id = row.model ? row.model.eventId : "";
|
||||
|
@ -232,28 +230,29 @@ Item {
|
|||
})
|
||||
}
|
||||
ImageButton {
|
||||
Layout.preferredWidth: 16
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
ToolTip.text: (row.model && row.model.threadId) ? qsTr("Reply in thread") : qsTr("New thread")
|
||||
ToolTip.visible: hovered
|
||||
hoverEnabled: true
|
||||
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
|
||||
Layout.preferredWidth: 16
|
||||
|
||||
onClicked: room.thread = (row.model.threadId || row.model.eventId)
|
||||
}
|
||||
ImageButton {
|
||||
Layout.preferredWidth: 16
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
ToolTip.text: qsTr("Reply")
|
||||
ToolTip.visible: hovered
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/reply.svg"
|
||||
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
|
||||
Layout.preferredWidth: 16
|
||||
|
||||
onClicked: room.reply = row.model.eventId
|
||||
}
|
||||
ImageButton {
|
||||
Layout.preferredWidth: 16
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
ToolTip.text: qsTr("Go to message")
|
||||
ToolTip.visible: hovered
|
||||
|
@ -261,7 +260,6 @@ Item {
|
|||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/go-to.svg"
|
||||
visible: !!row.model && filteredTimeline.filterByContent
|
||||
Layout.preferredWidth: 16
|
||||
|
||||
onClicked: {
|
||||
topBar.searchString = "";
|
||||
|
@ -271,12 +269,12 @@ Item {
|
|||
ImageButton {
|
||||
id: optionsButton
|
||||
|
||||
Layout.preferredWidth: 16
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
ToolTip.text: qsTr("Options")
|
||||
ToolTip.visible: hovered
|
||||
hoverEnabled: true
|
||||
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)
|
||||
}
|
||||
|
@ -413,9 +411,9 @@ Item {
|
|||
Component {
|
||||
id: reportDialog
|
||||
|
||||
ReportMessage {}
|
||||
ReportMessage {
|
||||
}
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
enabled: visible
|
||||
text: qsTr("Go to &message")
|
||||
|
@ -523,10 +521,13 @@ Item {
|
|||
}
|
||||
}
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Report message")
|
||||
enabled: visible
|
||||
text: qsTr("Report message")
|
||||
|
||||
onTriggered: function () {
|
||||
var dialog = reportDialog.createObject(timelineRoot, {"eventId": messageContextMenu.eventId});
|
||||
var dialog = reportDialog.createObject(timelineRoot, {
|
||||
"eventId": messageContextMenu.eventId
|
||||
});
|
||||
dialog.show();
|
||||
dialog.forceActiveFocus();
|
||||
timelineRoot.destroyOnClose(dialog);
|
||||
|
|
|
@ -50,8 +50,8 @@ Item {
|
|||
name: "Visible"
|
||||
|
||||
PropertyChanges {
|
||||
screenSaver.visible: true
|
||||
screenSaver.opacity: 1
|
||||
screenSaver.visible: true
|
||||
}
|
||||
},
|
||||
State {
|
||||
|
|
|
@ -30,9 +30,9 @@ Rectangle {
|
|||
anchors.top: parent.top
|
||||
anchors.topMargin: Nheko.paddingSmall
|
||||
eventId: room?.reply ?? ""
|
||||
maxWidth: parent.width - anchors.leftMargin - anchors.rightMargin
|
||||
userColor: TimelineManager.userColor(modelData.userId, palette.window)
|
||||
visible: room && room.reply
|
||||
maxWidth: parent.width - anchors.leftMargin - anchors.rightMargin
|
||||
}
|
||||
ImageButton {
|
||||
id: closeReplyButton
|
||||
|
|
|
@ -26,8 +26,8 @@ Page {
|
|||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
color: Nheko.theme.separator
|
||||
Layout.preferredHeight: 1
|
||||
color: Nheko.theme.separator
|
||||
}
|
||||
Pane {
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
|
@ -45,11 +45,11 @@ Page {
|
|||
ImageButton {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
Layout.preferredHeight: 22
|
||||
Layout.preferredWidth: 22
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
ToolTip.text: qsTr("Start a new chat")
|
||||
ToolTip.visible: hovered
|
||||
Layout.preferredHeight: 22
|
||||
Layout.preferredWidth: 22
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/add-square-button.svg"
|
||||
|
||||
|
@ -97,11 +97,11 @@ Page {
|
|||
ImageButton {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
Layout.preferredHeight: 22
|
||||
Layout.preferredWidth: 22
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
ToolTip.text: qsTr("Room directory")
|
||||
ToolTip.visible: hovered
|
||||
Layout.preferredHeight: 22
|
||||
Layout.preferredWidth: 22
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/room-directory.svg"
|
||||
visible: !collapsed
|
||||
|
@ -115,11 +115,11 @@ Page {
|
|||
ImageButton {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
Layout.preferredHeight: 22
|
||||
Layout.preferredWidth: 22
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
ToolTip.text: qsTr("Search rooms (Ctrl+K)")
|
||||
ToolTip.visible: hovered
|
||||
Layout.preferredHeight: 22
|
||||
Layout.preferredWidth: 22
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/search.svg"
|
||||
ripple: false
|
||||
|
@ -139,11 +139,11 @@ Page {
|
|||
ImageButton {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
Layout.preferredHeight: 22
|
||||
Layout.preferredWidth: 22
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
ToolTip.text: qsTr("User settings")
|
||||
ToolTip.visible: hovered
|
||||
Layout.preferredHeight: 22
|
||||
Layout.preferredWidth: 22
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/settings.svg"
|
||||
ripple: false
|
||||
|
@ -268,37 +268,45 @@ Page {
|
|||
}
|
||||
Platform.MenuSeparator {
|
||||
}
|
||||
|
||||
Platform.MenuItemGroup {
|
||||
id: onlineStateGroup
|
||||
|
||||
}
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Automatic online status")
|
||||
group: onlineStateGroup
|
||||
checkable: true
|
||||
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 {
|
||||
text: qsTr("Online")
|
||||
group: onlineStateGroup
|
||||
checkable: true
|
||||
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 {
|
||||
text: qsTr("Unavailable")
|
||||
group: onlineStateGroup
|
||||
checkable: true
|
||||
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 {
|
||||
text: qsTr("Offline")
|
||||
group: onlineStateGroup
|
||||
checkable: true
|
||||
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 {
|
||||
|
@ -319,8 +327,8 @@ Page {
|
|||
}
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
color: Nheko.theme.separator
|
||||
Layout.preferredHeight: 2
|
||||
color: Nheko.theme.separator
|
||||
}
|
||||
Rectangle {
|
||||
id: unverifiedStuffBubble
|
||||
|
@ -366,14 +374,14 @@ Page {
|
|||
id: closeUnverifiedBubble
|
||||
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
Layout.preferredHeight: fontMetrics.font.pixelSize
|
||||
Layout.preferredWidth: fontMetrics.font.pixelSize
|
||||
Layout.rightMargin: Nheko.paddingMedium
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
ToolTip.text: qsTr("Close")
|
||||
ToolTip.visible: closeUnverifiedBubble.hovered
|
||||
Layout.preferredHeight: fontMetrics.font.pixelSize
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/dismiss.svg"
|
||||
Layout.preferredWidth: fontMetrics.font.pixelSize
|
||||
|
||||
onClicked: unverifiedStuffBubble.visible = false
|
||||
}
|
||||
|
@ -398,8 +406,8 @@ Page {
|
|||
}
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
color: Nheko.theme.separator
|
||||
Layout.preferredHeight: 1
|
||||
color: Nheko.theme.separator
|
||||
visible: unverifiedStuffBubble.visible
|
||||
}
|
||||
}
|
||||
|
@ -436,9 +444,9 @@ Page {
|
|||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
height: parent.height
|
||||
model: Rooms
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
//reuseItems: true
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
|
@ -550,13 +558,13 @@ Page {
|
|||
id: avatar
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredHeight: avatarSize
|
||||
Layout.preferredWidth: avatarSize
|
||||
displayName: roomName
|
||||
enabled: false
|
||||
roomid: roomId
|
||||
url: avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
userid: isDirect ? directChatOtherUserId : ""
|
||||
Layout.preferredWidth: avatarSize
|
||||
Layout.preferredHeight: avatarSize
|
||||
|
||||
NotificationBubble {
|
||||
id: collapsedNotificationBubble
|
||||
|
@ -576,8 +584,8 @@ Page {
|
|||
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.minimumWidth: 100
|
||||
Layout.preferredWidth: roomItem.width - avatar.width
|
||||
Layout.preferredHeight: avatar.height
|
||||
Layout.preferredWidth: roomItem.width - avatar.width
|
||||
spacing: Nheko.paddingSmall
|
||||
visible: !collapsed
|
||||
|
||||
|
|
|
@ -9,54 +9,53 @@ import im.nheko
|
|||
|
||||
TimelineEvent {
|
||||
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 alias hovered: messageHover.hovered
|
||||
property bool scrolledToThis: false
|
||||
|
||||
mainInset: (threadId ? (4 + Nheko.paddingSmall) : 0) + 4
|
||||
replyInset: mainInset + 4 + Nheko.paddingSmall
|
||||
|
||||
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
|
||||
replyInset: mainInset + 4 + Nheko.paddingSmall
|
||||
width: chat.delegateMaxWidth
|
||||
|
||||
data: [
|
||||
Loader {
|
||||
id: section
|
||||
|
||||
active: wrapper.previousMessageUserId !== wrapper.userId || wrapper.previousMessageDay !== wrapper.day || wrapper.previousMessageIsStateEvent !== wrapper.isStateEvent
|
||||
visible: status == Loader.Ready
|
||||
z: 4
|
||||
|
||||
//asynchronous: true
|
||||
sourceComponent: TimelineSectionHeader {
|
||||
day: wrapper.day
|
||||
|
@ -71,13 +70,12 @@ TimelineEvent {
|
|||
userName: wrapper.userName
|
||||
userPowerlevel: wrapper.userPowerlevel
|
||||
}
|
||||
visible: status == Loader.Ready
|
||||
z: 4
|
||||
},
|
||||
},
|
||||
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 threadColor: TimelineManager.userColor(wrapper.threadId, palette.base)
|
||||
|
||||
anchors.fill: gridContainer
|
||||
color: (Settings.messageHoverHighlight && messageHover.hovered) ? palette.alternateBase : threadBackgroundColor
|
||||
|
||||
// this looks better without margins
|
||||
|
@ -91,8 +89,8 @@ TimelineEvent {
|
|||
},
|
||||
Rectangle {
|
||||
id: scrollHighlight
|
||||
anchors.fill: gridContainer
|
||||
|
||||
anchors.fill: gridContainer
|
||||
color: palette.highlight
|
||||
enabled: false
|
||||
opacity: 0
|
||||
|
@ -133,37 +131,43 @@ TimelineEvent {
|
|||
Item {
|
||||
id: gridContainer
|
||||
|
||||
width: wrapper.width - wrapper.avatarMargin
|
||||
implicitHeight: messageBubble.implicitHeight
|
||||
width: wrapper.width - wrapper.avatarMargin
|
||||
x: wrapper.avatarMargin
|
||||
y: section.visible && section.active ? section.y + section.height : 0
|
||||
|
||||
HoverHandler {
|
||||
id: messageHover
|
||||
|
||||
blocking: false
|
||||
|
||||
onHoveredChanged: () => {
|
||||
if (!Settings.mobileMode && hovered) {
|
||||
if (!messageActions.hovered) {
|
||||
messageActions.model = wrapper;
|
||||
messageActions.attached = wrapper;
|
||||
messageActions.anchors.bottomMargin = -gridContainer.y
|
||||
messageActions.anchors.bottomMargin = -gridContainer.y;
|
||||
//messageActions.anchors.rightMargin = metadata.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
AbstractButton {
|
||||
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)
|
||||
|
||||
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 {
|
||||
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: 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)
|
||||
implicitWidth: Math.max((wrapper.reply?.width ?? 0) + wrapper.replyInset, (wrapper.main?.width ?? 0) + wrapper.mainInset + ((fitsMetadata && !fitsMetadataInside) ? metadata.width : 0))
|
||||
|
||||
TimelineMetadata {
|
||||
id: metadata
|
||||
|
||||
scaling: 0.75
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
visible: !wrapper.isStateEvent
|
||||
|
||||
anchors.right: parent.right
|
||||
eventId: wrapper.eventId
|
||||
status: wrapper.status
|
||||
trustlevel: wrapper.trustlevel
|
||||
isEdited: wrapper.isEdited
|
||||
isEncrypted: wrapper.isEncrypted
|
||||
room: wrapper.room
|
||||
scaling: 0.75
|
||||
status: wrapper.status
|
||||
threadId: wrapper.threadId
|
||||
timestamp: wrapper.timestamp
|
||||
room: wrapper.room
|
||||
trustlevel: wrapper.trustlevel
|
||||
visible: !wrapper.isStateEvent
|
||||
}
|
||||
|
||||
Column {
|
||||
id: contentColumn
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
data: [replyRow, wrapper.main]
|
||||
|
||||
AbstractButton {
|
||||
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)
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
clip: true
|
||||
height: replyLine.height
|
||||
visible: wrapper.reply
|
||||
|
||||
NhekoCursorShape {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
background: Rectangle {
|
||||
//width: replyRow.implicitContentWidth
|
||||
color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1))
|
||||
}
|
||||
|
||||
contentItem: Row {
|
||||
id: replyRowLay
|
||||
|
||||
|
@ -226,94 +225,81 @@ TimelineEvent {
|
|||
|
||||
Rectangle {
|
||||
id: replyLine
|
||||
height: Math.min( wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height
|
||||
|
||||
color: replyRow.userColor
|
||||
height: Math.min(wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height
|
||||
width: 4
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 0
|
||||
|
||||
id: replyCol
|
||||
|
||||
data: [replyUserButton, wrapper.reply,]
|
||||
spacing: 0
|
||||
|
||||
AbstractButton {
|
||||
id: replyUserButton
|
||||
|
||||
contentItem: Label {
|
||||
id: userName_
|
||||
text: wrapper.reply?.userName ?? ''
|
||||
|
||||
color: replyRow.userColor
|
||||
text: wrapper.reply?.userName ?? ''
|
||||
textFormat: Text.RichText
|
||||
width: wrapper.maxWidth
|
||||
//elideWidth: wrapper.maxWidth
|
||||
}
|
||||
|
||||
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: {
|
||||
let link = wrapper.reply.hoveredLink
|
||||
let link = wrapper.reply.hoveredLink;
|
||||
if (link) {
|
||||
Nheko.openLink(link)
|
||||
Nheko.openLink(link);
|
||||
} else {
|
||||
console.log("Scrolling to "+wrapper.replyTo);
|
||||
wrapper.room.showEvent(wrapper.replyTo)
|
||||
console.log("Scrolling to " + 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 {
|
||||
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
|
||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
data: [replyRow, wrapper.main]
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
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 {
|
||||
id: replyDragHandler
|
||||
yAxis.enabled: false
|
||||
|
||||
xAxis.enabled: true
|
||||
xAxis.minimum: wrapper.avatarMargin - 100
|
||||
xAxis.maximum: wrapper.avatarMargin
|
||||
xAxis.minimum: wrapper.avatarMargin - 100
|
||||
yAxis.enabled: false
|
||||
|
||||
onActiveChanged: {
|
||||
if (!replyDragHandler.active) {
|
||||
if (replyDragHandler.xAxis.minimum <= replyDragHandler.xAxis.activeValue + 1) {
|
||||
wrapper.room.reply = wrapper.eventId
|
||||
wrapper.room.reply = wrapper.eventId;
|
||||
}
|
||||
gridContainer.x = wrapper.avatarMargin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onDoubleTapped: wrapper.room.reply = wrapper.eventId
|
||||
}
|
||||
|
||||
},
|
||||
Reactions {
|
||||
id: reactionRow
|
||||
|
@ -347,4 +333,3 @@ TimelineEvent {
|
|||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -9,52 +9,52 @@ import im.nheko
|
|||
|
||||
TimelineEvent {
|
||||
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
|
||||
|
||||
//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)
|
||||
replyInset: mainInset + 4 + Nheko.paddingSmall
|
||||
|
||||
maxWidth: chat.delegateMaxWidth - avatarMargin - metadata.width
|
||||
replyInset: mainInset + 4 + Nheko.paddingSmall
|
||||
width: chat.delegateMaxWidth
|
||||
|
||||
data: [
|
||||
Loader {
|
||||
id: section
|
||||
|
||||
active: wrapper.previousMessageUserId !== wrapper.userId || wrapper.previousMessageDay !== wrapper.day || wrapper.previousMessageIsStateEvent !== wrapper.isStateEvent
|
||||
visible: status == Loader.Ready
|
||||
z: 4
|
||||
|
||||
//asynchronous: true
|
||||
sourceComponent: TimelineSectionHeader {
|
||||
day: wrapper.day
|
||||
|
@ -69,9 +69,7 @@ TimelineEvent {
|
|||
userName: wrapper.userName
|
||||
userPowerlevel: wrapper.userPowerlevel
|
||||
}
|
||||
visible: status == Loader.Ready
|
||||
z: 4
|
||||
},
|
||||
},
|
||||
Rectangle {
|
||||
anchors.fill: gridContainer
|
||||
color: (Settings.messageHoverHighlight && messageHover.hovered) ? palette.alternateBase : "transparent"
|
||||
|
@ -87,8 +85,8 @@ TimelineEvent {
|
|||
},
|
||||
Rectangle {
|
||||
id: scrollHighlight
|
||||
anchors.fill: gridContainer
|
||||
|
||||
anchors.fill: gridContainer
|
||||
color: palette.highlight
|
||||
enabled: false
|
||||
opacity: 0
|
||||
|
@ -127,41 +125,41 @@ TimelineEvent {
|
|||
}
|
||||
},
|
||||
Rectangle {
|
||||
anchors.top: gridContainer.top
|
||||
anchors.left: gridContainer.left
|
||||
anchors.topMargin: -2
|
||||
anchors.left: gridContainer.left
|
||||
anchors.leftMargin: -2
|
||||
color: "transparent"
|
||||
anchors.top: gridContainer.top
|
||||
anchors.topMargin: -2
|
||||
border.color: Nheko.theme.red
|
||||
border.width: wrapper.notificationlevel == MtxEvent.Highlight ? 1 : 0
|
||||
radius: 4
|
||||
color: "transparent"
|
||||
height: contentColumn.implicitHeight + 4
|
||||
radius: 4
|
||||
width: contentColumn.implicitWidth + 4
|
||||
},
|
||||
Row {
|
||||
id: gridContainer
|
||||
|
||||
spacing: Nheko.paddingSmall
|
||||
width: wrapper.width - wrapper.avatarMargin
|
||||
x: wrapper.avatarMargin
|
||||
y: section.visible && section.active ? section.y + section.height : 0
|
||||
spacing: Nheko.paddingSmall
|
||||
|
||||
HoverHandler {
|
||||
id: messageHover
|
||||
|
||||
blocking: false
|
||||
|
||||
onHoveredChanged: () => {
|
||||
if (!Settings.mobileMode && hovered) {
|
||||
if (!messageActions.hovered) {
|
||||
messageActions.model = wrapper;
|
||||
messageActions.attached = wrapper;
|
||||
messageActions.anchors.bottomMargin = -gridContainer.y
|
||||
messageActions.anchors.rightMargin = metadata.width
|
||||
messageActions.anchors.bottomMargin = -gridContainer.y;
|
||||
messageActions.anchors.rightMargin = metadata.width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AbstractButton {
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
ToolTip.text: qsTr("Part of a thread")
|
||||
|
@ -179,31 +177,29 @@ TimelineEvent {
|
|||
color: TimelineManager.userColor(wrapper.threadId, palette.base)
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
height: 1
|
||||
visible: wrapper.isStateEvent
|
||||
width: (wrapper.maxWidth - (wrapper.main?.width ?? 0)) / 2
|
||||
height: 1
|
||||
}
|
||||
|
||||
Column {
|
||||
id: contentColumn
|
||||
|
||||
data: [replyRow, wrapper.main,]
|
||||
|
||||
AbstractButton {
|
||||
id: replyRow
|
||||
visible: wrapper.reply
|
||||
|
||||
height: replyLine.height
|
||||
|
||||
property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base)
|
||||
|
||||
clip: true
|
||||
height: replyLine.height
|
||||
visible: wrapper.reply
|
||||
|
||||
NhekoCursorShape {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
background: Rectangle {
|
||||
//width: replyRow.implicitContentWidth
|
||||
color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1))
|
||||
}
|
||||
|
||||
contentItem: Row {
|
||||
id: replyRowLay
|
||||
|
||||
|
@ -211,103 +207,96 @@ TimelineEvent {
|
|||
|
||||
Rectangle {
|
||||
id: replyLine
|
||||
height: Math.min( wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height
|
||||
|
||||
color: replyRow.userColor
|
||||
height: Math.min(wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height
|
||||
width: 4
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 0
|
||||
|
||||
id: replyCol
|
||||
|
||||
data: [replyUserButton, wrapper.reply,]
|
||||
spacing: 0
|
||||
|
||||
AbstractButton {
|
||||
id: replyUserButton
|
||||
|
||||
contentItem: Label {
|
||||
id: userName_
|
||||
text: wrapper.reply?.userName ?? ''
|
||||
|
||||
color: replyRow.userColor
|
||||
text: wrapper.reply?.userName ?? ''
|
||||
textFormat: Text.RichText
|
||||
width: wrapper.maxWidth
|
||||
//elideWidth: wrapper.maxWidth
|
||||
}
|
||||
|
||||
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: {
|
||||
let link = wrapper.reply.hoveredLink
|
||||
let link = wrapper.reply.hoveredLink;
|
||||
if (link) {
|
||||
Nheko.openLink(link)
|
||||
Nheko.openLink(link);
|
||||
} else {
|
||||
console.log("Scrolling to "+wrapper.replyTo);
|
||||
wrapper.room.showEvent(wrapper.replyTo)
|
||||
console.log("Scrolling to " + 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 {
|
||||
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
|
||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
data: [
|
||||
replyRow, wrapper.main,
|
||||
]
|
||||
}
|
||||
|
||||
DragHandler {
|
||||
id: replyDragHandler
|
||||
yAxis.enabled: false
|
||||
|
||||
xAxis.enabled: true
|
||||
xAxis.minimum: wrapper.avatarMargin - 100
|
||||
xAxis.maximum: wrapper.avatarMargin
|
||||
xAxis.minimum: wrapper.avatarMargin - 100
|
||||
yAxis.enabled: false
|
||||
|
||||
onActiveChanged: {
|
||||
if (!replyDragHandler.active) {
|
||||
if (replyDragHandler.xAxis.minimum <= replyDragHandler.xAxis.activeValue + 1) {
|
||||
wrapper.room.reply = wrapper.eventId
|
||||
wrapper.room.reply = wrapper.eventId;
|
||||
}
|
||||
gridContainer.x = wrapper.avatarMargin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onDoubleTapped: wrapper.room.reply = wrapper.eventId
|
||||
}
|
||||
},
|
||||
TimelineMetadata {
|
||||
id: metadata
|
||||
TimelineMetadata {
|
||||
id: metadata
|
||||
|
||||
scaling: 1
|
||||
|
||||
anchors.right: parent.right
|
||||
y: section.visible && section.active ? section.y + section.height : 0
|
||||
|
||||
visible: !wrapper.isStateEvent
|
||||
|
||||
eventId: wrapper.eventId
|
||||
status: wrapper.status
|
||||
trustlevel: wrapper.trustlevel
|
||||
isEdited: wrapper.isEdited
|
||||
isEncrypted: wrapper.isEncrypted
|
||||
threadId: wrapper.threadId
|
||||
timestamp: wrapper.timestamp
|
||||
room: wrapper.room
|
||||
},
|
||||
anchors.right: parent.right
|
||||
eventId: wrapper.eventId
|
||||
isEdited: wrapper.isEdited
|
||||
isEncrypted: wrapper.isEncrypted
|
||||
room: wrapper.room
|
||||
scaling: 1
|
||||
status: wrapper.status
|
||||
threadId: wrapper.threadId
|
||||
timestamp: wrapper.timestamp
|
||||
trustlevel: wrapper.trustlevel
|
||||
visible: !wrapper.isStateEvent
|
||||
y: section.visible && section.active ? section.y + section.height : 0
|
||||
},
|
||||
Reactions {
|
||||
id: reactionRow
|
||||
|
||||
|
|
|
@ -11,17 +11,16 @@ import im.nheko
|
|||
RowLayout {
|
||||
id: metadata
|
||||
|
||||
property int iconSize: Math.floor(fontMetrics.ascent * scaling)
|
||||
required property double scaling
|
||||
|
||||
required property string eventId
|
||||
required property int status
|
||||
required property int trustlevel
|
||||
property int iconSize: Math.floor(fontMetrics.ascent * scaling)
|
||||
required property bool isEdited
|
||||
required property bool isEncrypted
|
||||
required property Room room
|
||||
required property double scaling
|
||||
required property int status
|
||||
required property string threadId
|
||||
required property date timestamp
|
||||
required property Room room
|
||||
required property int trustlevel
|
||||
|
||||
spacing: 2
|
||||
|
||||
|
@ -43,6 +42,7 @@ RowLayout {
|
|||
sourceSize.height: parent.iconSize * Screen.devicePixelRatio
|
||||
sourceSize.width: parent.iconSize * Screen.devicePixelRatio
|
||||
visible: metadata.isEdited || metadata.eventId == metadata.room.edit
|
||||
|
||||
HoverHandler {
|
||||
id: editHovered
|
||||
|
||||
|
|
|
@ -6,11 +6,9 @@ import QtQuick
|
|||
import QtQuick.Controls
|
||||
import QtQuick.Window
|
||||
import im.nheko
|
||||
|
||||
import "./components"
|
||||
|
||||
Column {
|
||||
|
||||
required property var day
|
||||
required property bool isSender
|
||||
required property bool isStateEvent
|
||||
|
@ -79,31 +77,14 @@ Column {
|
|||
|
||||
target: room
|
||||
}
|
||||
|
||||
AbstractButton {
|
||||
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.text: userId
|
||||
ToolTip.visible: hovered
|
||||
leftPadding: powerlevelIndicator.visible ? 16 : 0
|
||||
leftInset: 0
|
||||
leftPadding: powerlevelIndicator.visible ? 16 : 0
|
||||
rightInset: 0
|
||||
rightPadding: 0
|
||||
|
||||
|
@ -117,6 +98,19 @@ Column {
|
|||
|
||||
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 {
|
||||
id: userNameTextMetrics
|
||||
|
||||
|
@ -153,7 +147,7 @@ Column {
|
|||
Connections {
|
||||
function onPresenceChanged(id) {
|
||||
if (id == userId)
|
||||
statusMsg.userStatus = Presence.userStatus(userId);
|
||||
statusMsg.userStatus = Presence.userStatus(userId);
|
||||
}
|
||||
|
||||
target: Presence
|
||||
|
@ -161,4 +155,3 @@ Column {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ Item {
|
|||
visible: TimelineManager.isInitialSync
|
||||
z: 3
|
||||
|
||||
Behavior on opacity {
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
}
|
||||
|
@ -188,9 +188,9 @@ Item {
|
|||
displayName: parent.roomName
|
||||
enabled: false
|
||||
implicitHeight: 130
|
||||
implicitWidth: 130
|
||||
roomid: parent.roomId
|
||||
url: parent.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
implicitWidth: 130
|
||||
}
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
@ -293,10 +293,10 @@ Item {
|
|||
displayName: roomPreview?.inviterDisplayName ?? ""
|
||||
enabled: true
|
||||
implicitHeight: 48
|
||||
implicitWidth: 48
|
||||
roomid: preview.roomId
|
||||
url: (roomPreview?.inviterAvatarUrl ?? "").replace("mxc://", "image://MxcImage/")
|
||||
userid: roomPreview?.inviterUserId ?? ""
|
||||
implicitWidth: 48
|
||||
|
||||
onClicked: TimelineManager.openGlobalUserProfile(roomPreview.inviterUserId)
|
||||
}
|
||||
|
@ -378,8 +378,8 @@ Item {
|
|||
running: false
|
||||
|
||||
onTriggered: {
|
||||
timelineEffects.removeParticles()
|
||||
shouldEffectsRun = false
|
||||
timelineEffects.removeParticles();
|
||||
shouldEffectsRun = false;
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
|
|
|
@ -124,9 +124,9 @@ Pane {
|
|||
Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
|
||||
Layout.row: 2
|
||||
clip: true
|
||||
enabled: false
|
||||
// don't use the disabled color
|
||||
color: topBar.palette.text
|
||||
enabled: false
|
||||
selectByMouse: false
|
||||
text: roomTopic
|
||||
}
|
||||
|
@ -287,9 +287,9 @@ Pane {
|
|||
|
||||
property var e: room ? room.getDump(modelData, "pins") : {}
|
||||
|
||||
maxWidth: pinnedMessages.width
|
||||
//Layout.preferredHeight: height
|
||||
eventId: e.eventId ?? ""
|
||||
maxWidth: pinnedMessages.width
|
||||
userColor: TimelineManager.userColor(e.userId, palette.window)
|
||||
|
||||
Connections {
|
||||
|
|
|
@ -13,64 +13,16 @@ Container {
|
|||
|
||||
id: container
|
||||
|
||||
property bool singlePageMode: width < 800
|
||||
property int splitterGrabMargin: Nheko.paddingSmall
|
||||
property alias pageIndex: view.currentIndex
|
||||
property Component handle
|
||||
property Component handleToucharea
|
||||
|
||||
onSinglePageModeChanged: if (!singlePageMode) pageIndex = 0
|
||||
|
||||
Component.onCompleted: {
|
||||
for (var i = 0; i < count - 1; i++) {
|
||||
let handle_ = handle.createObject(contentChildren[i]);
|
||||
let split_ = handleToucharea.createObject(contentChildren[i]);
|
||||
contentChildren[i].width = Qt.binding(function() {
|
||||
return split_.calculatedWidth;
|
||||
});
|
||||
contentChildren[i].splitterWidth = Qt.binding(function() {
|
||||
return handle_.width;
|
||||
});
|
||||
}
|
||||
contentChildren[count - 1].width = Qt.binding(function() {
|
||||
if (container.singlePageMode) {
|
||||
return container.width;
|
||||
} else {
|
||||
var w = container.width;
|
||||
for (var i = 0; i < count - 1; i++) {
|
||||
if (contentChildren[i].width)
|
||||
w = w - contentChildren[i].width;
|
||||
|
||||
}
|
||||
return w;
|
||||
}
|
||||
});
|
||||
contentChildren[count - 1].splitterWidth = 0;
|
||||
for (var i = 0; i < count; i++) {
|
||||
contentChildren[i].height = Qt.binding(function() {
|
||||
return container.height;
|
||||
});
|
||||
contentChildren[i].children[0].height = Qt.binding(function() {
|
||||
return container.height;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handle: Rectangle {
|
||||
z: 3
|
||||
property Component handle: Rectangle {
|
||||
anchors.right: parent.right
|
||||
color: Nheko.theme.separator
|
||||
height: container.height
|
||||
width: visible ? 1 : 0
|
||||
anchors.right: parent.right
|
||||
z: 3
|
||||
}
|
||||
|
||||
handleToucharea: Item {
|
||||
property Component 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;
|
||||
|
@ -79,6 +31,10 @@ Container {
|
|||
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
|
||||
|
@ -87,49 +43,84 @@ Container {
|
|||
z: 3
|
||||
|
||||
NhekoCursorShape {
|
||||
cursorShape: Qt.SizeHorCursor
|
||||
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
|
||||
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 int splitterGrabMargin: Nheko.paddingSmall
|
||||
|
||||
contentItem: ListView {
|
||||
id: view
|
||||
|
||||
model: container.contentModel
|
||||
snapMode: ListView.SnapOneItem
|
||||
orientation: ListView.Horizontal
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
currentIndex: container.singlePageMode ? container.pageIndex : 0
|
||||
highlightMoveDuration: (container.singlePageMode && !Settings.reducedMotion) ? 200 : 0
|
||||
highlightRangeMode: ListView.StrictlyEnforceRange
|
||||
interactive: singlePageMode
|
||||
highlightMoveDuration: (container.singlePageMode && !Settings.reducedMotion) ? 200 : 0
|
||||
currentIndex: container.singlePageMode ? container.pageIndex : 0
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
model: container.contentModel
|
||||
orientation: ListView.Horizontal
|
||||
snapMode: ListView.SnapOneItem
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
for (var i = 0; i < count - 1; i++) {
|
||||
let handle_ = handle.createObject(contentChildren[i]);
|
||||
let split_ = handleToucharea.createObject(contentChildren[i]);
|
||||
contentChildren[i].width = Qt.binding(function () {
|
||||
return split_.calculatedWidth;
|
||||
});
|
||||
contentChildren[i].splitterWidth = Qt.binding(function () {
|
||||
return handle_.width;
|
||||
});
|
||||
}
|
||||
contentChildren[count - 1].width = Qt.binding(function () {
|
||||
if (container.singlePageMode) {
|
||||
return container.width;
|
||||
} else {
|
||||
var w = container.width;
|
||||
for (var i = 0; i < count - 1; i++) {
|
||||
if (contentChildren[i].width)
|
||||
w = w - contentChildren[i].width;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
});
|
||||
contentChildren[count - 1].splitterWidth = 0;
|
||||
for (var i = 0; i < count; i++) {
|
||||
contentChildren[i].height = Qt.binding(function () {
|
||||
return container.height;
|
||||
});
|
||||
contentChildren[i].children[0].height = Qt.binding(function () {
|
||||
return container.height;
|
||||
});
|
||||
}
|
||||
}
|
||||
onSinglePageModeChanged: if (!singlePageMode)
|
||||
pageIndex = 0
|
||||
}
|
||||
|
|
|
@ -5,20 +5,20 @@
|
|||
import QtQuick
|
||||
|
||||
Item {
|
||||
property int minimumWidth: 100
|
||||
property int maximumWidth: 400
|
||||
property bool collapsed: width < minimumWidth
|
||||
property int collapsedWidth: 40
|
||||
property bool collapsible: true
|
||||
property bool collapsed: width < minimumWidth
|
||||
property int splitterWidth: 1
|
||||
property int maximumWidth: 400
|
||||
property int minimumWidth: 100
|
||||
property int preferredWidth: 100
|
||||
property int splitterWidth: 1
|
||||
|
||||
Component.onCompleted: {
|
||||
children[0].width = Qt.binding(() => {
|
||||
return parent.singlePageMode ? parent.width : width - splitterWidth;
|
||||
});
|
||||
return parent.singlePageMode ? parent.width : width - splitterWidth;
|
||||
});
|
||||
children[0].height = Qt.binding(() => {
|
||||
return parent.height;
|
||||
});
|
||||
return parent.height;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,25 +10,26 @@ import im.nheko
|
|||
Rectangle {
|
||||
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)
|
||||
required property string avatarUrl
|
||||
required property string title
|
||||
required property string subtitle
|
||||
required property int index
|
||||
required property int selectedIndex
|
||||
property color background: palette.window
|
||||
property color bubbleBackground: palette.highlight
|
||||
property color bubbleText: palette.highlightedText
|
||||
property bool crop: true
|
||||
property color importantText: palette.text
|
||||
required property int index
|
||||
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
|
||||
|
||||
color: background
|
||||
height: avatarSize + 2 * Nheko.paddingMedium
|
||||
width: ListView.view.width
|
||||
state: "normal"
|
||||
width: ListView.view.width
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "highlight"
|
||||
|
@ -37,13 +38,12 @@ Rectangle {
|
|||
PropertyChanges {
|
||||
tile {
|
||||
background: palette.dark
|
||||
importantText: palette.brightText
|
||||
unimportantText: palette.brightText
|
||||
bubbleBackground: palette.highlight
|
||||
bubbleText: palette.highlightedText
|
||||
importantText: palette.brightText
|
||||
unimportantText: palette.brightText
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "selected"
|
||||
|
@ -52,37 +52,35 @@ Rectangle {
|
|||
PropertyChanges {
|
||||
tile {
|
||||
background: palette.highlight
|
||||
importantText: palette.highlightedText
|
||||
unimportantText: palette.highlightedText
|
||||
bubbleBackground: palette.highlightedText
|
||||
bubbleText: palette.highlight
|
||||
importantText: palette.highlightedText
|
||||
unimportantText: palette.highlightedText
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
HoverHandler {
|
||||
id: hovered
|
||||
}
|
||||
|
||||
}
|
||||
RowLayout {
|
||||
spacing: Nheko.paddingMedium
|
||||
anchors.fill: parent
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
Avatar {
|
||||
id: avatar
|
||||
|
||||
enabled: false
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
crop: tile.crop
|
||||
displayName: title
|
||||
enabled: false
|
||||
implicitHeight: avatarSize
|
||||
implicitWidth: avatarSize
|
||||
url: tile.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
displayName: title
|
||||
crop: tile.crop
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: textContent
|
||||
|
||||
|
@ -103,33 +101,25 @@ Rectangle {
|
|||
fullText: title
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
ElidedLabel {
|
||||
color: tile.unimportantText
|
||||
font.pixelSize: fontMetrics.font.pixelSize * 0.9
|
||||
elideWidth: textContent.width - Nheko.paddingSmall
|
||||
font.pixelSize: fontMetrics.font.pixelSize * 0.9
|
||||
fullText: subtitle
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,53 +12,51 @@ import im.nheko
|
|||
Button {
|
||||
id: control
|
||||
|
||||
implicitHeight: Math.ceil(control.contentItem.implicitHeight * 1.70)
|
||||
implicitWidth: Math.ceil(control.contentItem.implicitWidth + control.contentItem.implicitHeight)
|
||||
hoverEnabled: true
|
||||
|
||||
property string iconImage: ""
|
||||
|
||||
MultiEffect {
|
||||
anchors.fill: control.background
|
||||
shadowHorizontalOffset: 3
|
||||
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
|
||||
}
|
||||
}
|
||||
hoverEnabled: true
|
||||
implicitHeight: Math.ceil(control.contentItem.implicitHeight * 1.70)
|
||||
implicitWidth: Math.ceil(control.contentItem.implicitWidth + control.contentItem.implicitHeight)
|
||||
|
||||
background: Rectangle {
|
||||
color: Qt.lighter(palette.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1))
|
||||
//height: control.contentItem.implicitHeight * 2
|
||||
//width: control.contentItem.implicitWidth * 2
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,30 +10,29 @@ Dialog {
|
|||
default property alias inner: scroll.data
|
||||
property int useableWidth: scroll.width - scroll.ScrollBar.vertical.width
|
||||
|
||||
parent: Overlay.overlay
|
||||
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
|
||||
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: [
|
||||
ScrollView {
|
||||
id: scroll
|
||||
|
||||
clip: true
|
||||
anchors.fill: parent
|
||||
ScrollBar.horizontal.visible: false
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,21 +9,19 @@ import im.nheko 1.0
|
|||
TabButton {
|
||||
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 {
|
||||
border.color: control.down ? palette.highlight : Nheko.theme.separator
|
||||
color: control.checked ? palette.highlight : palette.base
|
||||
border.width: 1
|
||||
color: control.checked ? palette.highlight : palette.base
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,39 +9,38 @@ import im.nheko 1.0
|
|||
Rectangle {
|
||||
id: bubbleRoot
|
||||
|
||||
required property int notificationCount
|
||||
required property bool hasLoudNotification
|
||||
required property color bubbleBackgroundColor
|
||||
required property color bubbleTextColor
|
||||
property bool mayBeVisible: true
|
||||
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
|
||||
implicitWidth: Math.max(notificationBubbleText.width, height)
|
||||
radius: height / 2
|
||||
color: hasLoudNotification ? Nheko.theme.red : bubbleBackgroundColor
|
||||
ToolTip.text: notificationCount
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
ToolTip.visible: notificationBubbleHover.hovered && (notificationCount > 9999)
|
||||
visible: mayBeVisible && notificationCount > 0
|
||||
|
||||
Label {
|
||||
id: notificationBubbleText
|
||||
|
||||
anchors.centerIn: bubbleRoot
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
width: Math.max(implicitWidth + Nheko.paddingMedium, bubbleRoot.height)
|
||||
color: bubbleRoot.hasLoudNotification ? "white" : bubbleRoot.bubbleTextColor
|
||||
font.bold: true
|
||||
font.pixelSize: fontMetrics.font.pixelSize * 0.8
|
||||
color: bubbleRoot.hasLoudNotification ? "white" : bubbleRoot.bubbleTextColor
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: bubbleRoot.notificationCount > 9999 ? "9999+" : bubbleRoot.notificationCount
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
width: Math.max(implicitWidth + Nheko.paddingMedium, bubbleRoot.height)
|
||||
|
||||
HoverHandler {
|
||||
id: notificationBubbleHover
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,24 +7,20 @@ import QtQuick.Controls
|
|||
import im.nheko
|
||||
|
||||
Image {
|
||||
required property int powerlevel
|
||||
required property var permissions
|
||||
|
||||
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 isModerator: permissions ? permissions.redactLevel() <= powerlevel : false
|
||||
required property var permissions
|
||||
required property int powerlevel
|
||||
readonly property string sourceUrl: {
|
||||
if (isAdmin)
|
||||
return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?";
|
||||
return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?";
|
||||
else if (isModerator)
|
||||
return "image://colorimage/:/icons/icons/ui/ribbon.svg?";
|
||||
else
|
||||
return "image://colorimage/:/icons/icons/ui/person.svg?";
|
||||
}
|
||||
|
||||
source: sourceUrl + (ma.hovered ? palette.highlight : palette.buttonText)
|
||||
ToolTip.visible: ma.hovered
|
||||
ToolTip.text: {
|
||||
if (isAdmin)
|
||||
return qsTr("Administrator: %1").arg(powerlevel);
|
||||
|
@ -33,8 +29,11 @@ Image {
|
|||
else
|
||||
return qsTr("User: %1").arg(powerlevel);
|
||||
}
|
||||
ToolTip.visible: ma.hovered
|
||||
source: sourceUrl + (ma.hovered ? palette.highlight : palette.buttonText)
|
||||
|
||||
HoverHandler {
|
||||
id: ma
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ import QtQml.Models
|
|||
Item {
|
||||
id: root
|
||||
|
||||
property alias model: visualModel.model
|
||||
property Component delegate
|
||||
property alias model: visualModel.model
|
||||
|
||||
Component {
|
||||
id: dragDelegate
|
||||
|
@ -17,104 +17,117 @@ Item {
|
|||
MouseArea {
|
||||
id: dragArea
|
||||
|
||||
required property var model
|
||||
required property int index
|
||||
|
||||
enabled: model.moveable == undefined || model.moveable
|
||||
|
||||
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
|
||||
|
||||
drag.target: held ? content : undefined
|
||||
drag.axis: Drag.YAxis
|
||||
|
||||
onHeldChanged: if (held)
|
||||
ListView.view.currentIndex = dragArea.index
|
||||
else
|
||||
ListView.view.currentIndex = -1
|
||||
onPressAndHold: held = true
|
||||
onPressed: if (mouse.source !== Qt.MouseEventNotSynthesized) { held = true }
|
||||
onPressed: if (mouse.source !== Qt.MouseEventNotSynthesized) {
|
||||
held = true;
|
||||
}
|
||||
onReleased: held = false
|
||||
onHeldChanged: if (held) ListView.view.currentIndex = dragArea.index; else ListView.view.currentIndex = -1
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
Rectangle {
|
||||
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 {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
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 {
|
||||
id: actualDelegate
|
||||
sourceComponent: root.delegate
|
||||
property var model: dragArea.model
|
||||
|
||||
property int index: dragArea.index
|
||||
property var model: dragArea.model
|
||||
property int offset: -view.contentY + dragArea.y
|
||||
anchors { fill: parent; margins: 2 }
|
||||
}
|
||||
|
||||
}
|
||||
sourceComponent: root.delegate
|
||||
|
||||
DropArea {
|
||||
enabled: index != 0 || model.moveable == undefined || model.moveable
|
||||
anchors { fill: parent; margins: 8 }
|
||||
|
||||
onEntered: (drag)=> {
|
||||
visualModel.model.move(drag.source.index, dragArea.index)
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
DropArea {
|
||||
enabled: index != 0 || model.moveable == undefined || model.moveable
|
||||
|
||||
onEntered: drag => {
|
||||
visualModel.model.move(drag.source.index, dragArea.index);
|
||||
}
|
||||
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DelegateModel {
|
||||
id: visualModel
|
||||
|
||||
delegate: dragDelegate
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: view
|
||||
|
||||
clip: true
|
||||
|
||||
anchors { fill: parent; margins: 2 }
|
||||
|
||||
model: visualModel
|
||||
|
||||
highlightRangeMode: ListView.ApplyRange
|
||||
preferredHighlightBegin: 0.2 * height
|
||||
preferredHighlightEnd: 0.8 * height
|
||||
|
||||
spacing: 4
|
||||
cacheBuffer: 50
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
DelegateModel {
|
||||
id: visualModel
|
||||
|
||||
delegate: dragDelegate
|
||||
}
|
||||
ListView {
|
||||
id: view
|
||||
|
||||
cacheBuffer: 50
|
||||
clip: true
|
||||
highlightRangeMode: ListView.ApplyRange
|
||||
model: visualModel
|
||||
preferredHighlightBegin: 0.2 * height
|
||||
preferredHighlightEnd: 0.8 * height
|
||||
spacing: 4
|
||||
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,76 +9,87 @@ import im.nheko 1.0
|
|||
Platform.Menu {
|
||||
id: spacesMenu
|
||||
|
||||
property string roomid
|
||||
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 int position: modelData == undefined ? -2 : modelData.treeIndex
|
||||
property string roomid
|
||||
|
||||
title: modelData != undefined ? modelData.name : qsTr("Add or remove from community")
|
||||
|
||||
onAboutToShow: loadChildren = true
|
||||
|
||||
//onAboutToHide: loadChildren = false
|
||||
|
||||
Platform.MenuItemGroup {
|
||||
id: modificationGroup
|
||||
|
||||
visible: position != -1
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Official community for this room")
|
||||
group: modificationGroup
|
||||
checkable: true
|
||||
checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && modelData.canonical)
|
||||
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 {
|
||||
text: qsTr("Affiliated community for this room")
|
||||
group: modificationGroup
|
||||
checkable: true
|
||||
checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && !modelData.canonical)
|
||||
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 {
|
||||
text: qsTr("Listed only for community members")
|
||||
group: modificationGroup
|
||||
checkable: true
|
||||
checked: spacesMenu.position >= 0 && (modelData.childValid && !modelData.parentValid)
|
||||
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 {
|
||||
text: qsTr("Listed only for room members")
|
||||
group: modificationGroup
|
||||
checkable: true
|
||||
checked: spacesMenu.position >= 0 && (!modelData.childValid && modelData.parentValid)
|
||||
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 {
|
||||
text: qsTr("Not related")
|
||||
group: modificationGroup
|
||||
checkable: true
|
||||
checked: spacesMenu.position >= 0 && (!modelData.childValid && !modelData.parentValid)
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
Instantiator {
|
||||
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)
|
||||
|
||||
delegate: childMenu
|
||||
model: spacesMenu.loadChildren ? Communities.spaceChildrenListFromIndex(spacesMenu.roomid, spacesMenu.position) : undefined
|
||||
|
||||
onObjectAdded: (idx, o) => {
|
||||
spacesMenu.insertMenu(idx + (spacesMenu.position != -1 ? 6 : 0), o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,37 +10,34 @@ import im.nheko 1.0 // for cursor shape
|
|||
AbstractButton {
|
||||
id: button
|
||||
|
||||
property color buttonTextColor: palette.buttonText
|
||||
property alias cursor: mouseArea.cursorShape
|
||||
property color highlightColor: palette.highlight
|
||||
property color buttonTextColor: palette.buttonText
|
||||
|
||||
focusPolicy: Qt.NoFocus
|
||||
width: buttonText.implicitWidth
|
||||
height: buttonText.implicitHeight
|
||||
implicitWidth: buttonText.implicitWidth
|
||||
implicitHeight: buttonText.implicitHeight
|
||||
implicitWidth: buttonText.implicitWidth
|
||||
width: buttonText.implicitWidth
|
||||
|
||||
Label {
|
||||
id: buttonText
|
||||
|
||||
anchors.centerIn: parent
|
||||
padding: 0
|
||||
text: button.text
|
||||
color: button.hovered ? highlightColor : buttonTextColor
|
||||
font: button.font
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
padding: 0
|
||||
text: button.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
NhekoCursorShape {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
Ripple {
|
||||
color: Qt.rgba(buttonTextColor.r, buttonTextColor.g, buttonTextColor.b, 0.5)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,43 +9,50 @@ import QtQuick.Layouts 1.12
|
|||
import im.nheko 1.0
|
||||
|
||||
ItemDelegate {
|
||||
property alias bgColor: background.color
|
||||
property alias userid: avatar.userid
|
||||
property alias displayName: avatar.displayName
|
||||
property string avatarUrl
|
||||
property alias bgColor: background.color
|
||||
property alias displayName: avatar.displayName
|
||||
property alias userid: avatar.userid
|
||||
|
||||
implicitHeight: layout.implicitHeight + Nheko.paddingSmall * 2
|
||||
background: Rectangle {id: background}
|
||||
|
||||
background: Rectangle {
|
||||
id: background
|
||||
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: layout
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - Nheko.paddingSmall * 2
|
||||
rows: 2
|
||||
columnSpacing: Nheko.paddingMedium
|
||||
columns: 2
|
||||
rowSpacing: Nheko.paddingSmall
|
||||
columnSpacing: Nheko.paddingMedium
|
||||
rows: 2
|
||||
width: parent.width - Nheko.paddingSmall * 2
|
||||
|
||||
Avatar {
|
||||
id: avatar
|
||||
Layout.rowSpan: 2
|
||||
Layout.preferredWidth: Nheko.avatarSize
|
||||
Layout.preferredHeight: Nheko.avatarSize
|
||||
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
url: avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
Layout.preferredHeight: Nheko.avatarSize
|
||||
Layout.preferredWidth: Nheko.avatarSize
|
||||
Layout.rowSpan: 2
|
||||
enabled: false
|
||||
url: avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: displayName
|
||||
color: TimelineManager.userColor(userid, palette.window)
|
||||
font.pointSize: fontMetrics.font.pointSize
|
||||
text: displayName
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignTop
|
||||
text: userid
|
||||
Layout.fillWidth: true
|
||||
color: palette.buttonText
|
||||
font.pointSize: fontMetrics.font.pointSize * 0.9
|
||||
text: userid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,29 +13,37 @@ Control {
|
|||
required property int encryptionError
|
||||
required property string eventId
|
||||
|
||||
padding: Nheko.paddingMedium
|
||||
implicitHeight: contents.implicitHeight + Nheko.paddingMedium * 2
|
||||
Layout.maximumWidth: contents.Layout.maximumWidth + padding * 2
|
||||
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 {
|
||||
id: contents
|
||||
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
Image {
|
||||
source: "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?" + Nheko.theme.error
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: 24
|
||||
Layout.preferredHeight: 24
|
||||
Layout.preferredWidth: 24
|
||||
source: "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?" + Nheko.theme.error
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Nheko.paddingSmall
|
||||
Layout.fillWidth: true
|
||||
spacing: Nheko.paddingSmall
|
||||
|
||||
Label {
|
||||
id: encryptedText
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: implicitWidth + 1
|
||||
color: palette.text
|
||||
text: {
|
||||
switch (r.encryptionError) {
|
||||
case Olm.MissingSession:
|
||||
|
@ -56,24 +64,13 @@ Control {
|
|||
}
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Label.WordWrap
|
||||
color: palette.text
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: implicitWidth + 1
|
||||
}
|
||||
|
||||
Button {
|
||||
visible: r.encryptionError == Olm.MissingSession || encryptionError == Olm.MissingSessionIndex
|
||||
text: qsTr("Request key")
|
||||
visible: r.encryptionError == Olm.MissingSession || encryptionError == Olm.MissingSessionIndex
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,53 +13,48 @@ Control {
|
|||
|
||||
required property string userName
|
||||
|
||||
padding: Nheko.paddingMedium
|
||||
Layout.fillWidth: true
|
||||
//implicitHeight: contents.implicitHeight + padd * 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 {
|
||||
id: contents
|
||||
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
Image {
|
||||
source: "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: 24
|
||||
Layout.preferredHeight: 24
|
||||
Layout.preferredWidth: 24
|
||||
source: "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Nheko.paddingSmall
|
||||
Layout.fillWidth: true
|
||||
spacing: Nheko.paddingSmall
|
||||
|
||||
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.maximumWidth: implicitWidth + 1
|
||||
color: palette.text
|
||||
font.bold: true
|
||||
font.pointSize: 14
|
||||
text: qsTr("%1 enabled end-to-end encryption").arg(r.userName)
|
||||
}
|
||||
|
||||
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.")
|
||||
textFormat: Text.PlainText
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,15 +13,19 @@ Control {
|
|||
required property string eventId
|
||||
required property string filename
|
||||
required property string filesize
|
||||
|
||||
padding: Settings.bubbles? 8 : 12
|
||||
property bool fitsMetadata: false
|
||||
//Layout.preferredHeight: rowa.implicitHeight + padding
|
||||
//Layout.maximumWidth: rowa.Layout.maximumWidth + metadataWidth + padding
|
||||
property int metadataWidth: 0
|
||||
property bool fitsMetadata: false
|
||||
|
||||
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 {
|
||||
id: rowa
|
||||
|
||||
|
@ -30,36 +34,32 @@ Control {
|
|||
Rectangle {
|
||||
id: button
|
||||
|
||||
color: palette.light
|
||||
radius: 22
|
||||
Layout.preferredHeight: 44
|
||||
Layout.preferredWidth: 44
|
||||
color: palette.light
|
||||
radius: 22
|
||||
|
||||
Image {
|
||||
id: img
|
||||
|
||||
anchors.centerIn: parent
|
||||
fillMode: Image.Pad
|
||||
height: 40
|
||||
width: 40
|
||||
source: "qrc:/icons/icons/ui/download.svg"
|
||||
sourceSize.height: 40
|
||||
sourceSize.width: 40
|
||||
|
||||
anchors.centerIn: parent
|
||||
source: "qrc:/icons/icons/ui/download.svg"
|
||||
fillMode: Image.Pad
|
||||
width: 40
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onSingleTapped: room.saveMedia(eventId)
|
||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||
}
|
||||
|
||||
onSingleTapped: room.saveMedia(eventId)
|
||||
}
|
||||
NhekoCursorShape {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: col
|
||||
|
||||
|
@ -68,31 +68,21 @@ Control {
|
|||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: implicitWidth + 1
|
||||
color: palette.text
|
||||
elide: Text.ElideRight
|
||||
text: evRoot.filename
|
||||
textFormat: Text.PlainText
|
||||
elide: Text.ElideRight
|
||||
color: palette.text
|
||||
}
|
||||
|
||||
Text {
|
||||
id: filesize_
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: implicitWidth + 1
|
||||
color: palette.text
|
||||
elide: Text.ElideRight
|
||||
text: evRoot.filesize
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,27 +8,28 @@ import QtQuick.Controls
|
|||
import im.nheko
|
||||
|
||||
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 body
|
||||
required property string filename
|
||||
required property string eventId
|
||||
required property int containerHeight
|
||||
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
|
||||
|
||||
hoverEnabled: true
|
||||
EventDelegateChooser.keepAspectRatio: true
|
||||
EventDelegateChooser.maxHeight: containerHeight / divisor
|
||||
EventDelegateChooser.maxWidth: originalWidth
|
||||
enabled: !EventDelegateChooser.isReply
|
||||
|
||||
hoverEnabled: true
|
||||
state: (img.status != Image.Ready || timeline.privacyScreen.active) ? "BlurhashVisible" : "ImageVisible"
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "BlurhashVisible"
|
||||
|
@ -39,11 +40,9 @@ AbstractButton {
|
|||
visible: (img.status != Image.Ready) || (timeline.privacyScreen.active && blurhash)
|
||||
}
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
img.opacity: 0
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
mxcimage.opacity: 0
|
||||
}
|
||||
|
@ -57,11 +56,9 @@ AbstractButton {
|
|||
visible: false
|
||||
}
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
img.opacity: 1
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
mxcimage.opacity: 1
|
||||
}
|
||||
|
@ -70,114 +67,98 @@ AbstractButton {
|
|||
transitions: [
|
||||
Transition {
|
||||
from: "ImageVisible"
|
||||
to: "BlurhashVisible"
|
||||
reversible: true
|
||||
to: "BlurhashVisible"
|
||||
|
||||
SequentialAnimation {
|
||||
PropertyAction {
|
||||
target: blurhash_
|
||||
property: "visible"
|
||||
target: blurhash_
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.Linear
|
||||
property: "opacity"
|
||||
target: blurhash_
|
||||
property: "opacity"
|
||||
}
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.Linear
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
target: img
|
||||
property: "opacity"
|
||||
duration: 300
|
||||
easing.type: Easing.Linear
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: mxcimage
|
||||
property: "opacity"
|
||||
duration: 300
|
||||
easing.type: Easing.Linear
|
||||
property: "opacity"
|
||||
target: mxcimage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
property int metadataWidth
|
||||
property bool fitsMetadata: parent != null ? (parent.width - width) > metadataWidth+4 : false
|
||||
onClicked: Settings.openImageExternal ? room.openMedia(eventId) : TimelineManager.openImageOverlay(room, url, eventId, originalWidth, proportionalHeight)
|
||||
|
||||
Image {
|
||||
id: img
|
||||
|
||||
visible: !mxcimage.loaded
|
||||
anchors.fill: parent
|
||||
source: url != "" ? (url.replace("mxc://", "image://MxcImage/") + "?scale") : ""
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
horizontalAlignment: Image.AlignLeft
|
||||
smooth: true
|
||||
mipmap: true
|
||||
|
||||
smooth: true
|
||||
source: url != "" ? (url.replace("mxc://", "image://MxcImage/") + "?scale") : ""
|
||||
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
|
||||
sourceSize.height: Math.min(Screen.desktopAvailableHeight, (originalWidth < 1 ? Screen.desktopAvailableHeight : originalWidth*proportionalHeight)) * Screen.devicePixelRatio
|
||||
visible: !mxcimage.loaded
|
||||
}
|
||||
|
||||
MxcAnimatedImage {
|
||||
id: mxcimage
|
||||
|
||||
visible: loaded
|
||||
roomm: room
|
||||
play: !Settings.animateImagesOnHover || parent.hovered
|
||||
eventId: parent.eventId
|
||||
|
||||
anchors.fill: parent
|
||||
eventId: parent.eventId
|
||||
play: !Settings.animateImagesOnHover || parent.hovered
|
||||
roomm: room
|
||||
visible: loaded
|
||||
}
|
||||
|
||||
Image {
|
||||
id: blurhash_
|
||||
|
||||
source: blurhash ? ("image://blurhash/" + blurhash) : ("image://colorimage/:/icons/icons/ui/image-failed.svg?" + palette.buttonText)
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
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
|
||||
|
||||
anchors.fill: parent
|
||||
sourceSize.width: parent.width * Screen.devicePixelRatio
|
||||
}
|
||||
|
||||
onClicked: Settings.openImageExternal ? room.openMedia(eventId) : TimelineManager.openImageOverlay(room, url, eventId, originalWidth, proportionalHeight);
|
||||
|
||||
Item {
|
||||
id: overlay
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
visible: parent.hovered
|
||||
|
||||
Rectangle {
|
||||
id: container
|
||||
|
||||
width: parent.width
|
||||
implicitHeight: imgcaption.implicitHeight
|
||||
anchors.bottom: overlay.bottom
|
||||
color: palette.window
|
||||
implicitHeight: imgcaption.implicitHeight
|
||||
opacity: 0.75
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Text {
|
||||
id: imgcaption
|
||||
|
||||
anchors.fill: container
|
||||
color: palette.text
|
||||
elide: Text.ElideMiddle
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
// See this MSC: https://github.com/matrix-org/matrix-doc/pull/2530
|
||||
text: filename ? filename : body
|
||||
color: palette.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
import QtQuick 2.5
|
||||
import im.nheko 1.0
|
||||
|
||||
|
||||
TextMessage {
|
||||
property bool isStateEvent
|
||||
font.italic: true
|
||||
|
||||
color: palette.buttonText
|
||||
font.pointSize: isStateEvent? 0.8*Settings.fontSize : Settings.fontSize
|
||||
horizontalAlignment: isStateEvent? Text.AlignHCenter : undefined
|
||||
font.italic: true
|
||||
font.pointSize: isStateEvent ? 0.8 * Settings.fontSize : Settings.fontSize
|
||||
horizontalAlignment: isStateEvent ? Text.AlignHCenter : undefined
|
||||
}
|
||||
|
|
|
@ -7,14 +7,14 @@ import QtQuick.Controls 2.1
|
|||
|
||||
Label {
|
||||
property bool isStateEvent
|
||||
|
||||
color: palette.text
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
height: Math.round(fontMetrics.height * 1.4)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
width: contentWidth * 1.2
|
||||
|
||||
background: Rectangle {
|
||||
radius: parent.height / 2
|
||||
color: palette.alternateBase
|
||||
radius: parent.height / 2
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import im.nheko 1.0
|
|||
MatrixText {
|
||||
required property string typeString
|
||||
|
||||
text: qsTr("unimplemented event: ") + typeString
|
||||
// width: parent.width
|
||||
// width: parent.width
|
||||
color: palette.inactive.text
|
||||
text: qsTr("unimplemented event: ") + typeString
|
||||
}
|
||||
|
|
|
@ -11,80 +11,79 @@ import im.nheko
|
|||
Item {
|
||||
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 filesize
|
||||
property double divisor: EventDelegateChooser.isReply ? 10 : 4
|
||||
property int tempWidth: originalWidth < 1? 400: originalWidth
|
||||
implicitWidth: type == MtxEvent.VideoMessage ? Math.round(tempWidth*Math.min((timelineView.height/divisor)/(tempWidth*proportionalHeight), 1)) : 500
|
||||
width: Math.min(parent?.width ?? implicitWidth, implicitWidth)
|
||||
height: (type == MtxEvent.VideoMessage ? width*proportionalHeight : 80) + fileInfoLabel.height
|
||||
required property int duration
|
||||
required property string eventId
|
||||
required property string filesize
|
||||
property bool fitsMetadata: (parent.width - fileInfoLabel.width) > metadataWidth + 4
|
||||
//implicitHeight: height
|
||||
|
||||
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 {
|
||||
id: mxcmedia
|
||||
|
||||
// TODO: Show error in overlay or so?
|
||||
roomm: room
|
||||
videoOutput: videoOutput
|
||||
|
||||
audioOutput: AudioOutput {
|
||||
muted: mediaControls.muted
|
||||
volume: mediaControls.desiredVolume
|
||||
}
|
||||
videoOutput: videoOutput
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: videoContainer
|
||||
|
||||
color: content.type == MtxEvent.VideoMessage ? palette.window : "transparent"
|
||||
width: parent.width
|
||||
height: parent.height - fileInfoLabel.height
|
||||
width: parent.width
|
||||
|
||||
TapHandler {
|
||||
onTapped: Settings.openVideoExternal ? room.openMedia(eventId) : mediaControls.showControls()
|
||||
}
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: content.thumbnailUrl ? thumbnailUrl.replace("mxc://", "image://MxcImage/") + "?scale" : "image://colorimage/:/icons/icons/ui/video-file.svg?" + palette.windowText
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: content.thumbnailUrl ? thumbnailUrl.replace("mxc://", "image://MxcImage/") + "?scale" : "image://colorimage/:/icons/icons/ui/video-file.svg?" + palette.windowText
|
||||
|
||||
VideoOutput {
|
||||
id: videoOutput
|
||||
|
||||
visible: content.type == MtxEvent.VideoMessage
|
||||
clip: true
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
fillMode: VideoOutput.PreserveAspectFit
|
||||
orientation: mxcmedia.orientation
|
||||
visible: content.type == MtxEvent.VideoMessage
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MediaControls {
|
||||
id: mediaControls
|
||||
|
||||
anchors.bottom: videoContainer.bottom
|
||||
anchors.left: videoContainer.left
|
||||
anchors.right: videoContainer.right
|
||||
anchors.bottom: videoContainer.bottom
|
||||
playingVideo: content.type == MtxEvent.VideoMessage
|
||||
positionValue: mxcmedia.position
|
||||
duration: mediaLoaded ? mxcmedia.duration : content.duration
|
||||
mediaLoaded: mxcmedia.loaded
|
||||
mediaState: mxcmedia.playbackState
|
||||
onPositionChanged: mxcmedia.position = position
|
||||
onPlayPauseActivated: mxcmedia.playbackState == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play()
|
||||
playingVideo: content.type == MtxEvent.VideoMessage
|
||||
positionValue: mxcmedia.position
|
||||
|
||||
onLoadActivated: mxcmedia.eventId = eventId
|
||||
onPlayPauseActivated: mxcmedia.playbackState == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play()
|
||||
onPositionChanged: mxcmedia.position = position
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,15 +92,13 @@ Item {
|
|||
id: fileInfoLabel
|
||||
|
||||
anchors.top: videoContainer.bottom
|
||||
color: palette.text
|
||||
elide: Text.ElideRight
|
||||
text: content.body + " [" + filesize + "]"
|
||||
textFormat: Text.RichText
|
||||
elide: Text.ElideRight
|
||||
color: palette.text
|
||||
|
||||
background: Rectangle {
|
||||
color: palette.base
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,47 +10,50 @@ import im.nheko
|
|||
Control {
|
||||
id: msgRoot
|
||||
|
||||
property int metadataWidth: 0
|
||||
required property string eventId
|
||||
property bool fitsMetadata: false //parent.width - redactedLayout.width > metadataWidth + 4
|
||||
|
||||
required property string eventId
|
||||
property int metadataWidth: 0
|
||||
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
|
||||
padding: Nheko.paddingSmall
|
||||
|
||||
background: Rectangle {
|
||||
color: palette.alternateBase
|
||||
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
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,43 +11,35 @@ import "../"
|
|||
AbstractButton {
|
||||
id: r
|
||||
|
||||
property color userColor: "red"
|
||||
property bool keepFullText: false
|
||||
|
||||
required property string eventId
|
||||
|
||||
property bool keepFullText: false
|
||||
required property int maxWidth
|
||||
property var room_: room
|
||||
|
||||
property color userColor: "red"
|
||||
property string userId: eventId ? room.dataById(eventId, Room.UserId, "") : ""
|
||||
property string userName: eventId ? room.dataById(eventId, Room.UserName, "") : ""
|
||||
|
||||
implicitHeight: replyContainer.implicitHeight
|
||||
implicitWidth: replyContainer.implicitWidth
|
||||
required property int maxWidth
|
||||
|
||||
NhekoCursorShape {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
background: Rectangle {
|
||||
id: backgroundItem
|
||||
|
||||
property color bgColor: palette.base
|
||||
property color userColor: TimelineManager.userColor(r.userId, palette.base)
|
||||
|
||||
color: Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.1))
|
||||
z: -1
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
let link = reply.child.linkAt != undefined && reply.child.linkAt(pressX-colorline.width, pressY - userName_.implicitHeight);
|
||||
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)
|
||||
|
||||
contentItem: TimelineEvent {
|
||||
id: timelineEvent
|
||||
|
||||
isStateEvent: false
|
||||
room: r.room_
|
||||
eventId: r.eventId
|
||||
replyTo: ""
|
||||
isStateEvent: false
|
||||
mainInset: 4 + Nheko.paddingMedium
|
||||
maxWidth: r.maxWidth
|
||||
replyTo: ""
|
||||
room: r.room_
|
||||
|
||||
//height: replyContainer.implicitHeight
|
||||
data: Row {
|
||||
|
@ -58,14 +50,14 @@ AbstractButton {
|
|||
Rectangle {
|
||||
id: colorline
|
||||
|
||||
width: 4
|
||||
height: content.height
|
||||
|
||||
color: TimelineManager.userColor(r.userId, palette.base)
|
||||
height: content.height
|
||||
width: 4
|
||||
}
|
||||
|
||||
Column {
|
||||
id: content
|
||||
|
||||
data: [usernameBtn, timelineEvent.main,]
|
||||
spacing: 0
|
||||
|
||||
AbstractButton {
|
||||
|
@ -73,29 +65,31 @@ AbstractButton {
|
|||
|
||||
contentItem: Label {
|
||||
id: userName_
|
||||
text: r.userName
|
||||
|
||||
color: r.userColor
|
||||
text: r.userName
|
||||
textFormat: Text.RichText
|
||||
width: timelineEvent.main?.width
|
||||
}
|
||||
|
||||
onClicked: room.openUserProfile(r.userId)
|
||||
}
|
||||
|
||||
data: [
|
||||
usernameBtn, timelineEvent.main,
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
id: backgroundItem
|
||||
|
||||
z: -1
|
||||
property color userColor: TimelineManager.userColor(r.userId, palette.base)
|
||||
property color bgColor: palette.base
|
||||
color: Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.1))
|
||||
onClicked: {
|
||||
let link = reply.child.linkAt != undefined && reply.child.linkAt(pressX - colorline.width, pressY - userName_.implicitHeight);
|
||||
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)
|
||||
|
||||
NhekoCursorShape {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,14 +7,17 @@ import im.nheko
|
|||
|
||||
MatrixText {
|
||||
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
|
||||
property bool isReply: EventDelegateChooser.isReply
|
||||
required property bool keepFullText
|
||||
required property string formatted
|
||||
|
||||
property string copyText: selectedText ? getText(selectionStart, selectionEnd) : body
|
||||
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
|
||||
text: `
|
||||
|
@ -30,7 +33,7 @@ MatrixText {
|
|||
}
|
||||
table th,
|
||||
table td {
|
||||
padding: ` + Math.ceil(fontMetrics.lineSpacing/2) + `px;
|
||||
padding: ` + Math.ceil(fontMetrics.lineSpacing / 2) + `px;
|
||||
}
|
||||
blockquote { margin-left: 1em; }
|
||||
` + (!Settings.mobileMode ? `span[data-mx-spoiler] {
|
||||
|
@ -40,13 +43,9 @@ MatrixText {
|
|||
`</style>
|
||||
` + 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 {
|
||||
enabled: isReply
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: isReply
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,82 +12,69 @@ ApplicationWindow {
|
|||
|
||||
property var flow
|
||||
|
||||
onClosing: VerificationManager.removeVerificationFlow(flow)
|
||||
title: stack.currentItem ? (stack.currentItem.title_ || "") : ""
|
||||
modality: Qt.NonModal
|
||||
color: palette.window
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
height: stack.currentItem.implicitHeight + 2 * Nheko.paddingMedium
|
||||
//height: stack.currentItem.implicitHeight
|
||||
minimumHeight: stack.currentItem.implicitHeight + 2 * Nheko.paddingLarge
|
||||
height: stack.currentItem.implicitHeight + 2 * Nheko.paddingMedium
|
||||
minimumWidth: 400
|
||||
modality: Qt.NonModal
|
||||
title: stack.currentItem ? (stack.currentItem.title_ || "") : ""
|
||||
width: 400
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
|
||||
background: Rectangle {
|
||||
color: palette.window
|
||||
}
|
||||
|
||||
onClosing: VerificationManager.removeVerificationFlow(flow)
|
||||
|
||||
StackView {
|
||||
id: stack
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
implicitHeight: dialog.height - 2 * Nheko.paddingMedium
|
||||
implicitWidth: dialog.width - 2 * Nheko.paddingMedium
|
||||
initialItem: newVerificationRequest
|
||||
implicitWidth: dialog.width - 2* Nheko.paddingMedium
|
||||
implicitHeight: dialog.height - 2* Nheko.paddingMedium
|
||||
}
|
||||
|
||||
Component {
|
||||
id: newVerificationRequest
|
||||
|
||||
NewVerificationRequest {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: waiting
|
||||
|
||||
Waiting {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: success
|
||||
|
||||
Success {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: failed
|
||||
|
||||
Failed {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: digitVerification
|
||||
|
||||
DigitVerification {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: emojiVerification
|
||||
|
||||
EmojiVerification {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
state: flow.state
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "PromptStartVerification"
|
||||
|
@ -95,7 +82,6 @@ ApplicationWindow {
|
|||
StateChangeScript {
|
||||
script: stack.replace(null, newVerificationRequest)
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "CompareEmoji"
|
||||
|
@ -103,7 +89,6 @@ ApplicationWindow {
|
|||
StateChangeScript {
|
||||
script: stack.replace(null, emojiVerification)
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "CompareNumber"
|
||||
|
@ -111,7 +96,6 @@ ApplicationWindow {
|
|||
StateChangeScript {
|
||||
script: stack.replace(null, digitVerification)
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "WaitingForKeys"
|
||||
|
@ -119,7 +103,6 @@ ApplicationWindow {
|
|||
StateChangeScript {
|
||||
script: stack.replace(null, waiting)
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "WaitingForOtherToAccept"
|
||||
|
@ -127,7 +110,6 @@ ApplicationWindow {
|
|||
StateChangeScript {
|
||||
script: stack.replace(null, waiting)
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "WaitingForMac"
|
||||
|
@ -135,7 +117,6 @@ ApplicationWindow {
|
|||
StateChangeScript {
|
||||
script: stack.replace(null, waiting)
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "Success"
|
||||
|
@ -143,7 +124,6 @@ ApplicationWindow {
|
|||
StateChangeScript {
|
||||
script: stack.replace(null, success)
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "Failed"
|
||||
|
@ -151,9 +131,7 @@ ApplicationWindow {
|
|||
StateChangeScript {
|
||||
script: stack.replace(null, failed)
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,59 +12,56 @@ ColumnLayout {
|
|||
spacing: 16
|
||||
|
||||
Label {
|
||||
Layout.preferredWidth: 400
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!")
|
||||
Layout.preferredWidth: 400
|
||||
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
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true; }
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
Label {
|
||||
color: palette.text
|
||||
font.pixelSize: Qt.application.font.pixelSize * 2
|
||||
text: flow.sasList[0]
|
||||
color: palette.text
|
||||
}
|
||||
|
||||
Label {
|
||||
color: palette.text
|
||||
font.pixelSize: Qt.application.font.pixelSize * 2
|
||||
text: flow.sasList[1]
|
||||
color: palette.text
|
||||
}
|
||||
|
||||
Label {
|
||||
color: palette.text
|
||||
font.pixelSize: Qt.application.font.pixelSize * 2
|
||||
text: flow.sasList[2]
|
||||
color: palette.text
|
||||
}
|
||||
|
||||
}
|
||||
Item { Layout.fillHeight: true; }
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
RowLayout {
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: qsTr("They do not match!")
|
||||
|
||||
onClicked: {
|
||||
flow.cancel();
|
||||
dialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: qsTr("They match!")
|
||||
|
||||
onClicked: flow.next()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@ import QtQuick.Layouts
|
|||
|
||||
Rectangle {
|
||||
color: "red"
|
||||
height: Qt.application.font.pixelSize * 4
|
||||
implicitHeight: Qt.application.font.pixelSize * 4
|
||||
implicitWidth: col.width
|
||||
height: Qt.application.font.pixelSize * 4
|
||||
width: col.width
|
||||
|
||||
ColumnLayout {
|
||||
|
@ -21,17 +21,14 @@ Rectangle {
|
|||
anchors.bottom: parent.bottom
|
||||
|
||||
Label {
|
||||
Layout.preferredHeight: font.pixelSize * 2
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: col.emoji.emoji
|
||||
Layout.preferredHeight: font.pixelSize * 2
|
||||
font.pixelSize: Qt.application.font.pixelSize * 2
|
||||
text: col.emoji.emoji
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
||||
text: col.emoji.description
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,339 +13,340 @@ ColumnLayout {
|
|||
spacing: 16
|
||||
|
||||
Label {
|
||||
Layout.preferredWidth: 400
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!")
|
||||
Layout.preferredWidth: 400
|
||||
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
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true; }
|
||||
RowLayout {
|
||||
id: emojis
|
||||
|
||||
property var mapping: [{
|
||||
"number": 0,
|
||||
"emoji": "🐶",
|
||||
"description": "Dog",
|
||||
"unicode": "U+1F436"
|
||||
}, {
|
||||
"number": 1,
|
||||
"emoji": "🐱",
|
||||
"description": "Cat",
|
||||
"unicode": "U+1F431"
|
||||
}, {
|
||||
"number": 2,
|
||||
"emoji": "🦁",
|
||||
"description": "Lion",
|
||||
"unicode": "U+1F981"
|
||||
}, {
|
||||
"number": 3,
|
||||
"emoji": "🐎",
|
||||
"description": "Horse",
|
||||
"unicode": "U+1F40E"
|
||||
}, {
|
||||
"number": 4,
|
||||
"emoji": "🦄",
|
||||
"description": "Unicorn",
|
||||
"unicode": "U+1F984"
|
||||
}, {
|
||||
"number": 5,
|
||||
"emoji": "🐷",
|
||||
"description": "Pig",
|
||||
"unicode": "U+1F437"
|
||||
}, {
|
||||
"number": 6,
|
||||
"emoji": "🐘",
|
||||
"description": "Elephant",
|
||||
"unicode": "U+1F418"
|
||||
}, {
|
||||
"number": 7,
|
||||
"emoji": "🐰",
|
||||
"description": "Rabbit",
|
||||
"unicode": "U+1F430"
|
||||
}, {
|
||||
"number": 8,
|
||||
"emoji": "🐼",
|
||||
"description": "Panda",
|
||||
"unicode": "U+1F43C"
|
||||
}, {
|
||||
"number": 9,
|
||||
"emoji": "🐓",
|
||||
"description": "Rooster",
|
||||
"unicode": "U+1F413"
|
||||
}, {
|
||||
"number": 10,
|
||||
"emoji": "🐧",
|
||||
"description": "Penguin",
|
||||
"unicode": "U+1F427"
|
||||
}, {
|
||||
"number": 11,
|
||||
"emoji": "🐢",
|
||||
"description": "Turtle",
|
||||
"unicode": "U+1F422"
|
||||
}, {
|
||||
"number": 12,
|
||||
"emoji": "🐟",
|
||||
"description": "Fish",
|
||||
"unicode": "U+1F41F"
|
||||
}, {
|
||||
"number": 13,
|
||||
"emoji": "🐙",
|
||||
"description": "Octopus",
|
||||
"unicode": "U+1F419"
|
||||
}, {
|
||||
"number": 14,
|
||||
"emoji": "🦋",
|
||||
"description": "Butterfly",
|
||||
"unicode": "U+1F98B"
|
||||
}, {
|
||||
"number": 15,
|
||||
"emoji": "🌷",
|
||||
"description": "Flower",
|
||||
"unicode": "U+1F337"
|
||||
}, {
|
||||
"number": 16,
|
||||
"emoji": "🌳",
|
||||
"description": "Tree",
|
||||
"unicode": "U+1F333"
|
||||
}, {
|
||||
"number": 17,
|
||||
"emoji": "🌵",
|
||||
"description": "Cactus",
|
||||
"unicode": "U+1F335"
|
||||
}, {
|
||||
"number": 18,
|
||||
"emoji": "🍄",
|
||||
"description": "Mushroom",
|
||||
"unicode": "U+1F344"
|
||||
}, {
|
||||
"number": 19,
|
||||
"emoji": "🌏",
|
||||
"description": "Globe",
|
||||
"unicode": "U+1F30F"
|
||||
}, {
|
||||
"number": 20,
|
||||
"emoji": "🌙",
|
||||
"description": "Moon",
|
||||
"unicode": "U+1F319"
|
||||
}, {
|
||||
"number": 21,
|
||||
"emoji": "☁️",
|
||||
"description": "Cloud",
|
||||
"unicode": "U+2601U+FE0F"
|
||||
}, {
|
||||
"number": 22,
|
||||
"emoji": "🔥",
|
||||
"description": "Fire",
|
||||
"unicode": "U+1F525"
|
||||
}, {
|
||||
"number": 23,
|
||||
"emoji": "🍌",
|
||||
"description": "Banana",
|
||||
"unicode": "U+1F34C"
|
||||
}, {
|
||||
"number": 24,
|
||||
"emoji": "🍎",
|
||||
"description": "Apple",
|
||||
"unicode": "U+1F34E"
|
||||
}, {
|
||||
"number": 25,
|
||||
"emoji": "🍓",
|
||||
"description": "Strawberry",
|
||||
"unicode": "U+1F353"
|
||||
}, {
|
||||
"number": 26,
|
||||
"emoji": "🌽",
|
||||
"description": "Corn",
|
||||
"unicode": "U+1F33D"
|
||||
}, {
|
||||
"number": 27,
|
||||
"emoji": "🍕",
|
||||
"description": "Pizza",
|
||||
"unicode": "U+1F355"
|
||||
}, {
|
||||
"number": 28,
|
||||
"emoji": "🎂",
|
||||
"description": "Cake",
|
||||
"unicode": "U+1F382"
|
||||
}, {
|
||||
"number": 29,
|
||||
"emoji": "❤️",
|
||||
"description": "Heart",
|
||||
"unicode": "U+2764U+FE0F"
|
||||
}, {
|
||||
"number": 30,
|
||||
"emoji": "😀",
|
||||
"description": "Smiley",
|
||||
"unicode": "U+1F600"
|
||||
}, {
|
||||
"number": 31,
|
||||
"emoji": "🤖",
|
||||
"description": "Robot",
|
||||
"unicode": "U+1F916"
|
||||
}, {
|
||||
"number": 32,
|
||||
"emoji": "🎩",
|
||||
"description": "Hat",
|
||||
"unicode": "U+1F3A9"
|
||||
}, {
|
||||
"number": 33,
|
||||
"emoji": "👓",
|
||||
"description": "Glasses",
|
||||
"unicode": "U+1F453"
|
||||
}, {
|
||||
"number": 34,
|
||||
"emoji": "🔧",
|
||||
"description": "Spanner",
|
||||
"unicode": "U+1F527"
|
||||
}, {
|
||||
"number": 35,
|
||||
"emoji": "🎅",
|
||||
"description": "Santa",
|
||||
"unicode": "U+1F385"
|
||||
}, {
|
||||
"number": 36,
|
||||
"emoji": "👍",
|
||||
"description": "Thumbs Up",
|
||||
"unicode": "U+1F44D"
|
||||
}, {
|
||||
"number": 37,
|
||||
"emoji": "☂️",
|
||||
"description": "Umbrella",
|
||||
"unicode": "U+2602U+FE0F"
|
||||
}, {
|
||||
"number": 38,
|
||||
"emoji": "⌛",
|
||||
"description": "Hourglass",
|
||||
"unicode": "U+231B"
|
||||
}, {
|
||||
"number": 39,
|
||||
"emoji": "⏰",
|
||||
"description": "Clock",
|
||||
"unicode": "U+23F0"
|
||||
}, {
|
||||
"number": 40,
|
||||
"emoji": "🎁",
|
||||
"description": "Gift",
|
||||
"unicode": "U+1F381"
|
||||
}, {
|
||||
"number": 41,
|
||||
"emoji": "💡",
|
||||
"description": "Light Bulb",
|
||||
"unicode": "U+1F4A1"
|
||||
}, {
|
||||
"number": 42,
|
||||
"emoji": "📕",
|
||||
"description": "Book",
|
||||
"unicode": "U+1F4D5"
|
||||
}, {
|
||||
"number": 43,
|
||||
"emoji": "✏️",
|
||||
"description": "Pencil",
|
||||
"unicode": "U+270FU+FE0F"
|
||||
}, {
|
||||
"number": 44,
|
||||
"emoji": "📎",
|
||||
"description": "Paperclip",
|
||||
"unicode": "U+1F4CE"
|
||||
}, {
|
||||
"number": 45,
|
||||
"emoji": "✂️",
|
||||
"description": "Scissors",
|
||||
"unicode": "U+2702U+FE0F"
|
||||
}, {
|
||||
"number": 46,
|
||||
"emoji": "🔒",
|
||||
"description": "Lock",
|
||||
"unicode": "U+1F512"
|
||||
}, {
|
||||
"number": 47,
|
||||
"emoji": "🔑",
|
||||
"description": "Key",
|
||||
"unicode": "U+1F511"
|
||||
}, {
|
||||
"number": 48,
|
||||
"emoji": "🔨",
|
||||
"description": "Hammer",
|
||||
"unicode": "U+1F528"
|
||||
}, {
|
||||
"number": 49,
|
||||
"emoji": "☎️",
|
||||
"description": "Telephone",
|
||||
"unicode": "U+260EU+FE0F"
|
||||
}, {
|
||||
"number": 50,
|
||||
"emoji": "🏁",
|
||||
"description": "Flag",
|
||||
"unicode": "U+1F3C1"
|
||||
}, {
|
||||
"number": 51,
|
||||
"emoji": "🚂",
|
||||
"description": "Train",
|
||||
"unicode": "U+1F682"
|
||||
}, {
|
||||
"number": 52,
|
||||
"emoji": "🚲",
|
||||
"description": "Bicycle",
|
||||
"unicode": "U+1F6B2"
|
||||
}, {
|
||||
"number": 53,
|
||||
"emoji": "✈️",
|
||||
"description": "Aeroplane",
|
||||
"unicode": "U+2708U+FE0F"
|
||||
}, {
|
||||
"number": 54,
|
||||
"emoji": "🚀",
|
||||
"description": "Rocket",
|
||||
"unicode": "U+1F680"
|
||||
}, {
|
||||
"number": 55,
|
||||
"emoji": "🏆",
|
||||
"description": "Trophy",
|
||||
"unicode": "U+1F3C6"
|
||||
}, {
|
||||
"number": 56,
|
||||
"emoji": "⚽",
|
||||
"description": "Ball",
|
||||
"unicode": "U+26BD"
|
||||
}, {
|
||||
"number": 57,
|
||||
"emoji": "🎸",
|
||||
"description": "Guitar",
|
||||
"unicode": "U+1F3B8"
|
||||
}, {
|
||||
"number": 58,
|
||||
"emoji": "🎺",
|
||||
"description": "Trumpet",
|
||||
"unicode": "U+1F3BA"
|
||||
}, {
|
||||
"number": 59,
|
||||
"emoji": "🔔",
|
||||
"description": "Bell",
|
||||
"unicode": "U+1F514"
|
||||
}, {
|
||||
"number": 60,
|
||||
"emoji": "⚓",
|
||||
"description": "Anchor",
|
||||
"unicode": "U+2693"
|
||||
}, {
|
||||
"number": 61,
|
||||
"emoji": "🎧",
|
||||
"description": "Headphones",
|
||||
"unicode": "U+1F3A7"
|
||||
}, {
|
||||
"number": 62,
|
||||
"emoji": "📁",
|
||||
"description": "Folder",
|
||||
"unicode": "U+1F4C1"
|
||||
}, {
|
||||
"number": 63,
|
||||
"emoji": "📌",
|
||||
"description": "Pin",
|
||||
"unicode": "U+1F4CC"
|
||||
}]
|
||||
"number": 0,
|
||||
"emoji": "🐶",
|
||||
"description": "Dog",
|
||||
"unicode": "U+1F436"
|
||||
}, {
|
||||
"number": 1,
|
||||
"emoji": "🐱",
|
||||
"description": "Cat",
|
||||
"unicode": "U+1F431"
|
||||
}, {
|
||||
"number": 2,
|
||||
"emoji": "🦁",
|
||||
"description": "Lion",
|
||||
"unicode": "U+1F981"
|
||||
}, {
|
||||
"number": 3,
|
||||
"emoji": "🐎",
|
||||
"description": "Horse",
|
||||
"unicode": "U+1F40E"
|
||||
}, {
|
||||
"number": 4,
|
||||
"emoji": "🦄",
|
||||
"description": "Unicorn",
|
||||
"unicode": "U+1F984"
|
||||
}, {
|
||||
"number": 5,
|
||||
"emoji": "🐷",
|
||||
"description": "Pig",
|
||||
"unicode": "U+1F437"
|
||||
}, {
|
||||
"number": 6,
|
||||
"emoji": "🐘",
|
||||
"description": "Elephant",
|
||||
"unicode": "U+1F418"
|
||||
}, {
|
||||
"number": 7,
|
||||
"emoji": "🐰",
|
||||
"description": "Rabbit",
|
||||
"unicode": "U+1F430"
|
||||
}, {
|
||||
"number": 8,
|
||||
"emoji": "🐼",
|
||||
"description": "Panda",
|
||||
"unicode": "U+1F43C"
|
||||
}, {
|
||||
"number": 9,
|
||||
"emoji": "🐓",
|
||||
"description": "Rooster",
|
||||
"unicode": "U+1F413"
|
||||
}, {
|
||||
"number": 10,
|
||||
"emoji": "🐧",
|
||||
"description": "Penguin",
|
||||
"unicode": "U+1F427"
|
||||
}, {
|
||||
"number": 11,
|
||||
"emoji": "🐢",
|
||||
"description": "Turtle",
|
||||
"unicode": "U+1F422"
|
||||
}, {
|
||||
"number": 12,
|
||||
"emoji": "🐟",
|
||||
"description": "Fish",
|
||||
"unicode": "U+1F41F"
|
||||
}, {
|
||||
"number": 13,
|
||||
"emoji": "🐙",
|
||||
"description": "Octopus",
|
||||
"unicode": "U+1F419"
|
||||
}, {
|
||||
"number": 14,
|
||||
"emoji": "🦋",
|
||||
"description": "Butterfly",
|
||||
"unicode": "U+1F98B"
|
||||
}, {
|
||||
"number": 15,
|
||||
"emoji": "🌷",
|
||||
"description": "Flower",
|
||||
"unicode": "U+1F337"
|
||||
}, {
|
||||
"number": 16,
|
||||
"emoji": "🌳",
|
||||
"description": "Tree",
|
||||
"unicode": "U+1F333"
|
||||
}, {
|
||||
"number": 17,
|
||||
"emoji": "🌵",
|
||||
"description": "Cactus",
|
||||
"unicode": "U+1F335"
|
||||
}, {
|
||||
"number": 18,
|
||||
"emoji": "🍄",
|
||||
"description": "Mushroom",
|
||||
"unicode": "U+1F344"
|
||||
}, {
|
||||
"number": 19,
|
||||
"emoji": "🌏",
|
||||
"description": "Globe",
|
||||
"unicode": "U+1F30F"
|
||||
}, {
|
||||
"number": 20,
|
||||
"emoji": "🌙",
|
||||
"description": "Moon",
|
||||
"unicode": "U+1F319"
|
||||
}, {
|
||||
"number": 21,
|
||||
"emoji": "☁️",
|
||||
"description": "Cloud",
|
||||
"unicode": "U+2601U+FE0F"
|
||||
}, {
|
||||
"number": 22,
|
||||
"emoji": "🔥",
|
||||
"description": "Fire",
|
||||
"unicode": "U+1F525"
|
||||
}, {
|
||||
"number": 23,
|
||||
"emoji": "🍌",
|
||||
"description": "Banana",
|
||||
"unicode": "U+1F34C"
|
||||
}, {
|
||||
"number": 24,
|
||||
"emoji": "🍎",
|
||||
"description": "Apple",
|
||||
"unicode": "U+1F34E"
|
||||
}, {
|
||||
"number": 25,
|
||||
"emoji": "🍓",
|
||||
"description": "Strawberry",
|
||||
"unicode": "U+1F353"
|
||||
}, {
|
||||
"number": 26,
|
||||
"emoji": "🌽",
|
||||
"description": "Corn",
|
||||
"unicode": "U+1F33D"
|
||||
}, {
|
||||
"number": 27,
|
||||
"emoji": "🍕",
|
||||
"description": "Pizza",
|
||||
"unicode": "U+1F355"
|
||||
}, {
|
||||
"number": 28,
|
||||
"emoji": "🎂",
|
||||
"description": "Cake",
|
||||
"unicode": "U+1F382"
|
||||
}, {
|
||||
"number": 29,
|
||||
"emoji": "❤️",
|
||||
"description": "Heart",
|
||||
"unicode": "U+2764U+FE0F"
|
||||
}, {
|
||||
"number": 30,
|
||||
"emoji": "😀",
|
||||
"description": "Smiley",
|
||||
"unicode": "U+1F600"
|
||||
}, {
|
||||
"number": 31,
|
||||
"emoji": "🤖",
|
||||
"description": "Robot",
|
||||
"unicode": "U+1F916"
|
||||
}, {
|
||||
"number": 32,
|
||||
"emoji": "🎩",
|
||||
"description": "Hat",
|
||||
"unicode": "U+1F3A9"
|
||||
}, {
|
||||
"number": 33,
|
||||
"emoji": "👓",
|
||||
"description": "Glasses",
|
||||
"unicode": "U+1F453"
|
||||
}, {
|
||||
"number": 34,
|
||||
"emoji": "🔧",
|
||||
"description": "Spanner",
|
||||
"unicode": "U+1F527"
|
||||
}, {
|
||||
"number": 35,
|
||||
"emoji": "🎅",
|
||||
"description": "Santa",
|
||||
"unicode": "U+1F385"
|
||||
}, {
|
||||
"number": 36,
|
||||
"emoji": "👍",
|
||||
"description": "Thumbs Up",
|
||||
"unicode": "U+1F44D"
|
||||
}, {
|
||||
"number": 37,
|
||||
"emoji": "☂️",
|
||||
"description": "Umbrella",
|
||||
"unicode": "U+2602U+FE0F"
|
||||
}, {
|
||||
"number": 38,
|
||||
"emoji": "⌛",
|
||||
"description": "Hourglass",
|
||||
"unicode": "U+231B"
|
||||
}, {
|
||||
"number": 39,
|
||||
"emoji": "⏰",
|
||||
"description": "Clock",
|
||||
"unicode": "U+23F0"
|
||||
}, {
|
||||
"number": 40,
|
||||
"emoji": "🎁",
|
||||
"description": "Gift",
|
||||
"unicode": "U+1F381"
|
||||
}, {
|
||||
"number": 41,
|
||||
"emoji": "💡",
|
||||
"description": "Light Bulb",
|
||||
"unicode": "U+1F4A1"
|
||||
}, {
|
||||
"number": 42,
|
||||
"emoji": "📕",
|
||||
"description": "Book",
|
||||
"unicode": "U+1F4D5"
|
||||
}, {
|
||||
"number": 43,
|
||||
"emoji": "✏️",
|
||||
"description": "Pencil",
|
||||
"unicode": "U+270FU+FE0F"
|
||||
}, {
|
||||
"number": 44,
|
||||
"emoji": "📎",
|
||||
"description": "Paperclip",
|
||||
"unicode": "U+1F4CE"
|
||||
}, {
|
||||
"number": 45,
|
||||
"emoji": "✂️",
|
||||
"description": "Scissors",
|
||||
"unicode": "U+2702U+FE0F"
|
||||
}, {
|
||||
"number": 46,
|
||||
"emoji": "🔒",
|
||||
"description": "Lock",
|
||||
"unicode": "U+1F512"
|
||||
}, {
|
||||
"number": 47,
|
||||
"emoji": "🔑",
|
||||
"description": "Key",
|
||||
"unicode": "U+1F511"
|
||||
}, {
|
||||
"number": 48,
|
||||
"emoji": "🔨",
|
||||
"description": "Hammer",
|
||||
"unicode": "U+1F528"
|
||||
}, {
|
||||
"number": 49,
|
||||
"emoji": "☎️",
|
||||
"description": "Telephone",
|
||||
"unicode": "U+260EU+FE0F"
|
||||
}, {
|
||||
"number": 50,
|
||||
"emoji": "🏁",
|
||||
"description": "Flag",
|
||||
"unicode": "U+1F3C1"
|
||||
}, {
|
||||
"number": 51,
|
||||
"emoji": "🚂",
|
||||
"description": "Train",
|
||||
"unicode": "U+1F682"
|
||||
}, {
|
||||
"number": 52,
|
||||
"emoji": "🚲",
|
||||
"description": "Bicycle",
|
||||
"unicode": "U+1F6B2"
|
||||
}, {
|
||||
"number": 53,
|
||||
"emoji": "✈️",
|
||||
"description": "Aeroplane",
|
||||
"unicode": "U+2708U+FE0F"
|
||||
}, {
|
||||
"number": 54,
|
||||
"emoji": "🚀",
|
||||
"description": "Rocket",
|
||||
"unicode": "U+1F680"
|
||||
}, {
|
||||
"number": 55,
|
||||
"emoji": "🏆",
|
||||
"description": "Trophy",
|
||||
"unicode": "U+1F3C6"
|
||||
}, {
|
||||
"number": 56,
|
||||
"emoji": "⚽",
|
||||
"description": "Ball",
|
||||
"unicode": "U+26BD"
|
||||
}, {
|
||||
"number": 57,
|
||||
"emoji": "🎸",
|
||||
"description": "Guitar",
|
||||
"unicode": "U+1F3B8"
|
||||
}, {
|
||||
"number": 58,
|
||||
"emoji": "🎺",
|
||||
"description": "Trumpet",
|
||||
"unicode": "U+1F3BA"
|
||||
}, {
|
||||
"number": 59,
|
||||
"emoji": "🔔",
|
||||
"description": "Bell",
|
||||
"unicode": "U+1F514"
|
||||
}, {
|
||||
"number": 60,
|
||||
"emoji": "⚓",
|
||||
"description": "Anchor",
|
||||
"unicode": "U+2693"
|
||||
}, {
|
||||
"number": 61,
|
||||
"emoji": "🎧",
|
||||
"description": "Headphones",
|
||||
"unicode": "U+1F3A7"
|
||||
}, {
|
||||
"number": 62,
|
||||
"emoji": "📁",
|
||||
"description": "Folder",
|
||||
"unicode": "U+1F4C1"
|
||||
}, {
|
||||
"number": 63,
|
||||
"emoji": "📌",
|
||||
"description": "Pin",
|
||||
"unicode": "U+1F4CC"
|
||||
}]
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
|
@ -370,58 +371,52 @@ ColumnLayout {
|
|||
Label {
|
||||
//height: font.pixelSize * 2
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: col.emoji.emoji
|
||||
font.pixelSize: Qt.application.font.pixelSize * 2
|
||||
font.family: Settings.emojiFont
|
||||
color: palette.text
|
||||
font.family: Settings.emojiFont
|
||||
font.pixelSize: Qt.application.font.pixelSize * 2
|
||||
text: col.emoji.emoji
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
||||
text: col.emoji.description
|
||||
color: palette.text
|
||||
text: col.emoji.description
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Item { Layout.fillHeight: true; }
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
Label {
|
||||
Layout.preferredWidth: 400
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
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.")
|
||||
Layout.preferredWidth: 400
|
||||
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
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true; }
|
||||
|
||||
RowLayout {
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: qsTr("They do not match!")
|
||||
|
||||
onClicked: {
|
||||
flow.cancel();
|
||||
dialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: qsTr("They match!")
|
||||
|
||||
onClicked: flow.next()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,49 +9,48 @@ import im.nheko 1.0
|
|||
|
||||
ColumnLayout {
|
||||
property string title: qsTr("Verification failed")
|
||||
|
||||
spacing: 16
|
||||
|
||||
Text {
|
||||
id: content
|
||||
|
||||
Layout.preferredWidth: 400
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
Layout.preferredWidth: 400
|
||||
color: palette.text
|
||||
text: {
|
||||
switch (flow.error) {
|
||||
case DeviceVerificationFlow.UnknownMethod:
|
||||
case DeviceVerificationFlow.UnknownMethod:
|
||||
return qsTr("Other client does not support our verification protocol.");
|
||||
case DeviceVerificationFlow.MismatchedCommitment:
|
||||
case DeviceVerificationFlow.MismatchedSAS:
|
||||
case DeviceVerificationFlow.KeyMismatch:
|
||||
case DeviceVerificationFlow.MismatchedCommitment:
|
||||
case DeviceVerificationFlow.MismatchedSAS:
|
||||
case DeviceVerificationFlow.KeyMismatch:
|
||||
return qsTr("Key mismatch detected!");
|
||||
case DeviceVerificationFlow.Timeout:
|
||||
case DeviceVerificationFlow.Timeout:
|
||||
return qsTr("Device verification timed out.");
|
||||
case DeviceVerificationFlow.User:
|
||||
case DeviceVerificationFlow.User:
|
||||
return qsTr("Other party canceled the verification.");
|
||||
case DeviceVerificationFlow.OutOfOrder:
|
||||
case DeviceVerificationFlow.OutOfOrder:
|
||||
return qsTr("Verification messages received out of order!");
|
||||
default:
|
||||
default:
|
||||
return qsTr("Unknown verification error.");
|
||||
}
|
||||
}
|
||||
color: palette.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true; }
|
||||
|
||||
RowLayout {
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: qsTr("Close")
|
||||
|
||||
onClicked: dialog.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,11 +12,11 @@ ColumnLayout {
|
|||
spacing: 16
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
// Self verification
|
||||
|
||||
Layout.preferredWidth: 400
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
color: palette.text
|
||||
text: {
|
||||
if (flow.sender) {
|
||||
if (flow.isSelfVerification)
|
||||
|
@ -35,32 +35,30 @@ ColumnLayout {
|
|||
return qsTr("Your device (%1) has requested to be verified.").arg(flow.deviceId);
|
||||
}
|
||||
}
|
||||
color: palette.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true; }
|
||||
|
||||
RowLayout {
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: flow.sender ? qsTr("Cancel") : qsTr("Deny")
|
||||
|
||||
onClicked: {
|
||||
flow.cancel();
|
||||
dialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: flow.sender ? qsTr("Start verification") : qsTr("Accept")
|
||||
|
||||
onClicked: flow.next()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,27 +14,25 @@ ColumnLayout {
|
|||
Label {
|
||||
id: content
|
||||
|
||||
Layout.preferredWidth: 400
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
text: qsTr("Verification successful! Both sides verified their devices!")
|
||||
Layout.preferredWidth: 400
|
||||
color: palette.text
|
||||
text: qsTr("Verification successful! Both sides verified their devices!")
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true; }
|
||||
|
||||
RowLayout {
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: qsTr("Close")
|
||||
|
||||
onClicked: dialog.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,52 +10,52 @@ import im.nheko 1.0
|
|||
|
||||
ColumnLayout {
|
||||
property string title: qsTr("Waiting for other party…")
|
||||
|
||||
spacing: 16
|
||||
|
||||
Label {
|
||||
id: content
|
||||
|
||||
Layout.preferredWidth: 400
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
Layout.preferredWidth: 400
|
||||
color: palette.text
|
||||
text: {
|
||||
switch (flow.state) {
|
||||
case "WaitingForOtherToAccept":
|
||||
return qsTr("Waiting for other side to accept the verification request.");
|
||||
case "WaitingForKeys":
|
||||
return qsTr("Waiting for other side to continue the verification process.");
|
||||
case "WaitingForMac":
|
||||
return qsTr("Waiting for other side to complete the verification process.");
|
||||
default:
|
||||
return "";
|
||||
case "WaitingForOtherToAccept":
|
||||
return qsTr("Waiting for other side to accept the verification request.");
|
||||
case "WaitingForKeys":
|
||||
return qsTr("Waiting for other side to continue the verification process.");
|
||||
case "WaitingForMac":
|
||||
return qsTr("Waiting for other side to complete the verification process.");
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
color: palette.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true; }
|
||||
Spinner {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
foreground: palette.mid
|
||||
}
|
||||
Item { Layout.fillHeight: true; }
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
RowLayout {
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: qsTr("Cancel")
|
||||
|
||||
onClicked: {
|
||||
flow.cancel();
|
||||
dialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -8,21 +8,31 @@ import QtQuick.Controls
|
|||
import QtQuick.Layouts
|
||||
import im.nheko
|
||||
|
||||
|
||||
ApplicationWindow {
|
||||
id: aliasEditorW
|
||||
|
||||
property var roomSettings
|
||||
property var editingModel: Nheko.editAliases(roomSettings.roomId)
|
||||
property var roomSettings
|
||||
|
||||
modality: Qt.NonModal
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
minimumWidth: 300
|
||||
minimumHeight: 400
|
||||
height: 600
|
||||
minimumHeight: 400
|
||||
minimumWidth: 300
|
||||
modality: Qt.NonModal
|
||||
title: qsTr("Aliases to %1").arg(roomSettings.roomName)
|
||||
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 {
|
||||
// sequence: StandardKey.Cancel
|
||||
|
@ -30,32 +40,27 @@ ApplicationWindow {
|
|||
// }
|
||||
|
||||
ColumnLayout {
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
anchors.fill: parent
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
spacing: 0
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
ListView {
|
||||
Layout.fillHeight: false
|
||||
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
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
cacheBuffer: 50
|
||||
clip: true
|
||||
|
||||
|
||||
model: editingModel
|
||||
spacing: 4
|
||||
cacheBuffer: 50
|
||||
|
||||
delegate: RowLayout {
|
||||
anchors.left: parent.left
|
||||
|
@ -63,79 +68,70 @@ ApplicationWindow {
|
|||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: model.name
|
||||
color: model.isPublished ? palette.text : Nheko.theme.error
|
||||
text: model.name
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.margins: 2
|
||||
image: ":/icons/icons/ui/star.svg"
|
||||
hoverEnabled: true
|
||||
ToolTip.text: model.isCanonical ? qsTr("Primary alias") : qsTr("Make primary alias")
|
||||
ToolTip.visible: hovered
|
||||
buttonTextColor: model.isCanonical ? palette.highlight : palette.text
|
||||
highlightColor: editingModel.canAdvertize ? palette.highlight : buttonTextColor
|
||||
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: model.isCanonical ? qsTr("Primary alias") : qsTr("Make primary alias")
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/star.svg"
|
||||
|
||||
onClicked: editingModel.makeCanonical(model.index)
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.margins: 2
|
||||
image: ":/icons/icons/ui/building-shop.svg"
|
||||
hoverEnabled: true
|
||||
ToolTip.text: qsTr("Advertise as an alias in this room")
|
||||
ToolTip.visible: hovered
|
||||
buttonTextColor: model.isAdvertized ? palette.highlight : palette.text
|
||||
highlightColor: editingModel.canAdvertize ? palette.highlight : buttonTextColor
|
||||
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Advertise as an alias in this room")
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/building-shop.svg"
|
||||
|
||||
onClicked: editingModel.toggleAdvertize(model.index)
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
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.visible: hovered
|
||||
buttonTextColor: model.isPublished ? palette.highlight : palette.text
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/room-directory.svg"
|
||||
|
||||
onClicked: editingModel.togglePublish(model.index)
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.margins: 2
|
||||
image: ":/icons/icons/ui/dismiss.svg"
|
||||
hoverEnabled: true
|
||||
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Remove this alias")
|
||||
ToolTip.visible: hovered
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/dismiss.svg"
|
||||
|
||||
onClicked: editingModel.deleteAlias(model.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Nheko.paddingMedium
|
||||
Layout.fillWidth: true
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
MatrixTextField {
|
||||
id: newAliasVal
|
||||
|
||||
focus: true
|
||||
Layout.fillWidth: true
|
||||
selectByMouse: true
|
||||
font.pixelSize: fontMetrics.font.pixelSize
|
||||
color: palette.text
|
||||
focus: true
|
||||
font.pixelSize: fontMetrics.font.pixelSize
|
||||
placeholderText: qsTr("#new-alias:server.tld")
|
||||
selectByMouse: true
|
||||
|
||||
Component.onCompleted: forceActiveFocus()
|
||||
Keys.onPressed: {
|
||||
|
@ -145,10 +141,10 @@ ApplicationWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Add")
|
||||
Layout.preferredWidth: 100
|
||||
text: qsTr("Add")
|
||||
|
||||
onClicked: {
|
||||
editingModel.addAlias(newAliasVal.text);
|
||||
newAliasVal.clear();
|
||||
|
@ -156,16 +152,4 @@ ApplicationWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: DialogButtonBox {
|
||||
id: dbb
|
||||
|
||||
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
|
||||
onAccepted: {
|
||||
editingModel.commit();
|
||||
aliasEditorW.close();
|
||||
}
|
||||
onRejected: aliasEditorW.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,152 +14,20 @@ ApplicationWindow {
|
|||
|
||||
property var roomSettings
|
||||
|
||||
minimumWidth: 340
|
||||
minimumHeight: 450
|
||||
width: 450
|
||||
height: 680
|
||||
color: palette.window
|
||||
modality: Qt.NonModal
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
height: 680
|
||||
minimumHeight: 450
|
||||
minimumWidth: 340
|
||||
modality: Qt.NonModal
|
||||
title: qsTr("Allowed rooms settings")
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
onActivated: roomSettingsDialog.close()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
ListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
id: view
|
||||
|
||||
clip: true
|
||||
|
||||
|
||||
model: roomSettings.allowedRoomsModel
|
||||
spacing: 4
|
||||
cacheBuffer: 50
|
||||
|
||||
delegate: RowLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: model.name
|
||||
color: palette.text
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: model.isParent ? qsTr("Parent community") : qsTr("Other room")
|
||||
color: palette.buttonText
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
checked: model.allowed
|
||||
Layout.alignment: Qt.AlignRight
|
||||
onCheckedChanged: model.allowed = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column{
|
||||
id: roomEntryCompleter
|
||||
Layout.fillWidth: true
|
||||
|
||||
spacing: 1
|
||||
z: 5
|
||||
|
||||
Completer {
|
||||
id: roomCompleter
|
||||
|
||||
visible: roomEntry.text.length > 0
|
||||
width: parent.width
|
||||
roomId: allowedDialog.roomSettings.roomId
|
||||
completerName: "room"
|
||||
bottomToTop: true
|
||||
fullWidth: true
|
||||
avatarHeight: Nheko.avatarSize / 2
|
||||
avatarWidth: Nheko.avatarSize / 2
|
||||
centerRowContent: false
|
||||
rowMargin: 2
|
||||
rowSpacing: 2
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
id: roomEntry
|
||||
|
||||
width: parent.width
|
||||
|
||||
placeholderText: qsTr("Enter additional rooms not in the list yet...")
|
||||
|
||||
color: palette.text
|
||||
onTextEdited: {
|
||||
roomCompleter.completer.searchString = text;
|
||||
}
|
||||
Keys.onPressed: {
|
||||
if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
|
||||
event.accepted = true;
|
||||
roomCompleter.up();
|
||||
} else if (event.key == Qt.Key_Down || event.key == Qt.Key_Tab) {
|
||||
event.accepted = true;
|
||||
if (event.key == Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))
|
||||
roomCompleter.up();
|
||||
else
|
||||
roomCompleter.down();
|
||||
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
|
||||
roomCompleter.finishCompletion();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onCompletionSelected(id) {
|
||||
console.log("selected: " + id);
|
||||
roomSettings.allowedRoomsModel.addRoom(id);
|
||||
roomEntry.clear();
|
||||
}
|
||||
|
||||
function onCountChanged() {
|
||||
if (roomCompleter.count > 0 && (roomCompleter.currentIndex < 0 || roomCompleter.currentIndex >= roomCompleter.count))
|
||||
roomCompleter.currentIndex = 0;
|
||||
|
||||
}
|
||||
|
||||
target: roomCompleter
|
||||
}
|
||||
|
||||
}
|
||||
width: 450
|
||||
|
||||
footer: DialogButtonBox {
|
||||
id: dbb
|
||||
|
||||
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
|
||||
|
||||
onAccepted: {
|
||||
roomSettings.applyAllowedFromModel();
|
||||
allowedDialog.close();
|
||||
|
@ -167,4 +35,123 @@ ApplicationWindow {
|
|||
onRejected: allowedDialog.close()
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
|
||||
onActivated: roomSettingsDialog.close()
|
||||
}
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
spacing: 0
|
||||
|
||||
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("List of rooms that allow access to this room. Anyone who is in any of those rooms can join this room.")
|
||||
}
|
||||
ListView {
|
||||
id: view
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
cacheBuffer: 50
|
||||
clip: true
|
||||
model: roomSettings.allowedRoomsModel
|
||||
spacing: 4
|
||||
|
||||
delegate: RowLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
color: palette.text
|
||||
text: model.name
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
color: palette.buttonText
|
||||
text: model.isParent ? qsTr("Parent community") : qsTr("Other room")
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
}
|
||||
ToggleButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
checked: model.allowed
|
||||
|
||||
onCheckedChanged: model.allowed = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
Column {
|
||||
id: roomEntryCompleter
|
||||
|
||||
Layout.fillWidth: true
|
||||
spacing: 1
|
||||
z: 5
|
||||
|
||||
Completer {
|
||||
id: roomCompleter
|
||||
|
||||
avatarHeight: Nheko.avatarSize / 2
|
||||
avatarWidth: Nheko.avatarSize / 2
|
||||
bottomToTop: true
|
||||
centerRowContent: false
|
||||
completerName: "room"
|
||||
fullWidth: true
|
||||
roomId: allowedDialog.roomSettings.roomId
|
||||
rowMargin: 2
|
||||
rowSpacing: 2
|
||||
visible: roomEntry.text.length > 0
|
||||
width: parent.width
|
||||
}
|
||||
MatrixTextField {
|
||||
id: roomEntry
|
||||
|
||||
color: palette.text
|
||||
placeholderText: qsTr("Enter additional rooms not in the list yet...")
|
||||
width: parent.width
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
|
||||
event.accepted = true;
|
||||
roomCompleter.up();
|
||||
} else if (event.key == Qt.Key_Down || event.key == Qt.Key_Tab) {
|
||||
event.accepted = true;
|
||||
if (event.key == Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))
|
||||
roomCompleter.up();
|
||||
else
|
||||
roomCompleter.down();
|
||||
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
|
||||
roomCompleter.finishCompletion();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
onTextEdited: {
|
||||
roomCompleter.completer.searchString = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
function onCompletionSelected(id) {
|
||||
console.log("selected: " + id);
|
||||
roomSettings.allowedRoomsModel.addRoom(id);
|
||||
roomEntry.clear();
|
||||
}
|
||||
function onCountChanged() {
|
||||
if (roomCompleter.count > 0 && (roomCompleter.currentIndex < 0 || roomCompleter.currentIndex >= roomCompleter.count))
|
||||
roomCompleter.currentIndex = 0;
|
||||
}
|
||||
|
||||
target: roomCompleter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,119 +15,18 @@ ApplicationWindow {
|
|||
|
||||
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
|
||||
width: 350
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
height: content.implicitHeight + Nheko.paddingLarge + footer.implicitHeight
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
modality: Qt.WindowModal
|
||||
title: summary.isSpace ? qsTr("Confirm community join") : qsTr("Confirm room join")
|
||||
width: 350
|
||||
|
||||
footer: DialogButtonBox {
|
||||
id: dbb
|
||||
|
||||
standardButtons: DialogButtonBox.Cancel
|
||||
|
||||
onAccepted: {
|
||||
summary.reason = reason.text;
|
||||
summary.join();
|
||||
|
@ -138,11 +37,102 @@ ApplicationWindow {
|
|||
}
|
||||
|
||||
Button {
|
||||
text: summary.isKnockOnly ? qsTr("Knock") : qsTr("Join")
|
||||
enabled: input.text.match("#.+?:.{3,}")
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,13 +11,31 @@ import im.nheko
|
|||
|
||||
ApplicationWindow {
|
||||
id: createDirectRoot
|
||||
title: qsTr("Create Direct Chat")
|
||||
|
||||
property bool otherUserHasE2ee: profile ? profile.deviceList.rowCount() > 0 : true
|
||||
property var profile
|
||||
property bool otherUserHasE2ee: profile? profile.deviceList.rowCount() > 0 : true
|
||||
minimumHeight: layout.implicitHeight + footer.implicitHeight + Nheko.paddingLarge*2
|
||||
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
minimumHeight: layout.implicitHeight + footer.implicitHeight + Nheko.paddingLarge * 2
|
||||
minimumWidth: Math.max(footer.implicitWidth, layout.implicitWidth)
|
||||
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: {
|
||||
userID.forceActiveFocus();
|
||||
|
@ -25,91 +43,82 @@ ApplicationWindow {
|
|||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
|
||||
onActivated: createDirectRoot.close()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Nheko.paddingLarge
|
||||
spacing: userID.height/4
|
||||
spacing: userID.height / 4
|
||||
|
||||
GridLayout {
|
||||
Layout.fillWidth: true
|
||||
rows: 2
|
||||
columnSpacing: Nheko.paddingMedium
|
||||
columns: 2
|
||||
rowSpacing: Nheko.paddingSmall
|
||||
columnSpacing: Nheko.paddingMedium
|
||||
rows: 2
|
||||
|
||||
Avatar {
|
||||
Layout.rowSpan: 2
|
||||
Layout.preferredWidth: Nheko.avatarSize
|
||||
Layout.preferredHeight: Nheko.avatarSize
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
userid: profile? profile.userid : ""
|
||||
url: profile? profile.avatarUrl.replace("mxc://", "image://MxcImage/") : null
|
||||
displayName: profile? profile.displayName : ""
|
||||
Layout.preferredHeight: Nheko.avatarSize
|
||||
Layout.preferredWidth: Nheko.avatarSize
|
||||
Layout.rowSpan: 2
|
||||
displayName: profile ? profile.displayName : ""
|
||||
enabled: false
|
||||
url: profile ? profile.avatarUrl.replace("mxc://", "image://MxcImage/") : null
|
||||
userid: profile ? profile.userid : ""
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: profile? profile.displayName : ""
|
||||
color: TimelineManager.userColor(userID.text, palette.window)
|
||||
font.pointSize: fontMetrics.font.pointSize
|
||||
text: profile ? profile.displayName : ""
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: userID.text
|
||||
color: palette.buttonText
|
||||
font.pointSize: fontMetrics.font.pointSize * 0.9
|
||||
text: userID.text
|
||||
}
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
id: userID
|
||||
|
||||
property bool isValidMxid: text.match("@.+?:.{3,}")
|
||||
|
||||
Layout.fillWidth: true
|
||||
focus: true
|
||||
label: qsTr("User to invite")
|
||||
placeholderText: qsTr("@user:server.tld")
|
||||
|
||||
onTextChanged: {
|
||||
// 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,}")) {
|
||||
profile = TimelineManager.getGlobalUserProfile(text);
|
||||
} else
|
||||
profile = null;
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: qsTr("Encryption")
|
||||
Layout.fillWidth: true
|
||||
color: palette.text
|
||||
text: qsTr("Encryption")
|
||||
}
|
||||
ToggleButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
id: encryption
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
checked: otherUserHasE2ee
|
||||
}
|
||||
}
|
||||
|
||||
Item {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()
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,11 +14,32 @@ ApplicationWindow {
|
|||
|
||||
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
|
||||
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: {
|
||||
newRoomName.forceActiveFocus();
|
||||
|
@ -26,10 +47,12 @@ ApplicationWindow {
|
|||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
|
||||
onActivated: createRoomRoot.close()
|
||||
}
|
||||
GridLayout {
|
||||
id: rootLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Nheko.paddingLarge
|
||||
columns: 2
|
||||
|
@ -37,127 +60,118 @@ ApplicationWindow {
|
|||
|
||||
MatrixTextField {
|
||||
id: newRoomName
|
||||
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
|
||||
focus: true
|
||||
label: qsTr("Name")
|
||||
placeholderText: qsTr("No name")
|
||||
}
|
||||
MatrixTextField {
|
||||
id: newRoomTopic
|
||||
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
|
||||
focus: true
|
||||
label: qsTr("Topic")
|
||||
placeholderText: qsTr("No topic")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredHeight: newRoomName.height / 2
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
|
||||
Label {
|
||||
Layout.preferredWidth: implicitWidth
|
||||
text: "#"
|
||||
color: palette.text
|
||||
text: "#"
|
||||
}
|
||||
MatrixTextField {
|
||||
id: newRoomAlias
|
||||
|
||||
focus: true
|
||||
placeholderText: qsTr("Alias")
|
||||
}
|
||||
Label {
|
||||
Layout.preferredWidth: implicitWidth
|
||||
property string userName: userInfoGrid.profile.userid
|
||||
text: userName.substring(userName.indexOf(":"))
|
||||
|
||||
Layout.preferredWidth: implicitWidth
|
||||
color: palette.text
|
||||
text: userName.substring(userName.indexOf(":"))
|
||||
}
|
||||
}
|
||||
Label {
|
||||
Layout.preferredWidth: implicitWidth
|
||||
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
|
||||
text: qsTr("Public")
|
||||
|
||||
HoverHandler {
|
||||
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 {
|
||||
id: isPublic
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.preferredWidth: implicitWidth
|
||||
id: isPublic
|
||||
checked: false
|
||||
}
|
||||
Label {
|
||||
visible: !space
|
||||
Layout.preferredWidth: implicitWidth
|
||||
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
|
||||
text: qsTr("Trusted")
|
||||
visible: !space
|
||||
|
||||
HoverHandler {
|
||||
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 {
|
||||
visible: !space
|
||||
id: isTrusted
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.preferredWidth: implicitWidth
|
||||
id: isTrusted
|
||||
checked: false
|
||||
enabled: !isPublic.checked
|
||||
visible: !space
|
||||
}
|
||||
Label {
|
||||
visible: !space
|
||||
Layout.preferredWidth: implicitWidth
|
||||
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
|
||||
text: qsTr("Encryption")
|
||||
visible: !space
|
||||
|
||||
HoverHandler {
|
||||
id: encryptionHover
|
||||
|
||||
}
|
||||
ToolTip.visible: encryptionHover.hovered
|
||||
ToolTip.text: qsTr("Caution: Encryption cannot be disabled")
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
}
|
||||
ToggleButton {
|
||||
visible: !space
|
||||
id: isEncrypted
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.preferredWidth: implicitWidth
|
||||
id: isEncrypted
|
||||
checked: false
|
||||
visible: !space
|
||||
}
|
||||
|
||||
Item {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();
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,157 +11,151 @@ import im.nheko
|
|||
ApplicationWindow {
|
||||
id: dialog
|
||||
|
||||
property string roomid: ""
|
||||
property string roomName: ""
|
||||
property var onAccepted: undefined
|
||||
property string roomName: ""
|
||||
property string roomid: ""
|
||||
|
||||
modality: Qt.NonModal
|
||||
flags: Qt.Dialog | Qt.WindowTitleHint
|
||||
width: 275
|
||||
height: 330
|
||||
minimumWidth: 250
|
||||
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 {
|
||||
id: eventExpiry
|
||||
|
||||
roomid: dialog.roomid
|
||||
}
|
||||
|
||||
title: {
|
||||
if (roomid) {
|
||||
return qsTr("Event expiration for %1").arg(roomName);
|
||||
}
|
||||
else {
|
||||
return qsTr("Event expiration");
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
|
||||
onActivated: dbb.rejected()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Nheko.paddingMedium
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
anchors.fill: parent
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
MatrixText {
|
||||
id: promptLabel
|
||||
|
||||
Layout.fillHeight: false
|
||||
Layout.fillWidth: true
|
||||
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.2)
|
||||
text: {
|
||||
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);
|
||||
}
|
||||
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.");
|
||||
}
|
||||
}
|
||||
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.2)
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: false
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
columns: 2
|
||||
rowSpacing: Nheko.paddingMedium
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
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.visible: hh1.hovered
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Expire events after X days")
|
||||
|
||||
HoverHandler {
|
||||
id: hh1
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
from: 0
|
||||
to: 1000
|
||||
stepSize: 1
|
||||
value: eventExpiry.expireEventsAfterDays
|
||||
onValueChanged: eventExpiry.expireEventsAfterDays = value
|
||||
editable: true
|
||||
}
|
||||
from: 0
|
||||
stepSize: 1
|
||||
to: 1000
|
||||
value: eventExpiry.expireEventsAfterDays
|
||||
|
||||
onValueChanged: eventExpiry.expireEventsAfterDays = value
|
||||
}
|
||||
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.visible: hh2.hovered
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Only keep latest X events")
|
||||
|
||||
HoverHandler {
|
||||
id: hh2
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SpinBox {
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
from: 0
|
||||
to: 1000000
|
||||
stepSize: 1
|
||||
value: eventExpiry.expireEventsAfterCount
|
||||
onValueChanged: eventExpiry.expireEventsAfterCount = value
|
||||
editable: true
|
||||
}
|
||||
from: 0
|
||||
stepSize: 1
|
||||
to: 1000000
|
||||
value: eventExpiry.expireEventsAfterCount
|
||||
|
||||
onValueChanged: eventExpiry.expireEventsAfterCount = value
|
||||
}
|
||||
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.visible: hh3.hovered
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Always keep latest X events")
|
||||
|
||||
HoverHandler {
|
||||
id: hh3
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SpinBox {
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
from: 0
|
||||
to: 1000000
|
||||
stepSize: 1
|
||||
value: eventExpiry.protectLatestEvents
|
||||
onValueChanged: eventExpiry.protectLatestEvents = value
|
||||
editable: true
|
||||
}
|
||||
from: 0
|
||||
stepSize: 1
|
||||
to: 1000000
|
||||
value: eventExpiry.protectLatestEvents
|
||||
|
||||
onValueChanged: eventExpiry.protectLatestEvents = value
|
||||
}
|
||||
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.visible: hh4.hovered
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Include state events")
|
||||
|
||||
HoverHandler {
|
||||
id: hh4
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
checked: eventExpiry.expireStateEvents
|
||||
|
||||
onToggled: eventExpiry.expireStateEvents = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: DialogButtonBox {
|
||||
id: dbb
|
||||
|
||||
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
|
||||
onAccepted: {
|
||||
eventExpiry.save();
|
||||
dialog.close();
|
||||
}
|
||||
onRejected: dialog.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -15,49 +15,46 @@ ApplicationWindow {
|
|||
fallback.confirm();
|
||||
fallbackRoot.close();
|
||||
}
|
||||
|
||||
function reject() {
|
||||
fallback.cancel();
|
||||
fallbackRoot.close();
|
||||
}
|
||||
|
||||
color: palette.window
|
||||
title: qsTr("Fallback authentication")
|
||||
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
height: msg.implicitHeight + footer.implicitHeight
|
||||
title: qsTr("Fallback authentication")
|
||||
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 {
|
||||
onAccepted: fallbackRoot.accept()
|
||||
onRejected: fallbackRoot.reject()
|
||||
|
||||
Button {
|
||||
text: qsTr("Open Fallback in Browser")
|
||||
|
||||
onClicked: fallback.openFallbackAuth()
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Cancel")
|
||||
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
|
||||
text: qsTr("Cancel")
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Confirm")
|
||||
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.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,119 +11,115 @@ import im.nheko 1.0
|
|||
ApplicationWindow {
|
||||
id: hiddenEventsDialog
|
||||
|
||||
property string roomid: ""
|
||||
property string roomName: ""
|
||||
property var onAccepted: undefined
|
||||
property string roomName: ""
|
||||
property string roomid: ""
|
||||
|
||||
modality: Qt.NonModal
|
||||
flags: Qt.Dialog | Qt.WindowTitleHint
|
||||
width: 275
|
||||
height: 220
|
||||
minimumWidth: 250
|
||||
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 {
|
||||
id: hiddenEvents
|
||||
|
||||
roomid: hiddenEventsDialog.roomid
|
||||
}
|
||||
|
||||
title: {
|
||||
if (roomid) {
|
||||
return qsTr("Hidden events for %1").arg(roomName);
|
||||
}
|
||||
else {
|
||||
return qsTr("Hidden events");
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
|
||||
onActivated: dbb.rejected()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Nheko.paddingMedium
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
anchors.fill: parent
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
MatrixText {
|
||||
id: promptLabel
|
||||
|
||||
Layout.fillHeight: false
|
||||
Layout.fillWidth: true
|
||||
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.2)
|
||||
text: {
|
||||
if (roomid) {
|
||||
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:");
|
||||
}
|
||||
}
|
||||
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.2)
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: false
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
columns: 2
|
||||
rowSpacing: Nheko.paddingMedium
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("User events")
|
||||
Layout.fillWidth: true
|
||||
ToolTip.text: qsTr("Joins, leaves, avatar and name changes, bans, …")
|
||||
ToolTip.visible: hh1.hovered
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("User events")
|
||||
|
||||
HoverHandler {
|
||||
id: hh1
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.Member)
|
||||
|
||||
onToggled: hiddenEvents.toggle(MtxEvent.Member)
|
||||
}
|
||||
|
||||
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.visible: hh2.hovered
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Power level changes")
|
||||
|
||||
HoverHandler {
|
||||
id: hh2
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.PowerLevels)
|
||||
|
||||
onToggled: hiddenEvents.toggle(MtxEvent.PowerLevels)
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("Stickers")
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Stickers")
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.Sticker)
|
||||
|
||||
onToggled: hiddenEvents.toggle(MtxEvent.Sticker)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: DialogButtonBox {
|
||||
id: dbb
|
||||
|
||||
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
|
||||
onAccepted: {
|
||||
hiddenEvents.save();
|
||||
hiddenEventsDialog.close();
|
||||
}
|
||||
onRejected: hiddenEventsDialog.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,72 +13,74 @@ import "../"
|
|||
Window {
|
||||
id: ignoredUsers
|
||||
|
||||
title: qsTr("Ignored users")
|
||||
color: palette.window
|
||||
flags: Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
height: 650
|
||||
width: 420
|
||||
minimumHeight: 420
|
||||
color: palette.window
|
||||
title: qsTr("Ignored users")
|
||||
width: 420
|
||||
|
||||
ListView {
|
||||
id: view
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: Nheko.paddingMedium
|
||||
footerPositioning: ListView.OverlayFooter
|
||||
|
||||
model: TimelineManager.ignoredUsers
|
||||
header: ColumnLayout {
|
||||
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!).")
|
||||
}
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
Item { Layout.preferredHeight: Nheko.paddingLarge }
|
||||
}
|
||||
delegate: RowLayout {
|
||||
property var profile: TimelineManager.getGlobalUserProfile(modelData)
|
||||
|
||||
width: view.width
|
||||
|
||||
Avatar {
|
||||
enabled: false
|
||||
displayName: profile.displayName
|
||||
userid: profile.userid
|
||||
enabled: false
|
||||
url: profile.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
userid: profile.userid
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
color: palette.text
|
||||
elide: Text.ElideRight
|
||||
text: modelData
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
Layout.preferredHeight: 24
|
||||
Layout.preferredWidth: 24
|
||||
image: ":/icons/icons/ui/dismiss.svg"
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Stop Ignoring.")
|
||||
ToolTip.visible: hovered
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/dismiss.svg"
|
||||
|
||||
onClicked: profile.ignored = false
|
||||
}
|
||||
}
|
||||
footer: DialogButtonBox {
|
||||
z: 2
|
||||
width: view.width
|
||||
alignment: Qt.AlignRight
|
||||
standardButtons: DialogButtonBox.Ok
|
||||
onAccepted: ignoredUsers.close()
|
||||
width: view.width
|
||||
z: 2
|
||||
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,33 +4,32 @@
|
|||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
|
||||
import ".."
|
||||
|
||||
import im.nheko 1.0
|
||||
|
||||
Window {
|
||||
id: imageOverlay
|
||||
|
||||
required property string url
|
||||
required property string eventId
|
||||
required property Room room
|
||||
required property int originalWidth
|
||||
required property double proportionalHeight
|
||||
|
||||
flags: Qt.FramelessWindowHint
|
||||
required property Room room
|
||||
required property string url
|
||||
|
||||
//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")
|
||||
|
||||
Shortcut {
|
||||
sequences: [StandardKey.Cancel]
|
||||
|
||||
onActivated: imageOverlay.close()
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequences: [StandardKey.Copy]
|
||||
|
||||
onActivated: {
|
||||
if (room) {
|
||||
room.copyMedia(eventId);
|
||||
|
@ -39,94 +38,85 @@ Window {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onSingleTapped: imageOverlay.close();
|
||||
onSingleTapped: imageOverlay.close()
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
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 imgSrcWidth: (imageOverlay.originalWidth && imageOverlay.originalWidth > 100) ? imageOverlay.originalWidth : Screen.width
|
||||
|
||||
height: Math.min(parent.height || Screen.height, imgSrcHeight)
|
||||
width: Math.min(parent.width || Screen.width, imgSrcWidth)
|
||||
|
||||
x: (parent.width - width) / 2
|
||||
y: (parent.height - height) / 2
|
||||
|
||||
onScaleChanged: {
|
||||
if (scale > 10)
|
||||
scale = 10;
|
||||
if (scale < 0.1)
|
||||
scale = 0.1;
|
||||
}
|
||||
|
||||
Image {
|
||||
id: img
|
||||
|
||||
visible: !mxcimage.loaded
|
||||
property bool loaded: status == Image.Ready
|
||||
|
||||
anchors.fill: parent
|
||||
source: url.replace("mxc://", "image://MxcImage/")
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
mipmap: true
|
||||
property bool loaded: status == Image.Ready
|
||||
smooth: true
|
||||
source: url.replace("mxc://", "image://MxcImage/")
|
||||
visible: !mxcimage.loaded
|
||||
}
|
||||
|
||||
MxcAnimatedImage {
|
||||
id: mxcimage
|
||||
|
||||
visible: loaded
|
||||
anchors.fill: parent
|
||||
roomm: imageOverlay.room
|
||||
play: !Settings.animateImagesOnHover || mouseArea.hovered
|
||||
eventId: imageOverlay.eventId
|
||||
}
|
||||
|
||||
onScaleChanged: {
|
||||
if (scale > 10) scale = 10;
|
||||
if (scale < 0.1) scale = 0.1
|
||||
play: !Settings.animateImagesOnHover || mouseArea.hovered
|
||||
roomm: imageOverlay.room
|
||||
visible: loaded
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
|
||||
PinchHandler {
|
||||
target: imgContainer
|
||||
maximumScale: 10
|
||||
minimumScale: 0.1
|
||||
target: imgContainer
|
||||
}
|
||||
|
||||
WheelHandler {
|
||||
property: "scale"
|
||||
// workaround for QTBUG-87646 / QTBUG-112394 / QTBUG-112432:
|
||||
// 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
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
property: "scale"
|
||||
target: imgContainer
|
||||
}
|
||||
|
||||
DragHandler {
|
||||
target: imgContainer
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: mouseArea
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Row {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Nheko.paddingLarge
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
ImageButton {
|
||||
height: 48
|
||||
width: 48
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/copy.svg"
|
||||
width: 48
|
||||
|
||||
//ToolTip.visible: hovered
|
||||
//ToolTip.delay: Nheko.tooltipDelay
|
||||
|
@ -142,12 +132,11 @@ Window {
|
|||
imageOverlay.close();
|
||||
}
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
height: 48
|
||||
width: 48
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/download.svg"
|
||||
width: 48
|
||||
|
||||
//ToolTip.visible: hovered
|
||||
//ToolTip.delay: Nheko.tooltipDelay
|
||||
|
@ -165,16 +154,15 @@ Window {
|
|||
}
|
||||
ImageButton {
|
||||
height: 48
|
||||
width: 48
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/dismiss.svg"
|
||||
width: 48
|
||||
|
||||
//ToolTip.visible: hovered
|
||||
//ToolTip.delay: Nheko.tooltipDelay
|
||||
//ToolTip.text: qsTr("Close")
|
||||
|
||||
|
||||
onClicked: imageOverlay.close()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,34 +14,46 @@ ApplicationWindow {
|
|||
id: win
|
||||
|
||||
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
|
||||
property SingleImagePackModel imagePack
|
||||
property int currentImageIndex: -1
|
||||
property SingleImagePackModel imagePack
|
||||
readonly property int stickerDim: 128
|
||||
readonly property int stickerDimPad: 128 + Nheko.paddingSmall
|
||||
|
||||
title: qsTr("Editing image pack")
|
||||
height: 600
|
||||
width: 600
|
||||
color: palette.base
|
||||
modality: Qt.WindowModal
|
||||
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 {
|
||||
id: adaptiveView
|
||||
|
||||
anchors.fill: parent
|
||||
singlePageMode: false
|
||||
pageIndex: 0
|
||||
singlePageMode: false
|
||||
|
||||
AdaptiveLayoutElement {
|
||||
id: packlistC
|
||||
|
||||
visible: Settings.groupView
|
||||
minimumWidth: 200
|
||||
collapsedWidth: 200
|
||||
preferredWidth: 300
|
||||
maximumWidth: 300
|
||||
clip: true
|
||||
collapsedWidth: 200
|
||||
maximumWidth: 300
|
||||
minimumWidth: 200
|
||||
preferredWidth: 300
|
||||
visible: Settings.groupView
|
||||
|
||||
ListView {
|
||||
//required property bool isEmote
|
||||
|
@ -49,75 +61,67 @@ ApplicationWindow {
|
|||
|
||||
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 {
|
||||
id: packItem
|
||||
|
||||
property color background: palette.window
|
||||
property color importantText: palette.text
|
||||
property color unimportantText: palette.buttonText
|
||||
required property string body
|
||||
property color bubbleBackground: palette.highlight
|
||||
property color bubbleText: palette.highlightedText
|
||||
property color importantText: palette.text
|
||||
required property string shortCode
|
||||
property color unimportantText: palette.buttonText
|
||||
required property string url
|
||||
required property string body
|
||||
|
||||
title: shortCode
|
||||
subtitle: body
|
||||
avatarUrl: url
|
||||
selectedIndex: currentImageIndex
|
||||
crop: false
|
||||
selectedIndex: currentImageIndex
|
||||
subtitle: body
|
||||
title: shortCode
|
||||
|
||||
TapHandler {
|
||||
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 {
|
||||
id: packinfoC
|
||||
|
||||
|
@ -127,211 +131,189 @@ ApplicationWindow {
|
|||
GridLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
visible: currentImageIndex == -1
|
||||
enabled: visible
|
||||
columns: 2
|
||||
enabled: visible
|
||||
rowSpacing: Nheko.paddingLarge
|
||||
visible: currentImageIndex == -1
|
||||
|
||||
Avatar {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.columnSpan: 2
|
||||
url: imagePack.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
displayName: imagePack.packname
|
||||
roomid: imagePack.statekey
|
||||
Layout.preferredHeight: 130
|
||||
Layout.preferredWidth: 130
|
||||
crop: false
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
displayName: imagePack.packname
|
||||
roomid: imagePack.statekey
|
||||
url: imagePack.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
|
||||
ImageButton {
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Change the overview image for this pack")
|
||||
ToolTip.visible: hovered
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.leftMargin: Nheko.paddingMedium
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Nheko.paddingMedium
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/edit.svg"
|
||||
|
||||
onClicked: addAvatarDialog.open()
|
||||
|
||||
FileDialog {
|
||||
id: addAvatarDialog
|
||||
|
||||
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
|
||||
fileMode: FileDialog.OpenFile
|
||||
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
|
||||
nameFilters: [qsTr("Overview Image (*.png *.webp *.jpg *.jpeg)")]
|
||||
title: qsTr("Select overview image for pack")
|
||||
|
||||
onAccepted: imagePack.setAvatar(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
id: statekeyField
|
||||
|
||||
visible: imagePack.roomid
|
||||
Layout.fillWidth: true
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("State key")
|
||||
text: imagePack.statekey
|
||||
visible: imagePack.roomid
|
||||
|
||||
onTextEdited: imagePack.statekey = text
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
Layout.fillWidth: true
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("Packname")
|
||||
text: imagePack.packname
|
||||
|
||||
onTextEdited: imagePack.packname = text
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
Layout.fillWidth: true
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("Attribution")
|
||||
text: imagePack.attribution
|
||||
|
||||
onTextEdited: imagePack.attribution = text
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
Layout.margins: statekeyField.textPadding
|
||||
font.weight: Font.DemiBold
|
||||
font.letterSpacing: font.pixelSize * 0.02
|
||||
font.weight: Font.DemiBold
|
||||
text: qsTr("Use as Emoji")
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
checked: imagePack.isEmotePack
|
||||
onCheckedChanged: imagePack.isEmotePack = checked
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
checked: imagePack.isEmotePack
|
||||
|
||||
onCheckedChanged: imagePack.isEmotePack = checked
|
||||
}
|
||||
MatrixText {
|
||||
Layout.margins: statekeyField.textPadding
|
||||
font.weight: Font.DemiBold
|
||||
font.letterSpacing: font.pixelSize * 0.02
|
||||
font.weight: Font.DemiBold
|
||||
text: qsTr("Use as Sticker")
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
checked: imagePack.isStickerPack
|
||||
onCheckedChanged: imagePack.isStickerPack = checked
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
checked: imagePack.isStickerPack
|
||||
|
||||
onCheckedChanged: imagePack.isStickerPack = checked
|
||||
}
|
||||
Item {
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
visible: currentImageIndex >= 0
|
||||
enabled: visible
|
||||
columns: 2
|
||||
enabled: visible
|
||||
rowSpacing: Nheko.paddingLarge
|
||||
visible: currentImageIndex >= 0
|
||||
|
||||
Avatar {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
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.preferredWidth: 130
|
||||
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 {
|
||||
Layout.fillWidth: true
|
||||
Layout.columnSpan: 2
|
||||
label: qsTr("Shortcode")
|
||||
property int bindingCounter: 0
|
||||
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("Shortcode")
|
||||
text: bindingCounter, imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
|
||||
|
||||
onTextEdited: {
|
||||
imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode);
|
||||
// force text field to update in case the model disagreed with the new value.
|
||||
bindingCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
id: bodyField
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("Body")
|
||||
text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Body)
|
||||
|
||||
onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.Body)
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
Layout.margins: bodyField.textPadding
|
||||
font.weight: Font.DemiBold
|
||||
font.letterSpacing: font.pixelSize * 0.02
|
||||
font.weight: Font.DemiBold
|
||||
text: qsTr("Use as Emoji")
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsEmote)
|
||||
onCheckedChanged: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsEmote)
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsEmote)
|
||||
|
||||
onCheckedChanged: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsEmote)
|
||||
}
|
||||
MatrixText {
|
||||
Layout.margins: bodyField.textPadding
|
||||
font.weight: Font.DemiBold
|
||||
font.letterSpacing: font.pixelSize * 0.02
|
||||
font.weight: Font.DemiBold
|
||||
text: qsTr("Use as Sticker")
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsSticker)
|
||||
onCheckedChanged: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsSticker)
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsSticker)
|
||||
|
||||
onCheckedChanged: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsSticker)
|
||||
}
|
||||
MatrixText {
|
||||
Layout.margins: bodyField.textPadding
|
||||
font.weight: Font.DemiBold
|
||||
font.letterSpacing: font.pixelSize * 0.02
|
||||
font.weight: Font.DemiBold
|
||||
text: qsTr("Remove from pack")
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: qsTr("Remove")
|
||||
|
||||
onClicked: {
|
||||
let temp = currentImageIndex;
|
||||
currentImageIndex = -1;
|
||||
imagePack.remove(temp);
|
||||
}
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
footer: DialogButtonBox {
|
||||
id: buttons
|
||||
|
||||
standardButtons: DialogButtonBox.Save | DialogButtonBox.Cancel
|
||||
onAccepted: {
|
||||
imagePack.save();
|
||||
win.close();
|
||||
}
|
||||
onRejected: win.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,95 +12,74 @@ import im.nheko 1.0
|
|||
ApplicationWindow {
|
||||
id: win
|
||||
|
||||
property Room room
|
||||
property ImagePackListModel packlist
|
||||
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
|
||||
property SingleImagePackModel currentPack: packlist.packAt(currentPackIndex)
|
||||
property int currentPackIndex: 0
|
||||
property ImagePackListModel packlist
|
||||
property Room room
|
||||
readonly property int stickerDim: 128
|
||||
readonly property int stickerDimPad: 128 + Nheko.paddingSmall
|
||||
|
||||
title: qsTr("Image pack settings")
|
||||
height: 600
|
||||
width: 800
|
||||
color: palette.base
|
||||
modality: Qt.NonModal
|
||||
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 {
|
||||
id: packEditor
|
||||
|
||||
ImagePackEditorDialog {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AdaptiveLayout {
|
||||
id: adaptiveView
|
||||
|
||||
anchors.fill: parent
|
||||
singlePageMode: false
|
||||
pageIndex: 0
|
||||
singlePageMode: false
|
||||
|
||||
AdaptiveLayoutElement {
|
||||
id: packlistC
|
||||
|
||||
visible: Settings.groupView
|
||||
minimumWidth: 200
|
||||
collapsedWidth: 200
|
||||
preferredWidth: 300
|
||||
maximumWidth: 300
|
||||
minimumWidth: 200
|
||||
preferredWidth: 300
|
||||
visible: Settings.groupView
|
||||
|
||||
ListView {
|
||||
model: packlist
|
||||
clip: true
|
||||
|
||||
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
}
|
||||
model: packlist
|
||||
|
||||
delegate: AvatarListTile {
|
||||
id: packItem
|
||||
|
||||
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
|
||||
required property string displayName
|
||||
required property bool fromAccountData
|
||||
required property bool fromCurrentRoom
|
||||
required property bool fromSpace
|
||||
property color importantText: palette.text
|
||||
required property string statekey
|
||||
property color unimportantText: palette.buttonText
|
||||
|
||||
title: displayName
|
||||
roomid: statekey
|
||||
selectedIndex: currentPackIndex
|
||||
subtitle: {
|
||||
if (fromAccountData)
|
||||
return qsTr("Private pack");
|
||||
|
@ -111,19 +90,42 @@ ApplicationWindow {
|
|||
else
|
||||
return qsTr("Globally enabled pack");
|
||||
}
|
||||
selectedIndex: currentPackIndex
|
||||
roomid: statekey
|
||||
title: displayName
|
||||
|
||||
TapHandler {
|
||||
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 {
|
||||
id: packinfoC
|
||||
|
||||
|
@ -133,9 +135,9 @@ ApplicationWindow {
|
|||
ColumnLayout {
|
||||
id: packinfo
|
||||
|
||||
property string packName: currentPack ? currentPack.packname : ""
|
||||
property string attribution: currentPack ? currentPack.attribution : ""
|
||||
property string avatarUrl: currentPack ? currentPack.avatarUrl : ""
|
||||
property string packName: currentPack ? currentPack.packname : ""
|
||||
property string statekey: currentPack ? currentPack.statekey : ""
|
||||
|
||||
anchors.fill: parent
|
||||
|
@ -143,119 +145,94 @@ ApplicationWindow {
|
|||
spacing: Nheko.paddingLarge
|
||||
|
||||
Avatar {
|
||||
url: packinfo.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
displayName: packinfo.packName
|
||||
roomid: packinfo.statekey
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredHeight: 100
|
||||
Layout.preferredWidth: 100
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
displayName: packinfo.packName
|
||||
enabled: false
|
||||
roomid: packinfo.statekey
|
||||
url: packinfo.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: packinfo.packName
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2
|
||||
font.pixelSize: Math.ceil(fontMetrics.pixelSize * 1.1)
|
||||
horizontalAlignment: TextEdit.AlignHCenter
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2
|
||||
text: packinfo.packName
|
||||
textFormat: TextEdit.PlainText
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: packinfo.attribution
|
||||
wrapMode: TextEdit.Wrap
|
||||
horizontalAlignment: TextEdit.AlignHCenter
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2
|
||||
horizontalAlignment: TextEdit.AlignHCenter
|
||||
text: packinfo.attribution
|
||||
textFormat: TextEdit.PlainText
|
||||
wrapMode: TextEdit.Wrap
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: currentPack && currentPack.roomid != ""
|
||||
columns: 2
|
||||
rowSpacing: Nheko.paddingMedium
|
||||
visible: currentPack && currentPack.roomid != ""
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("Enable globally")
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
ToolTip.text: qsTr("Enables this pack to be used in all rooms")
|
||||
checked: currentPack ? currentPack.isGloballyEnabled : false
|
||||
|
||||
onCheckedChanged: currentPack.isGloballyEnabled = checked
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("Edit")
|
||||
enabled: currentPack.canEdit
|
||||
text: qsTr("Edit")
|
||||
|
||||
onClicked: {
|
||||
var dialog = packEditor.createObject(timelineRoot, {
|
||||
"imagePack": currentPack
|
||||
});
|
||||
"imagePack": currentPack
|
||||
});
|
||||
dialog.show();
|
||||
timelineRoot.destroyOnClose(dialog);
|
||||
}
|
||||
}
|
||||
|
||||
GridView {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
model: currentPack
|
||||
cellWidth: stickerDimPad
|
||||
cellHeight: stickerDimPad
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
cacheBuffer: 500
|
||||
cellHeight: stickerDimPad
|
||||
cellWidth: stickerDimPad
|
||||
clip: true
|
||||
currentIndex: -1 // prevent sorting from stealing focus
|
||||
cacheBuffer: 500
|
||||
|
||||
model: currentPack
|
||||
|
||||
// Individual emoji
|
||||
delegate: AbstractButton {
|
||||
width: stickerDim
|
||||
height: stickerDim
|
||||
hoverEnabled: true
|
||||
ToolTip.text: ":" + model.shortCode + ": - " + model.body
|
||||
ToolTip.visible: hovered
|
||||
|
||||
contentItem: Image {
|
||||
height: stickerDim
|
||||
width: stickerDim
|
||||
source: model.url.replace("mxc://", "image://MxcImage/") + "?scale"
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
height: stickerDim
|
||||
hoverEnabled: true
|
||||
width: stickerDim
|
||||
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
color: hovered ? palette.highlight : 'transparent'
|
||||
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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,52 +11,27 @@ import im.nheko 1.0
|
|||
ApplicationWindow {
|
||||
id: inputDialog
|
||||
|
||||
property alias prompt: promptLabel.text
|
||||
property alias echoMode: statusInput.echoMode
|
||||
signal accepted(text: string)
|
||||
property alias prompt: promptLabel.text
|
||||
|
||||
modality: Qt.NonModal
|
||||
flags: Qt.Dialog
|
||||
width: 350
|
||||
height: fontMetrics.lineSpacing * 7
|
||||
signal accepted(string text)
|
||||
|
||||
function forceActiveFocus() {
|
||||
statusInput.forceActiveFocus();
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
onActivated: dbb.rejected()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
flags: Qt.Dialog
|
||||
height: fontMetrics.lineSpacing * 7
|
||||
modality: Qt.NonModal
|
||||
width: 350
|
||||
|
||||
footer: DialogButtonBox {
|
||||
id: dbb
|
||||
|
||||
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
|
||||
|
||||
onAccepted: {
|
||||
inputDialog.accepted(statusInput.text);
|
||||
|
||||
inputDialog.close();
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,84 +12,106 @@ import im.nheko
|
|||
ApplicationWindow {
|
||||
id: inviteDialogRoot
|
||||
|
||||
property InviteesModel invitees
|
||||
property var friendsCompleter
|
||||
property InviteesModel invitees
|
||||
property var profile
|
||||
minimumWidth: 300
|
||||
|
||||
Component.onCompleted: {
|
||||
friendsCompleter = TimelineManager.completerFor("user", "friends")
|
||||
width = 600
|
||||
}
|
||||
|
||||
function addInvite(mxid, displayName, avatarUrl) {
|
||||
if (mxid.match("@.+?:.{3,}")) {
|
||||
invitees.addUser(mxid, displayName, avatarUrl);
|
||||
} else
|
||||
console.log("invalid mxid: " + mxid)
|
||||
console.log("invalid mxid: " + mxid);
|
||||
}
|
||||
|
||||
function cleanUpAndClose() {
|
||||
if (inviteeEntry.isValidMxid)
|
||||
addInvite(inviteeEntry.text, "", "");
|
||||
|
||||
invitees.accept();
|
||||
close();
|
||||
}
|
||||
|
||||
title: qsTr("Invite users to %1").arg(invitees.room.plainRoomName)
|
||||
height: 380
|
||||
width: 340
|
||||
color: palette.window
|
||||
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 {
|
||||
sequence: "Ctrl+Enter"
|
||||
|
||||
onActivated: cleanUpAndClose()
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
|
||||
onActivated: inviteDialogRoot.close()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
Flow {
|
||||
layoutDirection: Qt.LeftToRight
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: implicitHeight
|
||||
layoutDirection: Qt.LeftToRight
|
||||
spacing: 4
|
||||
visible: !inviteesList.visible
|
||||
|
||||
Repeater {
|
||||
id: inviteesRepeater
|
||||
|
||||
model: invitees
|
||||
|
||||
delegate: ItemDelegate {
|
||||
onClicked: invitees.removeUser(model.mxid)
|
||||
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 {
|
||||
border.color: palette.text
|
||||
color: inviteeButton.hovered ? palette.highlight : palette.window
|
||||
border.width: 1
|
||||
color: inviteeButton.hovered ? palette.highlight : palette.window
|
||||
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 {
|
||||
text: qsTr("Search user")
|
||||
Layout.fillWidth: true
|
||||
color: palette.text
|
||||
text: qsTr("Search user")
|
||||
}
|
||||
RowLayout {
|
||||
spacing: Nheko.paddingMedium
|
||||
|
@ -99,147 +121,136 @@ ApplicationWindow {
|
|||
|
||||
property bool isValidMxid: text.match("@.+?:.{3,}")
|
||||
|
||||
Layout.fillWidth: true
|
||||
backgroundColor: palette.window
|
||||
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()
|
||||
Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers & Qt.ControlModifier))
|
||||
Keys.onPressed: {
|
||||
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers === Qt.ControlModifier))
|
||||
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: {
|
||||
searchTimer.restart()
|
||||
if(isValidMxid) {
|
||||
searchTimer.restart();
|
||||
if (isValidMxid) {
|
||||
profile = TimelineManager.getGlobalUserProfile(text);
|
||||
} else
|
||||
profile = null;
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: searchTimer
|
||||
|
||||
interval: 350
|
||||
|
||||
onTriggered: {
|
||||
userSearch.model.setSearchString(parent.text)
|
||||
userSearch.model.setSearchString(parent.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
id: searchOnServer
|
||||
|
||||
checked: false
|
||||
|
||||
onClicked: userSearch.model.setSearchString(inviteeEntry.text)
|
||||
}
|
||||
MatrixText {
|
||||
text: qsTr("Search on Server")
|
||||
}
|
||||
|
||||
}
|
||||
RowLayout {
|
||||
UserListRow {
|
||||
visible: inviteeEntry.isValidMxid
|
||||
id: del3
|
||||
Layout.preferredWidth: inviteDialogRoot.width/2
|
||||
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.preferredHeight: implicitHeight
|
||||
displayName: profile? profile.displayName : ""
|
||||
avatarUrl: profile? profile.avatarUrl : ""
|
||||
userid: inviteeEntry.text
|
||||
onClicked: addInvite(inviteeEntry.text, displayName, avatarUrl)
|
||||
Layout.preferredWidth: inviteDialogRoot.width / 2
|
||||
avatarUrl: profile ? profile.avatarUrl : ""
|
||||
bgColor: del3.hovered ? palette.dark : inviteDialogRoot.color
|
||||
displayName: profile ? profile.displayName : ""
|
||||
userid: inviteeEntry.text
|
||||
visible: inviteeEntry.isValidMxid
|
||||
|
||||
onClicked: addInvite(inviteeEntry.text, displayName, avatarUrl)
|
||||
}
|
||||
ListView {
|
||||
visible: !inviteeEntry.isValidMxid
|
||||
id: userSearch
|
||||
model: searchOnServer.checked? userDirectory : friendsCompleter
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
clip: true
|
||||
model: searchOnServer.checked ? userDirectory : friendsCompleter
|
||||
visible: !inviteeEntry.isValidMxid
|
||||
|
||||
delegate: UserListRow {
|
||||
id: del2
|
||||
width: ListView.view.width
|
||||
height: implicitHeight
|
||||
displayName: model.displayName
|
||||
userid: model.userid
|
||||
|
||||
avatarUrl: model.avatarUrl
|
||||
onClicked: addInvite(userid, displayName, avatarUrl)
|
||||
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 {
|
||||
Layout.fillHeight: true
|
||||
visible: inviteesList.visible
|
||||
Layout.preferredWidth: 1
|
||||
color: Nheko.theme.separator
|
||||
visible: inviteesList.visible
|
||||
}
|
||||
ListView {
|
||||
id: inviteesList
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
model: invitees
|
||||
Layout.fillWidth: true
|
||||
clip: true
|
||||
model: invitees
|
||||
visible: inviteDialogRoot.width >= 500
|
||||
|
||||
delegate: UserListRow {
|
||||
id: del
|
||||
hoverEnabled: true
|
||||
width: ListView.view.width
|
||||
height: implicitHeight
|
||||
onClicked: TimelineManager.openGlobalUserProfile(model.mxid)
|
||||
userid: model.mxid
|
||||
|
||||
avatarUrl: model.avatarUrl
|
||||
displayName: model.displayName
|
||||
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 {
|
||||
id: removeButton
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Nheko.paddingSmall
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Nheko.paddingSmall
|
||||
id: removeButton
|
||||
image: ":/icons/icons/ui/dismiss.svg"
|
||||
|
||||
onClicked: invitees.removeUser(model.mxid)
|
||||
}
|
||||
|
||||
NhekoCursorShape {
|
||||
anchors.fill: parent
|
||||
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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,48 +11,18 @@ import im.nheko 1.0
|
|||
ApplicationWindow {
|
||||
id: joinRoomRoot
|
||||
|
||||
title: qsTr("Join room")
|
||||
modality: Qt.WindowModal
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
color: palette.window
|
||||
width: 350
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
height: fontMetrics.lineSpacing * 7
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
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();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
modality: Qt.WindowModal
|
||||
title: qsTr("Join room")
|
||||
width: 350
|
||||
|
||||
footer: DialogButtonBox {
|
||||
id: dbb
|
||||
|
||||
standardButtons: DialogButtonBox.Cancel
|
||||
|
||||
onAccepted: {
|
||||
Nheko.joinRoom(input.text);
|
||||
joinRoomRoot.close();
|
||||
|
@ -62,11 +32,38 @@ ApplicationWindow {
|
|||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Join")
|
||||
enabled: input.text.match("#.+?:.{3,}")
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,21 +9,20 @@ import im.nheko
|
|||
P.MessageDialog {
|
||||
id: leaveRoomRoot
|
||||
|
||||
required property string roomId
|
||||
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
|
||||
onAccepted: {
|
||||
modality: Qt.ApplicationModal
|
||||
text: qsTr("Are you sure you want to leave?")
|
||||
title: qsTr("Leave room")
|
||||
|
||||
onAccepted: {
|
||||
if (CallManager.haveCallInvite) {
|
||||
callManager.rejectInvite();
|
||||
} else if (CallManager.isOnCall) {
|
||||
CallManager.hangUp();
|
||||
}
|
||||
Rooms.leave(roomId, reason)
|
||||
Rooms.leave(roomId, reason);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,10 +9,11 @@ import im.nheko
|
|||
P.MessageDialog {
|
||||
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
|
||||
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()
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -9,21 +9,38 @@ import QtQuick.Controls 2.5
|
|||
import QtQuick.Layouts 1.3
|
||||
import im.nheko 1.0
|
||||
|
||||
|
||||
ApplicationWindow {
|
||||
id: plEditorW
|
||||
|
||||
property var roomSettings
|
||||
property var editingModel: Nheko.editPowerlevels(roomSettings.roomId)
|
||||
property var roomSettings
|
||||
|
||||
modality: Qt.NonModal
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
minimumWidth: 300
|
||||
minimumHeight: 400
|
||||
height: 600
|
||||
minimumHeight: 400
|
||||
minimumWidth: 300
|
||||
modality: Qt.NonModal
|
||||
title: qsTr("Permissions in %1").arg(roomSettings.roomName)
|
||||
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 {
|
||||
// sequence: StandardKey.Cancel
|
||||
|
@ -31,22 +48,21 @@ ApplicationWindow {
|
|||
// }
|
||||
|
||||
ColumnLayout {
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
anchors.fill: parent
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
spacing: 0
|
||||
|
||||
|
||||
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.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 {
|
||||
id: bar
|
||||
|
||||
Layout.preferredWidth: parent.width
|
||||
|
||||
NhekoTabButton {
|
||||
|
@ -57,95 +73,95 @@ ApplicationWindow {
|
|||
}
|
||||
}
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: palette.alternateBase
|
||||
border.width: 1
|
||||
Layout.fillWidth: true
|
||||
border.color: Nheko.theme.separator
|
||||
border.width: 1
|
||||
color: palette.alternateBase
|
||||
|
||||
StackLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
currentIndex: bar.currentIndex
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
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
|
||||
color: palette.text
|
||||
}
|
||||
|
||||
ReorderableListview {
|
||||
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.fillWidth: true
|
||||
model: editingModel.types
|
||||
|
||||
delegate: RowLayout {
|
||||
Column {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text { visible: model.isType; text: model.displayName; color: palette.text}
|
||||
Text {
|
||||
visible: !model.isType;
|
||||
color: palette.text
|
||||
text: model.displayName
|
||||
visible: model.isType
|
||||
}
|
||||
Text {
|
||||
color: palette.text
|
||||
text: {
|
||||
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)
|
||||
return qsTr("Moderator (%1)").arg(model.powerlevel)
|
||||
return qsTr("Moderator (%1)").arg(model.powerlevel);
|
||||
else if (editingModel.defaultUserLevel == model.powerlevel)
|
||||
return qsTr("User (%1)").arg(model.powerlevel)
|
||||
return qsTr("User (%1)").arg(model.powerlevel);
|
||||
else
|
||||
return qsTr("Custom (%1)").arg(model.powerlevel)
|
||||
return qsTr("Custom (%1)").arg(model.powerlevel);
|
||||
}
|
||||
color: palette.text
|
||||
visible: !model.isType
|
||||
}
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.rightMargin: 2
|
||||
image: model.isType ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg"
|
||||
visible: !model.isType || model.removeable
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
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"
|
||||
visible: !model.isType || model.removeable
|
||||
|
||||
onClicked: {
|
||||
if (model.isType) {
|
||||
editingModel.types.remove(index);
|
||||
} else {
|
||||
typeEntry.y = offset
|
||||
typeEntry.visible = true
|
||||
typeEntry.y = offset;
|
||||
typeEntry.visible = true;
|
||||
typeEntry.index = index;
|
||||
typeEntry.forceActiveFocus()
|
||||
typeEntry.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
id: typeEntry
|
||||
|
||||
property int index
|
||||
|
||||
color: palette.text
|
||||
visible: false
|
||||
width: parent.width
|
||||
z: 5
|
||||
visible: false
|
||||
|
||||
color: palette.text
|
||||
|
||||
Keys.onPressed: {
|
||||
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.clear();
|
||||
event.accepted = true;
|
||||
}
|
||||
else if (event.matches(StandardKey.Cancel)) {
|
||||
} else if (event.matches(StandardKey.Cancel)) {
|
||||
typeEntry.visible = false;
|
||||
typeEntry.clear();
|
||||
event.accepted = true;
|
||||
|
@ -153,7 +169,6 @@ ApplicationWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Add new role")
|
||||
|
@ -164,19 +179,18 @@ ApplicationWindow {
|
|||
id: newPLLay
|
||||
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
color: palette.alternateBase
|
||||
visible: false
|
||||
|
||||
RowLayout {
|
||||
spacing: Nheko.paddingMedium
|
||||
anchors.fill: parent
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
SpinBox {
|
||||
id: newPLVal
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
Layout.fillWidth: true
|
||||
editable: true
|
||||
//from: -9007199254740991
|
||||
//to: 9007199254740991
|
||||
|
@ -192,10 +206,10 @@ ApplicationWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Add")
|
||||
Layout.preferredWidth: 100
|
||||
text: qsTr("Add")
|
||||
|
||||
onClicked: {
|
||||
editingModel.addRole(newPLVal.value);
|
||||
newPLLay.visible = false;
|
||||
|
@ -205,42 +219,109 @@ ApplicationWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
ReorderableListview {
|
||||
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.fillWidth: true
|
||||
model: editingModel.users
|
||||
|
||||
Column{
|
||||
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 {
|
||||
id: userEntryCompleter
|
||||
|
||||
property int index: 0
|
||||
|
||||
visible: false
|
||||
|
||||
width: parent.width
|
||||
spacing: 1
|
||||
visible: false
|
||||
width: parent.width
|
||||
z: 5
|
||||
|
||||
MatrixTextField {
|
||||
id: userEntry
|
||||
|
||||
width: parent.width
|
||||
//font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
|
||||
color: palette.text
|
||||
onTextEdited: {
|
||||
userCompleter.completer.searchString = text;
|
||||
}
|
||||
width: parent.width
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
|
||||
event.accepted = true;
|
||||
|
@ -248,9 +329,9 @@ ApplicationWindow {
|
|||
} else if (event.key == Qt.Key_Down || event.key == Qt.Key_Tab) {
|
||||
event.accepted = true;
|
||||
if (event.key == Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))
|
||||
userCompleter.up();
|
||||
userCompleter.up();
|
||||
else
|
||||
userCompleter.down();
|
||||
userCompleter.down();
|
||||
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
|
||||
if (userCompleter.currentCompletion()) {
|
||||
userCompleter.finishCompletion();
|
||||
|
@ -264,130 +345,45 @@ ApplicationWindow {
|
|||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
onTextEdited: {
|
||||
userCompleter.completer.searchString = text;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Completer {
|
||||
id: userCompleter
|
||||
|
||||
visible: userEntry.text.length > 0
|
||||
width: parent.width
|
||||
roomId: plEditorW.roomSettings.roomId
|
||||
completerName: "user"
|
||||
bottomToTop: false
|
||||
fullWidth: true
|
||||
avatarHeight: Nheko.avatarSize / 2
|
||||
avatarWidth: Nheko.avatarSize / 2
|
||||
bottomToTop: false
|
||||
centerRowContent: false
|
||||
completerName: "user"
|
||||
fullWidth: true
|
||||
roomId: plEditorW.roomSettings.roomId
|
||||
rowMargin: 2
|
||||
rowSpacing: 2
|
||||
visible: userEntry.text.length > 0
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
id: userCompletionConnections
|
||||
|
||||
function onCompletionSelected(id) {
|
||||
console.log("selected: " + id);
|
||||
editingModel.users.add(userEntryCompleter.index, id);
|
||||
userEntry.clear();
|
||||
userEntryCompleter.visible = false;
|
||||
}
|
||||
|
||||
function onCountChanged() {
|
||||
if (userCompleter.count > 0 && (userCompleter.currentIndex < 0 || userCompleter.currentIndex >= userCompleter.count))
|
||||
userCompleter.currentIndex = 0;
|
||||
|
||||
userCompleter.currentIndex = 0;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,124 +12,23 @@ import im.nheko
|
|||
ApplicationWindow {
|
||||
id: applyDialog
|
||||
|
||||
property RoomSettings roomSettings
|
||||
property PowerlevelEditingModels editingModel
|
||||
property RoomSettings roomSettings
|
||||
|
||||
minimumWidth: 340
|
||||
minimumHeight: 450
|
||||
width: 450
|
||||
height: 680
|
||||
color: palette.window
|
||||
modality: Qt.NonModal
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
height: 680
|
||||
minimumHeight: 450
|
||||
minimumWidth: 340
|
||||
modality: Qt.NonModal
|
||||
title: qsTr("Apply permission changes")
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
width: 450
|
||||
|
||||
footer: DialogButtonBox {
|
||||
id: dbb
|
||||
|
||||
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
|
||||
|
||||
onAccepted: {
|
||||
editingModel.spaces.commit();
|
||||
applyDialog.close();
|
||||
|
@ -137,4 +36,100 @@ ApplicationWindow {
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,42 +11,39 @@ ApplicationWindow {
|
|||
|
||||
property alias rawMessage: rawMessageView.text
|
||||
|
||||
height: 420
|
||||
width: 420
|
||||
color: palette.window
|
||||
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
height: 420
|
||||
width: 420
|
||||
|
||||
footer: DialogButtonBox {
|
||||
standardButtons: DialogButtonBox.Ok
|
||||
|
||||
onAccepted: rawMessageRoot.close()
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
|
||||
onActivated: rawMessageRoot.close()
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
anchors.fill: parent
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
padding: Nheko.paddingMedium
|
||||
|
||||
TextArea {
|
||||
id: rawMessageView
|
||||
|
||||
font: Nheko.monospaceFont()
|
||||
anchors.fill: parent
|
||||
color: palette.text
|
||||
font: Nheko.monospaceFont()
|
||||
readOnly: true
|
||||
textFormat: Text.PlainText
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
background: Rectangle {
|
||||
color: palette.base
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
footer: DialogButtonBox {
|
||||
standardButtons: DialogButtonBox.Ok
|
||||
onAccepted: rawMessageRoot.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,49 +15,46 @@ ApplicationWindow {
|
|||
recaptcha.confirm();
|
||||
recaptchaRoot.close();
|
||||
}
|
||||
|
||||
function reject() {
|
||||
recaptcha.cancel();
|
||||
recaptchaRoot.close();
|
||||
}
|
||||
|
||||
color: palette.window
|
||||
title: recaptcha.context
|
||||
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
height: msg.implicitHeight + footer.implicitHeight
|
||||
title: recaptcha.context
|
||||
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 {
|
||||
onAccepted: recaptchaRoot.accept()
|
||||
onRejected: recaptchaRoot.reject()
|
||||
|
||||
Button {
|
||||
text: qsTr("Open reCAPTCHA")
|
||||
|
||||
onClicked: recaptcha.openReCaptcha()
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Cancel")
|
||||
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
|
||||
text: qsTr("Cancel")
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Confirm")
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,18 +14,24 @@ ApplicationWindow {
|
|||
property ReadReceiptsProxy readReceipts
|
||||
property Room room
|
||||
|
||||
height: 380
|
||||
width: 340
|
||||
minimumHeight: 380
|
||||
minimumWidth: headerTitle.width + 2 * Nheko.paddingMedium
|
||||
color: palette.window
|
||||
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 {
|
||||
sequence: StandardKey.Cancel
|
||||
|
||||
onActivated: readReceiptsRoot.close()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
|
@ -34,98 +40,84 @@ ApplicationWindow {
|
|||
Label {
|
||||
id: headerTitle
|
||||
|
||||
color: palette.text
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
text: qsTr("Read receipts")
|
||||
color: palette.text
|
||||
font.pointSize: fontMetrics.font.pointSize * 1.5
|
||||
text: qsTr("Read receipts")
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
padding: Nheko.paddingMedium
|
||||
ScrollBar.horizontal.visible: false
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumHeight: 200
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 200
|
||||
ScrollBar.horizontal.visible: false
|
||||
padding: Nheko.paddingMedium
|
||||
|
||||
ListView {
|
||||
id: readReceiptsList
|
||||
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
clip: true
|
||||
model: readReceipts
|
||||
|
||||
delegate: ItemDelegate {
|
||||
id: del
|
||||
|
||||
onClicked: room.openUserProfile(model.mxid)
|
||||
padding: Nheko.paddingMedium
|
||||
width: ListView.view.width
|
||||
ToolTip.text: model.mxid
|
||||
ToolTip.visible: hovered
|
||||
height: receiptLayout.implicitHeight + Nheko.paddingSmall * 2
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: model.mxid
|
||||
padding: Nheko.paddingMedium
|
||||
width: ListView.view.width
|
||||
|
||||
background: Rectangle {
|
||||
color: del.hovered ? palette.dark : readReceiptsRoot.color
|
||||
}
|
||||
|
||||
onClicked: room.openUserProfile(model.mxid)
|
||||
|
||||
RowLayout {
|
||||
id: receiptLayout
|
||||
|
||||
spacing: Nheko.paddingMedium
|
||||
anchors.fill: parent
|
||||
anchors.margins: Nheko.paddingSmall
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
Avatar {
|
||||
id: avatar
|
||||
|
||||
Layout.preferredWidth: Nheko.avatarSize
|
||||
Layout.preferredHeight: Nheko.avatarSize
|
||||
userid: model.mxid
|
||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
Layout.preferredWidth: Nheko.avatarSize
|
||||
displayName: model.displayName
|
||||
enabled: false
|
||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
userid: model.mxid
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Nheko.paddingSmall
|
||||
Layout.fillWidth: true
|
||||
spacing: Nheko.paddingSmall
|
||||
|
||||
ElidedLabel {
|
||||
fullText: model.displayName
|
||||
Layout.fillWidth: true
|
||||
color: TimelineManager.userColor(model ? model.mxid : "", palette.window)
|
||||
elideWidth: del.width - Nheko.paddingMedium - avatar.width
|
||||
font.pointSize: fontMetrics.font.pointSize
|
||||
elideWidth: del.width - Nheko.paddingMedium - avatar.width
|
||||
Layout.fillWidth: true
|
||||
fullText: model.displayName
|
||||
}
|
||||
|
||||
ElidedLabel {
|
||||
fullText: model.timestamp
|
||||
color: palette.buttonText
|
||||
font.pointSize: fontMetrics.font.pointSize * 0.9
|
||||
elideWidth: del.width - Nheko.paddingMedium - avatar.width
|
||||
Layout.fillWidth: true
|
||||
color: palette.buttonText
|
||||
elideWidth: del.width - Nheko.paddingMedium - avatar.width
|
||||
font.pointSize: fontMetrics.font.pointSize * 0.9
|
||||
fullText: model.timestamp
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
NhekoCursorShape {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
footer: DialogButtonBox {
|
||||
standardButtons: DialogButtonBox.Ok
|
||||
onAccepted: readReceiptsRoot.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,71 +10,66 @@ import im.nheko
|
|||
ApplicationWindow {
|
||||
required property string eventId
|
||||
|
||||
width: 400
|
||||
height: gl.implicitHeight + 2 * Nheko.paddingMedium
|
||||
title: qsTr("Report message")
|
||||
width: 400
|
||||
|
||||
GridLayout {
|
||||
id: gl
|
||||
|
||||
columnSpacing: Nheko.paddingMedium
|
||||
rowSpacing: Nheko.paddingMedium
|
||||
columns: 2
|
||||
anchors.fill: parent
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
columnSpacing: Nheko.paddingMedium
|
||||
columns: 2
|
||||
rowSpacing: Nheko.paddingMedium
|
||||
|
||||
Label {
|
||||
Layout.columnSpan: 2
|
||||
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.")
|
||||
wrapMode: Label.WordWrap
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("Enter your reason for reporting:")
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: reason
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("How bad is the message?")
|
||||
}
|
||||
|
||||
Slider {
|
||||
id: score
|
||||
|
||||
from: 0
|
||||
to: -100
|
||||
stepSize: 25
|
||||
snapMode: Slider.SnapAlways
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
snapMode: Slider.SnapAlways
|
||||
stepSize: 25
|
||||
to: -100
|
||||
}
|
||||
Item {
|
||||
}
|
||||
|
||||
Item {}
|
||||
|
||||
Label {
|
||||
text: {
|
||||
if (score.value === 0)
|
||||
return qsTr("Not bad")
|
||||
return qsTr("Not bad");
|
||||
else if (score.value === -25)
|
||||
return qsTr("Mild")
|
||||
return qsTr("Mild");
|
||||
else if (score.value === -50)
|
||||
return qsTr("Bad")
|
||||
return qsTr("Bad");
|
||||
else if (score.value === -75)
|
||||
return qsTr("Serious")
|
||||
return qsTr("Serious");
|
||||
else if (score.value === -100)
|
||||
return qsTr("Extremely serious")
|
||||
return qsTr("Extremely serious");
|
||||
}
|
||||
}
|
||||
|
||||
DialogButtonBox {
|
||||
Layout.columnSpan: 2
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.columnSpan: 2
|
||||
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
|
||||
|
||||
onAccepted: {
|
||||
room.reportEvent(eventId, reason.text, score.value);
|
||||
close();
|
||||
|
|
|
@ -13,21 +13,72 @@ import im.nheko 1.0
|
|||
ApplicationWindow {
|
||||
id: roomDirectoryWindow
|
||||
|
||||
visible: true
|
||||
minimumWidth: 340
|
||||
minimumHeight: 340
|
||||
height: 420
|
||||
width: 650
|
||||
color: palette.window
|
||||
modality: Qt.NonModal
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
height: 420
|
||||
minimumHeight: 340
|
||||
minimumWidth: 340
|
||||
modality: Qt.NonModal
|
||||
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 {
|
||||
sequence: StandardKey.Cancel
|
||||
|
||||
onActivated: roomDirectoryWindow.close()
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: roomDirView
|
||||
|
||||
|
@ -37,171 +88,108 @@ ApplicationWindow {
|
|||
delegate: Rectangle {
|
||||
id: roomDirDelegate
|
||||
|
||||
property int avatarSize: fontMetrics.height * 3.2
|
||||
property color background: palette.window
|
||||
property color importantText: palette.text
|
||||
property color unimportantText: palette.buttonText
|
||||
property int avatarSize: fontMetrics.height * 3.2
|
||||
|
||||
color: background
|
||||
height: avatarSize + Nheko.paddingLarge
|
||||
width: ListView.view.width
|
||||
|
||||
RowLayout {
|
||||
spacing: Nheko.paddingMedium
|
||||
anchors.fill: parent
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
implicitHeight: textContent.implicitHeight
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
Avatar {
|
||||
id: roomAvatar
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.rightMargin: Nheko.paddingMedium
|
||||
Layout.preferredWidth: roomDirDelegate.avatarSize
|
||||
Layout.preferredHeight: roomDirDelegate.avatarSize
|
||||
|
||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
roomid: model.roomid
|
||||
Layout.preferredWidth: roomDirDelegate.avatarSize
|
||||
Layout.rightMargin: Nheko.paddingMedium
|
||||
displayName: model.name
|
||||
roomid: model.roomid
|
||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: textContent
|
||||
rows: 2
|
||||
columns: 2
|
||||
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.preferredWidth: parent.width - roomAvatar.width
|
||||
columns: 2
|
||||
rows: 2
|
||||
|
||||
ElidedLabel {
|
||||
Layout.row: 0
|
||||
Layout.column: 0
|
||||
Layout.fillWidth:true
|
||||
Layout.fillWidth: true
|
||||
Layout.row: 0
|
||||
color: roomDirDelegate.importantText
|
||||
elideWidth: width
|
||||
fullText: model.name
|
||||
}
|
||||
|
||||
Label {
|
||||
id: roomTopic
|
||||
|
||||
color: roomDirDelegate.unimportantText
|
||||
Layout.row: 1
|
||||
Layout.column: 0
|
||||
font.pointSize: fontMetrics.font.pointSize*0.9
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 2
|
||||
Layout.fillWidth: true
|
||||
Layout.row: 1
|
||||
color: roomDirDelegate.unimportantText
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: fontMetrics.font.pointSize * 0.9
|
||||
maximumLineCount: 2
|
||||
text: model.topic
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.row: 0
|
||||
Layout.column: 1
|
||||
id: roomCount
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.column: 1
|
||||
Layout.row: 0
|
||||
color: roomDirDelegate.unimportantText
|
||||
font.pointSize: fontMetrics.font.pointSize*0.9
|
||||
font.pointSize: fontMetrics.font.pointSize * 0.9
|
||||
text: model.numMembers.toString()
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.row: 1
|
||||
Layout.column: 1
|
||||
id: joinRoomButton
|
||||
|
||||
Layout.column: 1
|
||||
Layout.row: 1
|
||||
enabled: model.roomid !== ""
|
||||
text: model.canJoin ? qsTr("Join") : qsTr("Open")
|
||||
|
||||
onClicked: {
|
||||
if (model.canJoin)
|
||||
publicRooms.joinRoom(model.index);
|
||||
else
|
||||
{
|
||||
else {
|
||||
Rooms.setCurrentRoom(model.roomid);
|
||||
roomDirectoryWindow.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
footer: Item {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: parent.width
|
||||
visible: !publicRooms.reachedEndOfPagination && publicRooms.loadingMoreRooms
|
||||
anchors.margins: Nheko.paddingLarge
|
||||
// hacky but works
|
||||
height: loadingSpinner.height + 2 * Nheko.paddingLarge
|
||||
anchors.margins: Nheko.paddingLarge
|
||||
visible: !publicRooms.reachedEndOfPagination && publicRooms.loadingMoreRooms
|
||||
width: parent.width
|
||||
|
||||
Spinner {
|
||||
id: loadingSpinner
|
||||
|
||||
anchors.centerIn: parent
|
||||
anchors.margins: Nheko.paddingLarge
|
||||
running: visible
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,18 +17,24 @@ ApplicationWindow {
|
|||
property MemberList members
|
||||
property Room room
|
||||
|
||||
title: qsTr("Members of %1").arg(members.roomName)
|
||||
height: 650
|
||||
width: 420
|
||||
minimumHeight: 420
|
||||
color: palette.window
|
||||
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 {
|
||||
sequence: StandardKey.Cancel
|
||||
|
||||
onActivated: roomMembersRoot.close()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
|
@ -37,148 +43,146 @@ ApplicationWindow {
|
|||
Avatar {
|
||||
id: roomAvatar
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredHeight: 130
|
||||
Layout.preferredWidth: 130
|
||||
|
||||
roomid: members.roomId
|
||||
displayName: members.roomName
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
roomid: members.roomId
|
||||
url: members.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
|
||||
onClicked: TimelineManager.openRoomSettings(members.roomId)
|
||||
}
|
||||
|
||||
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
|
||||
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 {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
image: ":/icons/icons/ui/add-square-button.svg"
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Invite more people")
|
||||
ToolTip.visible: hovered
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/add-square-button.svg"
|
||||
|
||||
onClicked: TimelineManager.openInviteUsers(members.roomId)
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
id: searchBar
|
||||
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("Search...")
|
||||
onTextChanged: members.setFilterString(text)
|
||||
|
||||
Component.onCompleted: forceActiveFocus()
|
||||
onTextChanged: members.setFilterString(text)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
Label {
|
||||
text: qsTr("Sort by: ")
|
||||
color: palette.text
|
||||
text: qsTr("Sort by: ")
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
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") }
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
textRole: "text"
|
||||
valueRole: "data"
|
||||
|
||||
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)
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
padding: Nheko.paddingMedium
|
||||
ScrollBar.horizontal.visible: false
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumHeight: 200
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 200
|
||||
ScrollBar.horizontal.visible: false
|
||||
padding: Nheko.paddingMedium
|
||||
|
||||
ListView {
|
||||
id: memberList
|
||||
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
clip: true
|
||||
model: members
|
||||
|
||||
|
||||
delegate: ItemDelegate {
|
||||
id: del
|
||||
|
||||
onClicked: room.openUserProfile(model.mxid)
|
||||
padding: Nheko.paddingMedium
|
||||
width: ListView.view.width
|
||||
height: memberLayout.implicitHeight + Nheko.paddingSmall * 2
|
||||
hoverEnabled: true
|
||||
padding: Nheko.paddingMedium
|
||||
width: ListView.view.width
|
||||
|
||||
background: Rectangle {
|
||||
color: del.hovered ? palette.dark : roomMembersRoot.color
|
||||
}
|
||||
|
||||
onClicked: room.openUserProfile(model.mxid)
|
||||
|
||||
RowLayout {
|
||||
id: memberLayout
|
||||
|
||||
spacing: Nheko.paddingMedium
|
||||
anchors.centerIn: parent
|
||||
spacing: Nheko.paddingMedium
|
||||
width: parent.width - Nheko.paddingSmall * 2
|
||||
|
||||
Avatar {
|
||||
id: avatar
|
||||
|
||||
Layout.preferredWidth: Nheko.avatarSize
|
||||
Layout.preferredHeight: Nheko.avatarSize
|
||||
userid: model.mxid
|
||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
Layout.preferredWidth: Nheko.avatarSize
|
||||
displayName: model.displayName
|
||||
enabled: false
|
||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
userid: model.mxid
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Nheko.paddingSmall
|
||||
Layout.fillWidth: true
|
||||
spacing: Nheko.paddingSmall
|
||||
|
||||
ElidedLabel {
|
||||
fullText: model.displayName
|
||||
Layout.fillWidth: true
|
||||
color: TimelineManager.userColor(model ? model.mxid : "", del.background.color)
|
||||
elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width
|
||||
font.pixelSize: fontMetrics.font.pixelSize
|
||||
elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width
|
||||
Layout.fillWidth: true
|
||||
fullText: model.displayName
|
||||
}
|
||||
|
||||
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
|
||||
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 {
|
||||
powerlevel: model.powerlevel
|
||||
permissions: room.permissions
|
||||
powerlevel: model.powerlevel
|
||||
}
|
||||
|
||||
EncryptionIndicator {
|
||||
id: encryptInd
|
||||
|
||||
Layout.preferredWidth: 16
|
||||
Layout.preferredHeight: 16
|
||||
Layout.alignment: Qt.AlignRight
|
||||
visible: room.isEncrypted
|
||||
encrypted: room.isEncrypted
|
||||
trust: encrypted ? model.trustlevel : Crypto.Unverified
|
||||
Layout.preferredHeight: 16
|
||||
Layout.preferredWidth: 16
|
||||
ToolTip.text: {
|
||||
if (!encrypted)
|
||||
return qsTr("This room is not encrypted!");
|
||||
|
||||
switch (trust) {
|
||||
case Crypto.Verified:
|
||||
return qsTr("This user is verified.");
|
||||
|
@ -188,23 +192,22 @@ ApplicationWindow {
|
|||
return qsTr("This user has unverified devices!");
|
||||
}
|
||||
}
|
||||
encrypted: room.isEncrypted
|
||||
trust: encrypted ? model.trustlevel : Crypto.Unverified
|
||||
visible: room.isEncrypted
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
NhekoCursorShape {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
footer: Item {
|
||||
width: parent.width
|
||||
visible: (members.numUsersLoaded < members.memberCount) && members.loadingMoreMembers
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
// use the default height if it's visible, otherwise no height at all
|
||||
height: membersLoadingSpinner.implicitHeight
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
visible: (members.numUsersLoaded < members.memberCount) && members.loadingMoreMembers
|
||||
width: parent.width
|
||||
|
||||
Spinner {
|
||||
id: membersLoadingSpinner
|
||||
|
@ -212,18 +215,8 @@ ApplicationWindow {
|
|||
anchors.centerIn: parent
|
||||
implicitHeight: parent.visible ? 35 : 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
footer: DialogButtonBox {
|
||||
standardButtons: DialogButtonBox.Ok
|
||||
onAccepted: roomMembersRoot.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,80 +16,87 @@ ApplicationWindow {
|
|||
|
||||
property var roomSettings
|
||||
|
||||
minimumWidth: 340
|
||||
minimumHeight: 450
|
||||
width: 450
|
||||
height: 680
|
||||
color: palette.window
|
||||
modality: Qt.NonModal
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||
height: 680
|
||||
minimumHeight: 450
|
||||
minimumWidth: 340
|
||||
modality: Qt.NonModal
|
||||
title: qsTr("Room Settings")
|
||||
width: 450
|
||||
|
||||
footer: DialogButtonBox {
|
||||
standardButtons: DialogButtonBox.Ok
|
||||
|
||||
onAccepted: close()
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
|
||||
onActivated: roomSettingsDialog.close()
|
||||
}
|
||||
|
||||
Flickable {
|
||||
id: flickable
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
anchors.fill: parent
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
clip: true
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
contentWidth: roomSettingsDialog.width
|
||||
contentHeight: contentLayout1.height
|
||||
contentWidth: roomSettingsDialog.width
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
|
||||
ColumnLayout {
|
||||
id: contentLayout1
|
||||
width: parent.width
|
||||
|
||||
spacing: Nheko.paddingMedium
|
||||
width: parent.width
|
||||
|
||||
Avatar {
|
||||
id: displayAvatar
|
||||
|
||||
Layout.topMargin: Nheko.paddingMedium
|
||||
url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
roomid: roomSettings.roomId
|
||||
displayName: roomSettings.roomName
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredHeight: 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)
|
||||
|
||||
ImageButton {
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Change room avatar.")
|
||||
ToolTip.visible: hovered
|
||||
anchors.left: displayAvatar.left
|
||||
anchors.top: displayAvatar.top
|
||||
anchors.leftMargin: Nheko.paddingMedium
|
||||
anchors.top: displayAvatar.top
|
||||
anchors.topMargin: Nheko.paddingMedium
|
||||
visible: roomSettings.canChangeAvatar
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/edit.svg"
|
||||
visible: roomSettings.canChangeAvatar
|
||||
|
||||
onClicked: {
|
||||
roomSettings.updateAvatar();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Spinner {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: roomSettings.isLoading
|
||||
foreground: palette.mid
|
||||
running: roomSettings.isLoading
|
||||
visible: roomSettings.isLoading
|
||||
}
|
||||
|
||||
Text {
|
||||
id: errorText
|
||||
|
||||
color: "red"
|
||||
visible: opacity > 0
|
||||
opacity: 0
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
wrapMode: Text.Wrap // somehow still doesn't wrap
|
||||
Layout.fillWidth: true
|
||||
color: "red"
|
||||
opacity: 0
|
||||
visible: opacity > 0
|
||||
wrapMode: Text.Wrap // somehow still doesn't wrap
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: hideErrorAnimation
|
||||
|
||||
|
@ -98,43 +105,38 @@ ApplicationWindow {
|
|||
PauseAnimation {
|
||||
duration: 4000
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: errorText
|
||||
property: 'opacity'
|
||||
to: 0
|
||||
duration: 1000
|
||||
property: 'opacity'
|
||||
target: errorText
|
||||
to: 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: roomSettings
|
||||
function onDisplayError(errorMessage) {
|
||||
errorText.text = errorMessage;
|
||||
errorText.opacity = 1;
|
||||
hideErrorAnimation.restart();
|
||||
}
|
||||
}
|
||||
|
||||
target: roomSettings
|
||||
}
|
||||
TextEdit {
|
||||
id: roomName
|
||||
|
||||
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.maximumWidth: parent.width - (Nheko.paddingSmall * 2) - nameChangeButton.anchors.leftMargin - (nameChangeButton.width * 2)
|
||||
color: palette.text
|
||||
font.pixelSize: fontMetrics.font.pixelSize * 2
|
||||
horizontalAlignment: TextEdit.AlignHCenter
|
||||
wrapMode: TextEdit.Wrap
|
||||
readOnly: !isNameEditingAllowed
|
||||
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: {
|
||||
if (event.matches(StandardKey.InsertLineSeparator) || event.matches(StandardKey.InsertParagraphSeparator)) {
|
||||
roomSettings.changeName(roomName.text);
|
||||
|
@ -142,18 +144,21 @@ ApplicationWindow {
|
|||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
Keys.onShortcutOverride: event.key === Qt.Key_Enter
|
||||
|
||||
ImageButton {
|
||||
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.leftMargin: Nheko.paddingSmall
|
||||
anchors.verticalCenter: roomName.verticalCenter
|
||||
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"
|
||||
visible: roomSettings.canChangeName
|
||||
|
||||
onClicked: {
|
||||
if (roomName.isNameEditingAllowed) {
|
||||
roomSettings.changeName(roomName.text);
|
||||
|
@ -165,69 +170,64 @@ ApplicationWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Nheko.paddingMedium
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
Label {
|
||||
text: qsTr("%n member(s)", "", roomSettings.memberCount)
|
||||
color: palette.text
|
||||
text: qsTr("%n member(s)", "", roomSettings.memberCount)
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
image: ":/icons/icons/ui/people.svg"
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
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))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TextArea {
|
||||
id: roomTopic
|
||||
|
||||
property bool cut: implicitHeight > 100
|
||||
property bool isTopicEditingAllowed: false
|
||||
property bool showMore: false
|
||||
clip: true
|
||||
Layout.maximumHeight: showMore? Number.POSITIVE_INFINITY : 100
|
||||
Layout.preferredHeight: implicitHeight
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Nheko.paddingLarge
|
||||
Layout.maximumHeight: showMore ? Number.POSITIVE_INFINITY : 100
|
||||
Layout.preferredHeight: implicitHeight
|
||||
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
|
||||
clip: true
|
||||
color: palette.text
|
||||
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)
|
||||
|
||||
NhekoCursorShape {
|
||||
anchors.fill: parent
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: topicChangeButton
|
||||
|
||||
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.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"
|
||||
visible: roomSettings.canChangeTopic
|
||||
|
||||
onClicked: {
|
||||
if (roomTopic.isTopicEditingAllowed) {
|
||||
roomSettings.changeTopic(roomTopic.text);
|
||||
|
@ -240,234 +240,219 @@ ApplicationWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
id: showMorePlaceholder
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredHeight: showMoreButton.height
|
||||
Layout.preferredWidth: showMoreButton.width
|
||||
visible: roomTopic.cut
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
columns: 2
|
||||
rowSpacing: Nheko.paddingMedium
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
Layout.fillWidth: true
|
||||
|
||||
Label {
|
||||
text: qsTr("NOTIFICATIONS")
|
||||
font.bold: true
|
||||
color: palette.text
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Nheko.paddingLarge
|
||||
color: palette.text
|
||||
font.bold: true
|
||||
text: qsTr("NOTIFICATIONS")
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("Notifications")
|
||||
Layout.fillWidth: true
|
||||
color: palette.text
|
||||
text: qsTr("Notifications")
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
model: [qsTr("Muted"), qsTr("Mentions only"), qsTr("All messages")]
|
||||
Layout.fillWidth: true
|
||||
currentIndex: roomSettings.notifications
|
||||
model: [qsTr("Muted"), qsTr("Mentions only"), qsTr("All messages")]
|
||||
|
||||
onActivated: {
|
||||
roomSettings.changeNotifications(index);
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
WheelHandler{} // suppress scrolling changing values
|
||||
}
|
||||
|
||||
WheelHandler {
|
||||
} // suppress scrolling changing values
|
||||
}
|
||||
Label {
|
||||
text: qsTr("ENTRY PERMISSIONS")
|
||||
font.bold: true
|
||||
color: palette.text
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Nheko.paddingLarge
|
||||
color: palette.text
|
||||
font.bold: true
|
||||
text: qsTr("ENTRY PERMISSIONS")
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("Anyone can join")
|
||||
Layout.fillWidth: true
|
||||
color: palette.text
|
||||
text: qsTr("Anyone can join")
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
id: publicRoomButton
|
||||
|
||||
enabled: roomSettings.canChangeJoinRules
|
||||
checked: !roomSettings.privateAccess
|
||||
Layout.alignment: Qt.AlignRight
|
||||
checked: !roomSettings.privateAccess
|
||||
enabled: roomSettings.canChangeJoinRules
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("Allow knocking")
|
||||
Layout.fillWidth: true
|
||||
color: palette.text
|
||||
text: qsTr("Allow knocking")
|
||||
visible: knockingButton.visible
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
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
|
||||
}
|
||||
checked: roomSettings.knockingEnabled
|
||||
enabled: roomSettings.canChangeJoinRules && roomSettings.supportsKnocking
|
||||
visible: !publicRoomButton.checked
|
||||
|
||||
onCheckedChanged: {
|
||||
if (checked && !roomSettings.supportsKnockRestricted)
|
||||
restrictedButton.checked = false;
|
||||
}
|
||||
}
|
||||
Label {
|
||||
text: qsTr("Allow joining via other rooms")
|
||||
Layout.fillWidth: true
|
||||
color: palette.text
|
||||
text: qsTr("Allow joining via other rooms")
|
||||
visible: restrictedButton.visible
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
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
|
||||
}
|
||||
checked: roomSettings.restrictedEnabled
|
||||
enabled: roomSettings.canChangeJoinRules && roomSettings.supportsRestricted
|
||||
visible: !publicRoomButton.checked
|
||||
|
||||
onCheckedChanged: {
|
||||
if (checked && !roomSettings.supportsKnockRestricted)
|
||||
knockingButton.checked = false;
|
||||
}
|
||||
}
|
||||
Label {
|
||||
text: qsTr("Rooms to join via")
|
||||
Layout.fillWidth: true
|
||||
color: palette.text
|
||||
text: qsTr("Rooms to join via")
|
||||
visible: allowedRoomsButton.visible
|
||||
}
|
||||
|
||||
Button {
|
||||
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
|
||||
}
|
||||
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 {
|
||||
text: qsTr("Allow guests to join")
|
||||
Layout.fillWidth: true
|
||||
color: palette.text
|
||||
text: qsTr("Allow guests to join")
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
id: guestAccessButton
|
||||
|
||||
enabled: roomSettings.canChangeJoinRules
|
||||
checked: roomSettings.guestAccess
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
Button {
|
||||
visible: publicRoomButton.checked == roomSettings.privateAccess || knockingButton.checked != roomSettings.knockingEnabled || restrictedButton.checked != roomSettings.restrictedEnabled || guestAccessButton.checked != roomSettings.guestAccess || roomSettings.allowedRoomsModified
|
||||
checked: roomSettings.guestAccess
|
||||
enabled: roomSettings.canChangeJoinRules
|
||||
|
||||
text: qsTr("Apply access rules")
|
||||
onClicked: roomSettings.changeAccessRules(!publicRoomButton.checked, guestAccessButton.checked, knockingButton.checked, restrictedButton.checked)
|
||||
}
|
||||
Button {
|
||||
Layout.columnSpan: 2
|
||||
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 {
|
||||
text: qsTr("MESSAGE VISIBILITY")
|
||||
font.bold: true
|
||||
color: palette.text
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Nheko.paddingLarge
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("Allow viewing history without joining")
|
||||
Layout.fillWidth: true
|
||||
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.visible: publicHistoryHover.hovered
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
color: palette.text
|
||||
text: qsTr("Allow viewing history without joining")
|
||||
|
||||
HoverHandler {
|
||||
id: publicHistoryHover
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
id: publicHistoryButton
|
||||
|
||||
enabled: roomSettings.canChangeHistoryVisibility
|
||||
checked: roomSettings.historyVisibility == RoomSettings.WorldReadable
|
||||
Layout.alignment: Qt.AlignRight
|
||||
checked: roomSettings.historyVisibility == RoomSettings.WorldReadable
|
||||
enabled: roomSettings.canChangeHistoryVisibility
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: !publicHistoryButton.checked
|
||||
text: qsTr("Members can see messages since")
|
||||
Layout.fillWidth: true
|
||||
color: palette.text
|
||||
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.visible: privateHistoryHover.hovered
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
color: palette.text
|
||||
text: qsTr("Members can see messages since")
|
||||
visible: !publicHistoryButton.checked
|
||||
|
||||
HoverHandler {
|
||||
id: privateHistoryHover
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: !publicHistoryButton.checked
|
||||
enabled: roomSettings.canChangeHistoryVisibility
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignRight
|
||||
Layout.fillWidth: true
|
||||
enabled: roomSettings.canChangeHistoryVisibility
|
||||
visible: !publicHistoryButton.checked
|
||||
|
||||
RadioButton {
|
||||
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.visible: hovered
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
checked: roomSettings.historyVisibility == RoomSettings.Shared
|
||||
text: qsTr("Everything")
|
||||
}
|
||||
RadioButton {
|
||||
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.visible: hovered
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
checked: roomSettings.historyVisibility == RoomSettings.Invited
|
||||
text: qsTr("They got invited")
|
||||
}
|
||||
RadioButton {
|
||||
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.visible: hovered
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
checked: roomSettings.historyVisibility == RoomSettings.Joined || roomSettings.historyVisibility == RoomSettings.WorldReadable
|
||||
text: qsTr("They joined")
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
visible: roomSettings.historyVisibility != selectedVisibility
|
||||
enabled: roomSettings.canChangeHistoryVisibility
|
||||
|
||||
text: qsTr("Apply visibility changes")
|
||||
property int selectedVisibility: {
|
||||
if (publicHistoryButton.checked)
|
||||
return RoomSettings.WorldReadable;
|
||||
|
@ -477,202 +462,200 @@ ApplicationWindow {
|
|||
return RoomSettings.Invited;
|
||||
return RoomSettings.Joined;
|
||||
}
|
||||
onClicked: roomSettings.changeHistoryVisibility(selectedVisibility)
|
||||
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
enabled: roomSettings.canChangeHistoryVisibility
|
||||
text: qsTr("Apply visibility changes")
|
||||
visible: roomSettings.historyVisibility != selectedVisibility
|
||||
|
||||
onClicked: roomSettings.changeHistoryVisibility(selectedVisibility)
|
||||
}
|
||||
Label {
|
||||
text: qsTr("Locally hidden events")
|
||||
color: palette.text
|
||||
text: qsTr("Locally hidden events")
|
||||
}
|
||||
|
||||
HiddenEventsDialog {
|
||||
id: hiddenEventsDialog
|
||||
roomid: roomSettings.roomId
|
||||
|
||||
roomName: roomSettings.roomName
|
||||
roomid: roomSettings.roomId
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Configure")
|
||||
ToolTip.text: qsTr("Select events to hide in this room")
|
||||
onClicked: hiddenEventsDialog.show()
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
ToolTip.text: qsTr("Select events to hide in this room")
|
||||
text: qsTr("Configure")
|
||||
|
||||
onClicked: hiddenEventsDialog.show()
|
||||
}
|
||||
Label {
|
||||
text: qsTr("Automatic event deletion")
|
||||
color: palette.text
|
||||
text: qsTr("Automatic event deletion")
|
||||
}
|
||||
|
||||
EventExpirationDialog {
|
||||
id: eventExpirationDialog
|
||||
roomid: roomSettings.roomId
|
||||
|
||||
roomName: roomSettings.roomName
|
||||
roomid: roomSettings.roomId
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Configure")
|
||||
ToolTip.text: qsTr("Select if your events get automatically deleted in this room.")
|
||||
onClicked: eventExpirationDialog.show()
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
ToolTip.text: qsTr("Select if your events get automatically deleted in this room.")
|
||||
text: qsTr("Configure")
|
||||
|
||||
onClicked: eventExpirationDialog.show()
|
||||
}
|
||||
Label {
|
||||
text: qsTr("GENERAL SETTINGS")
|
||||
font.bold: true
|
||||
color: palette.text
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Nheko.paddingLarge
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("Encryption")
|
||||
color: palette.text
|
||||
font.bold: true
|
||||
text: qsTr("GENERAL SETTINGS")
|
||||
}
|
||||
Label {
|
||||
color: palette.text
|
||||
text: qsTr("Encryption")
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
id: encryptionToggle
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
checked: roomSettings.isEncryptionEnabled
|
||||
|
||||
onCheckedChanged: {
|
||||
if (roomSettings.isEncryptionEnabled) {
|
||||
checked = true;
|
||||
return ;
|
||||
return;
|
||||
}
|
||||
if (checked === true)
|
||||
confirmEncryptionDialog.open();
|
||||
}
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
Platform.MessageDialog {
|
||||
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>
|
||||
Please take note that it can't be disabled afterwards.`)
|
||||
modality: Qt.NonModal
|
||||
title: qsTr("End-to-End Encryption")
|
||||
|
||||
onAccepted: {
|
||||
if (roomSettings.isEncryptionEnabled)
|
||||
return ;
|
||||
|
||||
return;
|
||||
roomSettings.enableEncryption();
|
||||
}
|
||||
onRejected: {
|
||||
encryptionToggle.checked = false;
|
||||
}
|
||||
buttons: Platform.MessageDialog.Ok | Platform.MessageDialog.Cancel
|
||||
}
|
||||
|
||||
Label {
|
||||
color: palette.text
|
||||
text: qsTr("Permission")
|
||||
color: palette.text
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Configure")
|
||||
Layout.alignment: Qt.AlignRight
|
||||
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")
|
||||
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 {
|
||||
text: qsTr("Sticker & Emote Settings")
|
||||
color: palette.text
|
||||
text: qsTr("Aliases")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
ToolTip.text: qsTr("View and change the addresses/aliases of this room")
|
||||
text: qsTr("Configure")
|
||||
|
||||
onClicked: timelineRoot.showAliasEditor(roomSettings)
|
||||
}
|
||||
Label {
|
||||
text: qsTr("INFO")
|
||||
font.bold: true
|
||||
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.topMargin: Nheko.paddingLarge
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("Internal ID")
|
||||
Layout.topMargin: Nheko.paddingLarge
|
||||
color: palette.text
|
||||
font.bold: true
|
||||
text: qsTr("INFO")
|
||||
}
|
||||
|
||||
AbstractButton { // AbstractButton does not allow setting text color
|
||||
Label {
|
||||
color: palette.text
|
||||
text: qsTr("Internal ID")
|
||||
}
|
||||
AbstractButton {
|
||||
// AbstractButton does not allow setting text color
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.fillWidth: true
|
||||
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
|
||||
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.visible: toolTipTimer.running
|
||||
}
|
||||
TextEdit{ // label does not allow selection
|
||||
id: textEdit
|
||||
visible: false
|
||||
color: palette.text
|
||||
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 0.8)
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: roomSettings.roomId
|
||||
width: parent.width
|
||||
wrapMode: Text.WrapAnywhere
|
||||
}
|
||||
onClicked: {
|
||||
textEdit.selectAll()
|
||||
textEdit.copy()
|
||||
toolTipTimer.start()
|
||||
TextEdit {
|
||||
// label does not allow selection
|
||||
id: textEdit
|
||||
|
||||
text: roomSettings.roomId
|
||||
visible: false
|
||||
}
|
||||
Timer {
|
||||
id: toolTipTimer
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("Room Version")
|
||||
color: palette.text
|
||||
text: qsTr("Room Version")
|
||||
}
|
||||
|
||||
Label {
|
||||
text: roomSettings.roomVersion
|
||||
font.pixelSize: fontMetrics.font.pixelSize
|
||||
Layout.alignment: Qt.AlignRight
|
||||
color: palette.text
|
||||
font.pixelSize: fontMetrics.font.pixelSize
|
||||
text: roomSettings.roomVersion
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Button {
|
||||
id: showMoreButton
|
||||
|
||||
anchors.horizontalCenter: flickable.horizontalCenter
|
||||
y: Math.min(showMorePlaceholder.y+contentLayout1.y-flickable.contentY,flickable.height-height)
|
||||
text: roomTopic.showMore ? qsTr("show less") : qsTr("show more")
|
||||
visible: roomTopic.cut
|
||||
text: roomTopic.showMore? qsTr("show less") : qsTr("show more")
|
||||
onClicked: {roomTopic.showMore = !roomTopic.showMore
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,20 +17,20 @@ ApplicationWindow {
|
|||
|
||||
property var profile
|
||||
|
||||
height: 650
|
||||
width: 420
|
||||
minimumWidth: 150
|
||||
minimumHeight: 150
|
||||
color: palette.window
|
||||
title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile")
|
||||
modality: Qt.NonModal
|
||||
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 {
|
||||
sequence: StandardKey.Cancel
|
||||
|
||||
onActivated: userProfileDialog.close()
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: devicelist
|
||||
|
||||
|
@ -38,61 +38,73 @@ ApplicationWindow {
|
|||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
clip: true
|
||||
spacing: 8
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
clip: true
|
||||
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 {
|
||||
id: contentL
|
||||
|
||||
width: devicelist.width
|
||||
spacing: Nheko.paddingMedium
|
||||
width: devicelist.width
|
||||
|
||||
Avatar {
|
||||
id: displayAvatar
|
||||
|
||||
url: profile.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredHeight: 130
|
||||
Layout.preferredWidth: 130
|
||||
displayName: profile.displayName
|
||||
url: profile.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
userid: profile.userid
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
onClicked: TimelineManager.openImageOverlay(null, profile.avatarUrl, "", 0, 0)
|
||||
|
||||
ImageButton {
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
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.top: displayAvatar.top
|
||||
anchors.leftMargin: Nheko.paddingMedium
|
||||
anchors.top: displayAvatar.top
|
||||
anchors.topMargin: Nheko.paddingMedium
|
||||
visible: profile.isSelf
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/edit.svg"
|
||||
visible: profile.isSelf
|
||||
|
||||
onClicked: profile.changeAvatar()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Spinner {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
foreground: palette.mid
|
||||
running: profile.isLoading
|
||||
visible: profile.isLoading
|
||||
foreground: palette.mid
|
||||
}
|
||||
|
||||
Text {
|
||||
id: errorText
|
||||
|
||||
color: "red"
|
||||
visible: opacity > 0
|
||||
opacity: 0
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
color: "red"
|
||||
opacity: 0
|
||||
visible: opacity > 0
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: hideErrorAnimation
|
||||
|
||||
|
@ -101,16 +113,13 @@ ApplicationWindow {
|
|||
PauseAnimation {
|
||||
duration: 4000
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: errorText
|
||||
property: 'opacity'
|
||||
to: 0
|
||||
duration: 1000
|
||||
property: 'opacity'
|
||||
target: errorText
|
||||
to: 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onDisplayError(errorMessage) {
|
||||
errorText.text = errorMessage;
|
||||
|
@ -120,22 +129,22 @@ ApplicationWindow {
|
|||
|
||||
target: profile
|
||||
}
|
||||
|
||||
TextInput {
|
||||
id: displayUsername
|
||||
|
||||
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.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
|
||||
wrapMode: TextInput.Wrap
|
||||
readOnly: !isUsernameEditingAllowed
|
||||
selectByMouse: true
|
||||
text: profile.displayName
|
||||
wrapMode: TextInput.Wrap
|
||||
|
||||
onAccepted: {
|
||||
profile.changeUsername(displayUsername.text);
|
||||
displayUsername.isUsernameEditingAllowed = false;
|
||||
|
@ -143,14 +152,16 @@ ApplicationWindow {
|
|||
|
||||
ImageButton {
|
||||
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.leftMargin: Nheko.paddingSmall
|
||||
anchors.verticalCenter: displayUsername.verticalCenter
|
||||
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"
|
||||
visible: profile.isSelf
|
||||
|
||||
onClicked: {
|
||||
if (displayUsername.isUsernameEditingAllowed) {
|
||||
profile.changeUsername(displayUsername.text);
|
||||
|
@ -162,82 +173,79 @@ ApplicationWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: profile.userid
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: profile.userid
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
id: statusMsg
|
||||
text: qsTr("<i><b>Status:</b> %1</i>").arg(userStatus)
|
||||
visible: userStatus != ""
|
||||
|
||||
property string userStatus: Presence.userStatus(profile.userid)
|
||||
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: TextEdit.AlignHCenter
|
||||
Layout.leftMargin: Nheko.paddingMedium
|
||||
Layout.rightMargin: Nheko.paddingMedium
|
||||
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 {
|
||||
target: Presence
|
||||
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 {
|
||||
visible: !profile.isGlobalUserProfile
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: Nheko.paddingSmall
|
||||
visible: !profile.isGlobalUserProfile
|
||||
|
||||
MatrixText {
|
||||
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.visible: ma.hovered
|
||||
Layout.maximumWidth: parent.parent.width - (parent.spacing * 3) - 16
|
||||
horizontalAlignment: TextEdit.AlignHCenter
|
||||
text: qsTr("Room: %1").arg(profile.room ? profile.room.roomName : "")
|
||||
|
||||
HoverHandler {
|
||||
id: ma
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
image: ":/icons/icons/ui/world.svg"
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Open the global profile for this user.")
|
||||
ToolTip.visible: hovered
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/world.svg"
|
||||
|
||||
onClicked: profile.openGlobalProfile()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Button {
|
||||
id: verifyUserButton
|
||||
|
||||
text: qsTr("Verify")
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
enabled: profile.userVerified != Crypto.Verified
|
||||
text: qsTr("Verify")
|
||||
visible: profile.userVerified != Crypto.Verified && !profile.isSelf && profile.userVerificationEnabled
|
||||
|
||||
onClicked: profile.verify()
|
||||
}
|
||||
|
||||
EncryptionIndicator {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredHeight: 32
|
||||
Layout.preferredWidth: 32
|
||||
ToolTip.visible: false
|
||||
encrypted: profile.userVerificationEnabled
|
||||
trust: profile.userVerified
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
ToolTip.visible: false
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
// ImageButton{
|
||||
// image:":/icons/icons/ui/volume-off-indicator.svg"
|
||||
|
@ -259,139 +267,135 @@ ApplicationWindow {
|
|||
ImageButton {
|
||||
Layout.preferredHeight: 24
|
||||
Layout.preferredWidth: 24
|
||||
image: ":/icons/icons/ui/chat.svg"
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Start a private chat.")
|
||||
ToolTip.visible: hovered
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/chat.svg"
|
||||
|
||||
onClicked: profile.startChat()
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
Layout.preferredHeight: 24
|
||||
Layout.preferredWidth: 24
|
||||
image: ":/icons/icons/ui/round-remove-button.svg"
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
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()
|
||||
}
|
||||
|
||||
onClicked: profile.kickUser()
|
||||
}
|
||||
ImageButton {
|
||||
Layout.preferredHeight: 24
|
||||
Layout.preferredWidth: 24
|
||||
image: ":/icons/icons/ui/ban.svg"
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
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()
|
||||
}
|
||||
|
||||
onClicked: profile.banUser()
|
||||
}
|
||||
ImageButton {
|
||||
Layout.preferredHeight: 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.visible: hovered
|
||||
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
|
||||
}
|
||||
|
||||
onClicked: profile.ignored = !profile.ignored
|
||||
}
|
||||
ImageButton {
|
||||
Layout.preferredHeight: 24
|
||||
Layout.preferredWidth: 24
|
||||
image: ":/icons/icons/ui/refresh.svg"
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Refresh device list.")
|
||||
ToolTip.visible: hovered
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/refresh.svg"
|
||||
|
||||
onClicked: profile.refreshDevices()
|
||||
}
|
||||
}
|
||||
|
||||
TabBar {
|
||||
id: tabbar
|
||||
visible: !profile.isSelf
|
||||
|
||||
Layout.bottomMargin: Nheko.paddingMedium
|
||||
Layout.fillWidth: true
|
||||
visible: !profile.isSelf
|
||||
|
||||
onCurrentIndexChanged: devicelist.selectedTab = currentIndex
|
||||
|
||||
|
||||
NhekoTabButton {
|
||||
text: qsTr("Devices")
|
||||
}
|
||||
NhekoTabButton {
|
||||
text: qsTr("Shared Rooms")
|
||||
}
|
||||
|
||||
Layout.bottomMargin: Nheko.paddingMedium
|
||||
}
|
||||
}
|
||||
|
||||
model: (selectedTab == 0) ? devicesModel : sharedRoomsModel
|
||||
|
||||
DelegateModel {
|
||||
id: devicesModel
|
||||
|
||||
model: profile.deviceList
|
||||
|
||||
delegate: RowLayout {
|
||||
required property int verificationStatus
|
||||
required property string deviceId
|
||||
required property string deviceName
|
||||
required property string lastIp
|
||||
required property var lastTs
|
||||
required property int verificationStatus
|
||||
|
||||
width: devicelist.width
|
||||
spacing: 4
|
||||
width: devicelist.width
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
Layout.leftMargin: Nheko.paddingMedium
|
||||
Layout.rightMargin: Nheko.paddingMedium
|
||||
spacing: 0
|
||||
|
||||
RowLayout {
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.fillWidth: true
|
||||
color: palette.text
|
||||
elide: Text.ElideRight
|
||||
font.bold: true
|
||||
color: palette.text
|
||||
text: deviceId
|
||||
}
|
||||
|
||||
Image {
|
||||
Layout.preferredHeight: 16
|
||||
Layout.preferredWidth: 16
|
||||
visible: profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE
|
||||
sourceSize.height: 16 * Screen.devicePixelRatio
|
||||
sourceSize.width: 16 * Screen.devicePixelRatio
|
||||
source: {
|
||||
switch (verificationStatus) {
|
||||
case VerificationStatus.VERIFIED:
|
||||
case VerificationStatus.VERIFIED:
|
||||
return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green;
|
||||
case VerificationStatus.UNVERIFIED:
|
||||
case VerificationStatus.UNVERIFIED:
|
||||
return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?" + Nheko.theme.orange;
|
||||
case VerificationStatus.SELF:
|
||||
case VerificationStatus.SELF:
|
||||
return "image://colorimage/:/icons/icons/ui/checkmark.svg?" + Nheko.theme.green;
|
||||
default:
|
||||
default:
|
||||
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 {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
image: ":/icons/icons/ui/power-off.svg"
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
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
|
||||
|
||||
onClicked: profile.signOutDevice(deviceId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: deviceNameRow
|
||||
|
||||
|
@ -400,24 +404,25 @@ ApplicationWindow {
|
|||
TextInput {
|
||||
id: deviceNameField
|
||||
|
||||
readOnly: !deviceNameRow.isEditingAllowed
|
||||
text: deviceName
|
||||
color: palette.text
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.fillWidth: true
|
||||
color: palette.text
|
||||
readOnly: !deviceNameRow.isEditingAllowed
|
||||
selectByMouse: true
|
||||
text: deviceName
|
||||
|
||||
onAccepted: {
|
||||
profile.changeDeviceName(deviceId, deviceNameField.text);
|
||||
deviceNameRow.isEditingAllowed = false;
|
||||
}
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
visible: profile.isSelf
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Change device name.")
|
||||
ToolTip.visible: hovered
|
||||
hoverEnabled: true
|
||||
image: deviceNameRow.isEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
|
||||
visible: profile.isSelf
|
||||
|
||||
onClicked: {
|
||||
if (deviceNameRow.isEditingAllowed) {
|
||||
profile.changeDeviceName(deviceId, deviceNameField.text);
|
||||
|
@ -429,111 +434,88 @@ ApplicationWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: profile.isSelf
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
color: palette.text
|
||||
elide: Text.ElideRight
|
||||
text: qsTr("Last seen %1 from %2").arg(new Date(lastTs).toLocaleString(Locale.ShortFormat)).arg(lastIp ? lastIp : "???")
|
||||
visible: profile.isSelf
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Image {
|
||||
Layout.preferredHeight: 16
|
||||
Layout.preferredWidth: 16
|
||||
visible: !profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE
|
||||
source: {
|
||||
switch (verificationStatus) {
|
||||
case VerificationStatus.VERIFIED:
|
||||
case VerificationStatus.VERIFIED:
|
||||
return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green;
|
||||
case VerificationStatus.UNVERIFIED:
|
||||
case VerificationStatus.UNVERIFIED:
|
||||
return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?" + Nheko.theme.orange;
|
||||
case VerificationStatus.SELF:
|
||||
case VerificationStatus.SELF:
|
||||
return "image://colorimage/:/icons/icons/ui/checkmark.svg?" + Nheko.theme.green;
|
||||
default:
|
||||
default:
|
||||
return "image://colorimage/:/icons/icons/ui/shield-filled.svg?" + Nheko.theme.red;
|
||||
}
|
||||
}
|
||||
visible: !profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE
|
||||
}
|
||||
|
||||
Button {
|
||||
id: verifyButton
|
||||
|
||||
visible: verificationStatus == VerificationStatus.UNVERIFIED && (profile.isSelf || !profile.userVerificationEnabled)
|
||||
text: (verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify")
|
||||
visible: verificationStatus == VerificationStatus.UNVERIFIED && (profile.isSelf || !profile.userVerificationEnabled)
|
||||
|
||||
onClicked: {
|
||||
if (verificationStatus == VerificationStatus.VERIFIED)
|
||||
profile.unverify(deviceId);
|
||||
profile.unverify(deviceId);
|
||||
else
|
||||
profile.verify(deviceId);
|
||||
profile.verify(deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
DelegateModel {
|
||||
id: sharedRoomsModel
|
||||
|
||||
model: profile.sharedRooms
|
||||
|
||||
delegate: RowLayout {
|
||||
required property string avatarUrl
|
||||
required property string roomId
|
||||
required property string roomName
|
||||
required property string avatarUrl
|
||||
|
||||
width: devicelist.width
|
||||
spacing: 4
|
||||
|
||||
width: devicelist.width
|
||||
|
||||
Avatar {
|
||||
id: avatar
|
||||
|
||||
enabled: false
|
||||
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 1.6)
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: Nheko.paddingMedium
|
||||
|
||||
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 1.6)
|
||||
Layout.preferredHeight: avatarSize
|
||||
Layout.preferredWidth: avatarSize
|
||||
url: avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
roomid: roomId
|
||||
displayName: roomName
|
||||
enabled: false
|
||||
roomid: roomId
|
||||
url: avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
}
|
||||
|
||||
ElidedLabel {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: palette.text
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: Nheko.paddingMedium
|
||||
color: palette.text
|
||||
elideWidth: width
|
||||
fullText: roomName
|
||||
textFormat: Text.PlainText
|
||||
Layout.rightMargin: Nheko.paddingMedium
|
||||
}
|
||||
|
||||
Item {
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,17 +12,17 @@ Menu {
|
|||
id: stickerPopup
|
||||
|
||||
property var callback
|
||||
property string roomid
|
||||
property alias model: gridView.model
|
||||
required property bool emoji
|
||||
property var textArea
|
||||
property real highlightHue: palette.highlight.hslHue
|
||||
property real highlightSat: palette.highlight.hslSaturation
|
||||
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 stickerDimPad: stickerDim + Nheko.paddingSmall
|
||||
readonly property int stickersPerRow: emoji ? 7 : 3
|
||||
readonly property int sidebarAvatarSize: 24
|
||||
property var textArea
|
||||
|
||||
function show(showAt, roomid_, callback) {
|
||||
console.debug("Showing sticker picker");
|
||||
|
@ -31,29 +31,29 @@ Menu {
|
|||
popup(showAt ? showAt : null);
|
||||
}
|
||||
|
||||
margins: 2
|
||||
bottomPadding: 0
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
focus: true
|
||||
leftPadding: 0
|
||||
margins: 2
|
||||
modal: true
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
modal: true
|
||||
focus: true
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
width: sidebarAvatarSize + Nheko.paddingSmall + stickersPerRow * stickerDimPad + 20
|
||||
|
||||
Rectangle {
|
||||
color: palette.window
|
||||
height: columnView.implicitHeight + Nheko.paddingSmall*2
|
||||
height: columnView.implicitHeight + Nheko.paddingSmall * 2
|
||||
width: sidebarAvatarSize + Nheko.paddingSmall + stickersPerRow * stickerDimPad + 20
|
||||
|
||||
GridLayout {
|
||||
id: columnView
|
||||
|
||||
anchors.leftMargin: Nheko.paddingSmall
|
||||
anchors.rightMargin: Nheko.paddingSmall
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Nheko.paddingSmall
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Nheko.paddingSmall
|
||||
columns: 2
|
||||
rows: 2
|
||||
|
||||
|
@ -61,14 +61,15 @@ Menu {
|
|||
TextField {
|
||||
id: emojiSearch
|
||||
|
||||
Layout.column: 1
|
||||
Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - Nheko.paddingSmall
|
||||
Layout.row: 0
|
||||
Layout.column: 1
|
||||
background: null
|
||||
placeholderTextColor: palette.buttonText
|
||||
placeholderText: qsTr("Search")
|
||||
selectByMouse: true
|
||||
placeholderTextColor: palette.buttonText
|
||||
rightPadding: clearSearch.width
|
||||
selectByMouse: true
|
||||
|
||||
onTextChanged: searchTimer.restart()
|
||||
onVisibleChanged: {
|
||||
if (visible)
|
||||
|
@ -81,23 +82,24 @@ Menu {
|
|||
id: searchTimer
|
||||
|
||||
interval: 350 // tweak as needed?
|
||||
|
||||
onTriggered: stickerPopup.model.searchString = emojiSearch.text
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: clearSearch
|
||||
|
||||
focusPolicy: Qt.NoFocus
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/round-remove-button.svg"
|
||||
visible: emojiSearch.text !== ''
|
||||
|
||||
image: ":/icons/icons/ui/round-remove-button.svg"
|
||||
focusPolicy: Qt.NoFocus
|
||||
onClicked: emojiSearch.clear()
|
||||
hoverEnabled: true
|
||||
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
right: parent.right
|
||||
rightMargin: Nheko.paddingSmall
|
||||
top: parent.top
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -106,39 +108,30 @@ Menu {
|
|||
ListView {
|
||||
id: gridView
|
||||
|
||||
model: roomid ? TimelineManager.completerFor(stickerPopup.emoji ? "emojigrid" : "stickergrid", roomid) : null
|
||||
Layout.row: 1
|
||||
property int cellHeight: stickerDimPad
|
||||
|
||||
Layout.column: 1
|
||||
Layout.preferredHeight: cellHeight * (stickersPerRow + 0.5)
|
||||
Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - Nheko.paddingSmall
|
||||
property int cellHeight: stickerDimPad
|
||||
Layout.row: 1
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
clip: true
|
||||
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.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.property: "packname"
|
||||
spacing: Nheko.paddingSmall
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
id: emojiScroll
|
||||
|
||||
}
|
||||
|
||||
// Individual emoji
|
||||
delegate: Row {
|
||||
required property var row;
|
||||
required property var row
|
||||
|
||||
spacing: Nheko.paddingSmall
|
||||
|
||||
|
@ -150,11 +143,45 @@ Menu {
|
|||
|
||||
required property var modelData
|
||||
|
||||
width: stickerDim
|
||||
height: stickerDim
|
||||
hoverEnabled: true
|
||||
ToolTip.text: ":" + modelData.shortcode + ": - " + (modelData.unicode ? modelData.unicodeName : modelData.body)
|
||||
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?
|
||||
onClicked: {
|
||||
console.debug("Picked " + modelData);
|
||||
|
@ -170,95 +197,63 @@ Menu {
|
|||
callback(modelData.url, modelData.markdown);
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: DelegateChooser {
|
||||
roleValue: del.modelData.unicode != undefined
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: true
|
||||
|
||||
Text {
|
||||
width: stickerDim
|
||||
height: stickerDim
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
section.delegate: Rectangle {
|
||||
required property string section
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
id: emojiScroll
|
||||
color: palette.alternateBase
|
||||
height: childrenRect.height
|
||||
width: gridView.width
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
font.bold: true
|
||||
text: parent.section
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ListView {
|
||||
Layout.row: 1
|
||||
Layout.column: 0
|
||||
Layout.preferredWidth: sidebarAvatarSize
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: sidebarAvatarSize
|
||||
Layout.rightMargin: Nheko.paddingSmall
|
||||
|
||||
Layout.row: 1
|
||||
clip: true
|
||||
model: gridView.model ? gridView.model.sections : null
|
||||
spacing: Nheko.paddingSmall
|
||||
clip: true
|
||||
|
||||
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.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)
|
||||
}
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
Layout.row: 0
|
||||
Layout.column: 0
|
||||
Layout.preferredWidth: sidebarAvatarSize
|
||||
Layout.preferredHeight: sidebarAvatarSize
|
||||
Layout.preferredWidth: sidebarAvatarSize
|
||||
Layout.rightMargin: Nheko.paddingSmall
|
||||
|
||||
image: ":/icons/icons/ui/settings.svg"
|
||||
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
Layout.row: 0
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,147 +13,139 @@ import "../"
|
|||
|
||||
Item {
|
||||
id: loginPage
|
||||
property int maxExpansion: 400
|
||||
|
||||
property string error: login.error
|
||||
property int maxExpansion: 400
|
||||
|
||||
Login {
|
||||
id: login
|
||||
}
|
||||
|
||||
}
|
||||
ScrollView {
|
||||
id: scroll
|
||||
|
||||
clip: false
|
||||
ScrollBar.horizontal.visible: false
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Nheko.paddingLarge
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: Math.min(loginPage.height, col.implicitHeight)
|
||||
anchors.margins: Nheko.paddingLarge
|
||||
|
||||
clip: false
|
||||
contentWidth: availableWidth
|
||||
height: Math.min(loginPage.height, col.implicitHeight)
|
||||
|
||||
ColumnLayout {
|
||||
id: col
|
||||
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: Math.min(loginPage.maxExpansion, scroll.width- Nheko.paddingLarge*2)
|
||||
spacing: Nheko.paddingMedium
|
||||
width: Math.min(loginPage.maxExpansion, scroll.width - Nheko.paddingLarge * 2)
|
||||
|
||||
Image {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
source: "qrc:/logos/login.png"
|
||||
Layout.preferredHeight: 128
|
||||
Layout.preferredWidth: 128
|
||||
source: "qrc:/logos/login.png"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Nheko.paddingLarge
|
||||
|
||||
Layout.fillWidth: true
|
||||
MatrixTextField {
|
||||
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")
|
||||
placeholderText: qsTr("e.g @user:yourserver.example.com")
|
||||
|
||||
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 {
|
||||
Layout.preferredHeight: matrixIdLabel.height/2
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
|
||||
visible: running
|
||||
running: login.lookingUpHs
|
||||
Layout.preferredHeight: matrixIdLabel.height / 2
|
||||
foreground: palette.mid
|
||||
running: login.lookingUpHs
|
||||
visible: running
|
||||
}
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
Layout.fillWidth: true
|
||||
textFormat: Text.PlainText
|
||||
color: Nheko.theme.error
|
||||
text: login.mxidError
|
||||
textFormat: Text.PlainText
|
||||
visible: text
|
||||
wrapMode: TextEdit.Wrap
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
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 {
|
||||
id: deviceNameLabel
|
||||
|
||||
Keys.forwardTo: [pwBtn, ssoRepeater]
|
||||
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")
|
||||
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 {
|
||||
id: hsLabel
|
||||
enabled: visible
|
||||
visible: login.homeserverNeeded
|
||||
|
||||
Keys.forwardTo: [pwBtn, ssoRepeater]
|
||||
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")
|
||||
placeholderText: qsTr("yourserver.example.com:8787")
|
||||
text: login.homeserver
|
||||
onEditingFinished: login.homeserver = text
|
||||
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]
|
||||
}
|
||||
visible: login.homeserverNeeded
|
||||
|
||||
onEditingFinished: login.homeserver = text
|
||||
}
|
||||
Item {
|
||||
Layout.preferredHeight: Nheko.avatarSize
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Nheko.avatarSize
|
||||
|
||||
Spinner {
|
||||
height: parent.height
|
||||
anchors.centerIn: parent
|
||||
|
||||
visible: running
|
||||
running: login.loggingIn
|
||||
foreground: palette.mid
|
||||
height: parent.height
|
||||
running: login.loggingIn
|
||||
visible: running
|
||||
}
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
Layout.fillWidth: true
|
||||
textFormat: Text.PlainText
|
||||
color: Nheko.theme.error
|
||||
text: loginPage.error
|
||||
textFormat: Text.PlainText
|
||||
visible: text
|
||||
wrapMode: TextEdit.Wrap
|
||||
}
|
||||
|
||||
FlatButton {
|
||||
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() {
|
||||
login.onLoginButtonClicked(Login.Password, matrixIdLabel.text, passwordLabel.text, deviceNameLabel.text)
|
||||
login.onLoginButtonClicked(Login.Password, matrixIdLabel.text, passwordLabel.text, deviceNameLabel.text);
|
||||
}
|
||||
onClicked: pwBtn.pwLogin()
|
||||
|
||||
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()
|
||||
Keys.enabled: pwBtn.enabled && login.passwordSupported
|
||||
onClicked: pwBtn.pwLogin()
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: ssoRepeater
|
||||
|
||||
|
@ -161,32 +153,35 @@ Item {
|
|||
|
||||
delegate: FlatButton {
|
||||
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() {
|
||||
login.onLoginButtonClicked(Login.SSO, matrixIdLabel.text, modelData.id, deviceNameLabel.text)
|
||||
login.onLoginButtonClicked(Login.SSO, matrixIdLabel.text, modelData.id, deviceNameLabel.text);
|
||||
}
|
||||
onClicked: ssoBtn.ssoLogin()
|
||||
|
||||
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()
|
||||
Keys.enabled: ssoBtn.enabled && !login.passwordSupported
|
||||
onClicked: ssoBtn.ssoLogin()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
anchors.top: parent.top
|
||||
ToolTip.text: qsTr("Back")
|
||||
ToolTip.visible: hovered
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
width: Nheko.avatarSize
|
||||
anchors.top: parent.top
|
||||
height: Nheko.avatarSize
|
||||
image: ":/icons/icons/ui/angle-arrow-left.svg"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Back")
|
||||
width: Nheko.avatarSize
|
||||
|
||||
onClicked: mainWindow.pop()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,208 +13,197 @@ import "../"
|
|||
|
||||
Item {
|
||||
id: registrationPage
|
||||
property int maxExpansion: 400
|
||||
|
||||
property string error: regis.error
|
||||
property int maxExpansion: 400
|
||||
|
||||
Registration {
|
||||
id: regis
|
||||
}
|
||||
|
||||
}
|
||||
ScrollView {
|
||||
id: scroll
|
||||
|
||||
clip: false
|
||||
ScrollBar.horizontal.visible: false
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Nheko.paddingLarge
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: Math.min(registrationPage.height, col.implicitHeight)
|
||||
anchors.margins: Nheko.paddingLarge
|
||||
|
||||
clip: false
|
||||
contentWidth: availableWidth
|
||||
height: Math.min(registrationPage.height, col.implicitHeight)
|
||||
|
||||
ColumnLayout {
|
||||
id: col
|
||||
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: Math.min(registrationPage.maxExpansion, scroll.width- Nheko.paddingLarge*2)
|
||||
spacing: Nheko.paddingMedium
|
||||
width: Math.min(registrationPage.maxExpansion, scroll.width - Nheko.paddingLarge * 2)
|
||||
|
||||
Image {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
source: "qrc:/logos/login.png"
|
||||
Layout.preferredHeight: 128
|
||||
Layout.preferredWidth: 128
|
||||
source: "qrc:/logos/login.png"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Nheko.paddingLarge
|
||||
|
||||
Layout.fillWidth: true
|
||||
MatrixTextField {
|
||||
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.")
|
||||
label: qsTr("Homeserver")
|
||||
placeholderText: qsTr("your.server")
|
||||
|
||||
onEditingFinished: regis.setServer(text)
|
||||
}
|
||||
|
||||
|
||||
Spinner {
|
||||
Layout.preferredHeight: hsLabel.height/2
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
|
||||
visible: running
|
||||
running: regis.lookingUpHs
|
||||
Layout.preferredHeight: hsLabel.height / 2
|
||||
foreground: palette.mid
|
||||
running: regis.lookingUpHs
|
||||
visible: running
|
||||
}
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
Layout.fillWidth: true
|
||||
textFormat: Text.PlainText
|
||||
color: Nheko.theme.error
|
||||
text: regis.hsError
|
||||
textFormat: Text.PlainText
|
||||
visible: text
|
||||
wrapMode: TextEdit.Wrap
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Nheko.paddingLarge
|
||||
|
||||
visible: regis.supported
|
||||
|
||||
Layout.fillWidth: true
|
||||
MatrixTextField {
|
||||
id: usernameLabel
|
||||
|
||||
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 /.")
|
||||
label: qsTr("Username")
|
||||
|
||||
onEditingFinished: regis.checkUsername(text)
|
||||
}
|
||||
Spinner {
|
||||
Layout.preferredHeight: usernameLabel.height/2
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
|
||||
visible: running
|
||||
running: regis.lookingUpUsername
|
||||
Layout.preferredHeight: usernameLabel.height / 2
|
||||
foreground: palette.mid
|
||||
running: regis.lookingUpUsername
|
||||
visible: running
|
||||
}
|
||||
|
||||
Image {
|
||||
Layout.preferredHeight: 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
|
||||
Layout.preferredHeight: usernameLabel.height / 2
|
||||
Layout.preferredWidth: usernameLabel.height / 2
|
||||
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.width: width * Screen.devicePixelRatio
|
||||
visible: regis.usernameAvailable || regis.usernameUnavailable
|
||||
|
||||
HoverHandler {
|
||||
id: ma
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
Layout.fillWidth: true
|
||||
textFormat: Text.PlainText
|
||||
color: Nheko.theme.error
|
||||
text: regis.usernameError
|
||||
textFormat: Text.PlainText
|
||||
visible: text && regis.supported
|
||||
wrapMode: TextEdit.Wrap
|
||||
}
|
||||
|
||||
|
||||
MatrixTextField {
|
||||
visible: regis.supported
|
||||
id: passwordLabel
|
||||
|
||||
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.")
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
visible: regis.supported
|
||||
id: passwordConfirmationLabel
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("Password confirmation")
|
||||
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 {
|
||||
Layout.fillWidth: true
|
||||
visible: regis.supported
|
||||
textFormat: Text.PlainText
|
||||
color: Nheko.theme.error
|
||||
text: passwordLabel.text != passwordConfirmationLabel.text ? qsTr("Your passwords do not match!") : ""
|
||||
textFormat: Text.PlainText
|
||||
visible: regis.supported
|
||||
wrapMode: TextEdit.Wrap
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
visible: regis.supported
|
||||
id: deviceNameLabel
|
||||
|
||||
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")
|
||||
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 {
|
||||
Layout.preferredHeight: Nheko.avatarSize
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Nheko.avatarSize
|
||||
|
||||
Spinner {
|
||||
height: parent.height
|
||||
anchors.centerIn: parent
|
||||
|
||||
visible: running
|
||||
running: regis.registering
|
||||
foreground: palette.mid
|
||||
height: parent.height
|
||||
running: regis.registering
|
||||
visible: running
|
||||
}
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
Layout.fillWidth: true
|
||||
textFormat: Text.PlainText
|
||||
color: Nheko.theme.error
|
||||
text: registrationPage.error
|
||||
textFormat: Text.PlainText
|
||||
visible: text
|
||||
wrapMode: TextEdit.Wrap
|
||||
}
|
||||
|
||||
FlatButton {
|
||||
id: regisBtn
|
||||
visible: regis.supported
|
||||
enabled: usernameLabel.text && passwordLabel.text && passwordLabel.text == passwordConfirmationLabel.text
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("REGISTER")
|
||||
|
||||
function register() {
|
||||
regis.startRegistration(usernameLabel.text, passwordLabel.text, deviceNameLabel.text)
|
||||
regis.startRegistration(usernameLabel.text, passwordLabel.text, deviceNameLabel.text);
|
||||
}
|
||||
onClicked: regisBtn.register()
|
||||
|
||||
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()
|
||||
Keys.enabled: regisBtn.enabled && regis.supported
|
||||
onClicked: regisBtn.register()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
anchors.top: parent.top
|
||||
ToolTip.text: qsTr("Back")
|
||||
ToolTip.visible: hovered
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
width: Nheko.avatarSize
|
||||
anchors.top: parent.top
|
||||
height: Nheko.avatarSize
|
||||
image: ":/icons/icons/ui/angle-arrow-left.svg"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Back")
|
||||
width: Nheko.avatarSize
|
||||
|
||||
onClicked: mainWindow.pop()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ Rectangle {
|
|||
|
||||
property int collapsePoint: 600
|
||||
property bool collapsed: width < collapsePoint
|
||||
|
||||
color: palette.window
|
||||
|
||||
ScrollView {
|
||||
|
@ -23,87 +24,91 @@ Rectangle {
|
|||
|
||||
ScrollBar.horizontal.visible: false
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: (collapsed? backButton.height : 0)+Nheko.paddingLarge
|
||||
leftPadding: collapsed? Nheko.paddingMedium : Nheko.paddingLarge
|
||||
anchors.topMargin: (collapsed ? backButton.height : 0) + Nheko.paddingLarge
|
||||
bottomPadding: Nheko.paddingLarge
|
||||
contentWidth: availableWidth
|
||||
leftPadding: collapsed ? Nheko.paddingMedium : Nheko.paddingLarge
|
||||
|
||||
ColumnLayout {
|
||||
id: grid
|
||||
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
width: scroll.availableWidth
|
||||
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
|
||||
|
||||
spacing: Nheko.paddingMedium
|
||||
width: scroll.availableWidth
|
||||
|
||||
Repeater {
|
||||
model: UserSettingsModel
|
||||
|
||||
delegate: GridLayout {
|
||||
width: scroll.availableWidth
|
||||
columns: collapsed? 1 : 2
|
||||
rows: collapsed? 2: 1
|
||||
required property var model
|
||||
id: r
|
||||
|
||||
required property var model
|
||||
|
||||
columns: collapsed ? 1 : 2
|
||||
rows: collapsed ? 2 : 1
|
||||
width: scroll.availableWidth
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.fillWidth: true
|
||||
color: palette.text
|
||||
text: model.name
|
||||
//Layout.column: 0
|
||||
Layout.columnSpan: (model.type == UserSettingsModel.SectionTitle && !userSettingsDialog.collapsed) ? 2 : 1
|
||||
Layout.fillWidth: true
|
||||
//Layout.row: model.index
|
||||
//Layout.minimumWidth: implicitWidth
|
||||
Layout.leftMargin: model.type == UserSettingsModel.SectionTitle ? 0 : Nheko.paddingMedium
|
||||
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
|
||||
text: model.name
|
||||
wrapMode: Text.Wrap
|
||||
|
||||
HoverHandler {
|
||||
id: hovered
|
||||
|
||||
enabled: model.description ?? false
|
||||
}
|
||||
ToolTip.visible: hovered.hovered && model.description
|
||||
ToolTip.text: model.description ?? ""
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
DelegateChooser {
|
||||
id: chooser
|
||||
|
||||
roleValue: model.type
|
||||
Layout.alignment: Qt.AlignRight
|
||||
|
||||
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.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
|
||||
roleValue: model.type
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: UserSettingsModel.Toggle
|
||||
|
||||
ToggleButton {
|
||||
checked: model.value
|
||||
onCheckedChanged: model.value = checked
|
||||
enabled: model.enabled
|
||||
|
||||
onCheckedChanged: model.value = checked
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: UserSettingsModel.Options
|
||||
|
||||
ComboBox {
|
||||
anchors.right: parent.right
|
||||
model: r.model.values
|
||||
currentIndex: r.model.value
|
||||
width: Math.min(implicitWidth, scroll.availableWidth - Nheko.paddingMedium)
|
||||
onCurrentIndexChanged: r.model.value = currentIndex
|
||||
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 {
|
||||
|
@ -111,14 +116,16 @@ Rectangle {
|
|||
|
||||
SpinBox {
|
||||
anchors.right: parent.right
|
||||
from: model.valueLowerBound
|
||||
to: model.valueUpperBound
|
||||
stepSize: model.valueStep
|
||||
value: model.value
|
||||
onValueChanged: model.value = value
|
||||
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 {
|
||||
|
@ -127,54 +134,56 @@ Rectangle {
|
|||
SpinBox {
|
||||
id: spinbox
|
||||
|
||||
readonly property double div: 100
|
||||
readonly property int decimals: 2
|
||||
|
||||
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
|
||||
|
||||
readonly property double div: 100
|
||||
property real realValue: value / div
|
||||
|
||||
anchors.right: parent.right
|
||||
editable: true
|
||||
from: model.valueLowerBound * 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 {
|
||||
bottom: Math.min(spinbox.from/spinbox.div, spinbox.to/spinbox.div)
|
||||
top: Math.max(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)
|
||||
}
|
||||
|
||||
textFromValue: function(value, locale) {
|
||||
return Number(value / spinbox.div).toLocaleString(locale, 'f', spinbox.decimals)
|
||||
}
|
||||
onValueChanged: model.value = value / div
|
||||
|
||||
valueFromText: function(text, locale) {
|
||||
return Number.fromLocaleString(locale, text) * spinbox.div
|
||||
}
|
||||
|
||||
WheelHandler{} // suppress scrolling changing values
|
||||
WheelHandler {
|
||||
} // suppress scrolling changing values
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: UserSettingsModel.ReadOnlyText
|
||||
|
||||
TextEdit {
|
||||
color: palette.text
|
||||
text: model.value
|
||||
readOnly: true
|
||||
text: model.value
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: UserSettingsModel.SectionTitle
|
||||
|
||||
Item {
|
||||
width: grid.width
|
||||
height: fontMetrics.lineSpacing
|
||||
width: grid.width
|
||||
|
||||
Rectangle {
|
||||
anchors.topMargin: Nheko.paddingSmall
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Nheko.paddingSmall
|
||||
color: palette.buttonText
|
||||
height: 1
|
||||
}
|
||||
|
@ -182,6 +191,7 @@ Rectangle {
|
|||
}
|
||||
DelegateChoice {
|
||||
roleValue: UserSettingsModel.KeyStatus
|
||||
|
||||
Text {
|
||||
color: model.good ? "green" : Nheko.theme.error
|
||||
text: model.value ? qsTr("CACHED") : qsTr("NOT CACHED")
|
||||
|
@ -189,34 +199,42 @@ Rectangle {
|
|||
}
|
||||
DelegateChoice {
|
||||
roleValue: UserSettingsModel.SessionKeyImportExport
|
||||
|
||||
RowLayout {
|
||||
Button {
|
||||
text: qsTr("IMPORT")
|
||||
|
||||
onClicked: UserSettingsModel.importSessionKeys()
|
||||
}
|
||||
Button {
|
||||
text: qsTr("EXPORT")
|
||||
|
||||
onClicked: UserSettingsModel.exportSessionKeys()
|
||||
}
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: UserSettingsModel.XSignKeysRequestDownload
|
||||
|
||||
RowLayout {
|
||||
Button {
|
||||
text: qsTr("DOWNLOAD")
|
||||
|
||||
onClicked: UserSettingsModel.downloadCrossSigningSecrets()
|
||||
}
|
||||
Button {
|
||||
text: qsTr("REQUEST")
|
||||
|
||||
onClicked: UserSettingsModel.requestCrossSigningSecrets()
|
||||
}
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: UserSettingsModel.ConfigureHiddenEvents
|
||||
|
||||
Button {
|
||||
text: qsTr("CONFIGURE")
|
||||
|
||||
onClicked: {
|
||||
var dialog = hiddenEventsDialog.createObject();
|
||||
dialog.show();
|
||||
|
@ -226,15 +244,17 @@ Rectangle {
|
|||
Component {
|
||||
id: hiddenEventsDialog
|
||||
|
||||
HiddenEventsDialog {}
|
||||
HiddenEventsDialog {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: UserSettingsModel.ManageIgnoredUsers
|
||||
|
||||
Button {
|
||||
text: qsTr("MANAGE")
|
||||
|
||||
onClicked: {
|
||||
var dialog = ignoredUsersDialog.createObject();
|
||||
dialog.show();
|
||||
|
@ -244,11 +264,11 @@ Rectangle {
|
|||
Component {
|
||||
id: ignoredUsersDialog
|
||||
|
||||
IgnoredUsers {}
|
||||
IgnoredUsers {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
Text {
|
||||
text: model.value
|
||||
|
@ -259,19 +279,18 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: backButton
|
||||
anchors.top: parent.top
|
||||
|
||||
ToolTip.text: qsTr("Back")
|
||||
ToolTip.visible: hovered
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Nheko.paddingMedium
|
||||
width: Nheko.avatarSize
|
||||
anchors.top: parent.top
|
||||
height: Nheko.avatarSize
|
||||
image: ":/icons/icons/ui/angle-arrow-left.svg"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Back")
|
||||
width: Nheko.avatarSize
|
||||
|
||||
onClicked: mainWindow.pop()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -14,86 +14,83 @@ ColumnLayout {
|
|||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
Image {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
source: "qrc:/logos/splash.png"
|
||||
Layout.preferredHeight: 256
|
||||
Layout.preferredWidth: 256
|
||||
source: "qrc:/logos/splash.png"
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.margins: Nheko.paddingLarge
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.bottomMargin: 0
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Welcome to nheko! The desktop client for the Matrix protocol.")
|
||||
Layout.margins: Nheko.paddingLarge
|
||||
color: palette.text
|
||||
font.pointSize: fontMetrics.font.pointSize*2
|
||||
wrapMode: Text.Wrap
|
||||
font.pointSize: fontMetrics.font.pointSize * 2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: qsTr("Welcome to nheko! The desktop client for the Matrix protocol.")
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
Label {
|
||||
Layout.margins: Nheko.paddingLarge
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Enjoy your stay!")
|
||||
Layout.margins: Nheko.paddingLarge
|
||||
color: palette.text
|
||||
font.pointSize: fontMetrics.font.pointSize*1.5
|
||||
wrapMode: Text.Wrap
|
||||
font.pointSize: fontMetrics.font.pointSize * 1.5
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: qsTr("Enjoy your stay!")
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
FlatButton {
|
||||
Layout.margins: Nheko.paddingLarge
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.margins: Nheko.paddingLarge
|
||||
text: qsTr("REGISTER")
|
||||
|
||||
onClicked: {
|
||||
mainWindow.push(registerPage);
|
||||
}
|
||||
}
|
||||
FlatButton {
|
||||
Layout.margins: Nheko.paddingLarge
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.margins: Nheko.paddingLarge
|
||||
text: qsTr("LOGIN")
|
||||
|
||||
onClicked: {
|
||||
mainWindow.push(loginPage);
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.margins: Nheko.paddingLarge
|
||||
|
||||
ToggleButton {
|
||||
Layout.margins: Nheko.paddingLarge
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.margins: Nheko.paddingLarge
|
||||
checked: Settings.reducedMotion
|
||||
|
||||
onCheckedChanged: Settings.reducedMotion = checked
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
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
|
||||
text: qsTr("Reduce animations")
|
||||
|
||||
HoverHandler {
|
||||
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 {
|
||||
|
|
|
@ -9,42 +9,39 @@ import im.nheko 1.0
|
|||
Slider {
|
||||
id: control
|
||||
|
||||
property color progressColor: palette.highlight
|
||||
property bool alwaysShowSlider: true
|
||||
property color progressColor: palette.highlight
|
||||
property int sliderRadius: 16
|
||||
|
||||
value: 0
|
||||
implicitHeight: sliderRadius
|
||||
padding: 0
|
||||
value: 0
|
||||
|
||||
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
|
||||
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 {
|
||||
width: control.visualPosition * parent.width
|
||||
height: parent.height
|
||||
color: control.progressColor
|
||||
height: parent.height
|
||||
radius: 2
|
||||
width: control.visualPosition * parent.width
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@ Item {
|
|||
|
||||
property color color: "#22000000"
|
||||
property real maxRadius: Math.max(width, height)
|
||||
readonly property real opacityAnimationDuration: 300
|
||||
readonly property real radiusAnimationRate: 0.05
|
||||
readonly property real radiusTailAnimationRate: 0.5
|
||||
readonly property real opacityAnimationDuration: 300
|
||||
property var rippleTarget: parent
|
||||
|
||||
anchors.fill: parent
|
||||
|
@ -19,18 +19,13 @@ Item {
|
|||
PointHandler {
|
||||
id: ph
|
||||
|
||||
onGrabChanged: (_, point) => {
|
||||
circle.centerX = point.position.x
|
||||
circle.centerY = point.position.y
|
||||
}
|
||||
|
||||
target: Rectangle {
|
||||
id: backgroundLayer
|
||||
parent: rippleTarget
|
||||
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
clip: true
|
||||
color: "transparent"
|
||||
parent: rippleTarget
|
||||
|
||||
Rectangle {
|
||||
id: circle
|
||||
|
@ -38,15 +33,14 @@ Item {
|
|||
property real centerX
|
||||
property real centerY
|
||||
|
||||
color: ripple.color
|
||||
height: radius * 2
|
||||
radius: 0
|
||||
state: ph.active ? "ACTIVE" : "NORMAL"
|
||||
width: radius * 2
|
||||
x: centerX - radius
|
||||
y: centerY - radius
|
||||
|
||||
height: radius*2
|
||||
width: radius*2
|
||||
radius: 0
|
||||
color: ripple.color
|
||||
|
||||
state: ph.active ? "ACTIVE" : "NORMAL"
|
||||
states: [
|
||||
State {
|
||||
name: "NORMAL"
|
||||
|
@ -63,26 +57,30 @@ Item {
|
|||
SequentialAnimation {
|
||||
//PropertyAction { target: circle; property: "centerX"; value: ph.point.position.x }
|
||||
//PropertyAction { target: circle; property: "centerY"; value: ph.point.position.y }
|
||||
PropertyAction { target: circle; property: "visible"; value: true }
|
||||
PropertyAction { target: circle; property: "opacity"; value: 1 }
|
||||
|
||||
PropertyAction {
|
||||
property: "visible"
|
||||
target: circle
|
||||
value: true
|
||||
}
|
||||
PropertyAction {
|
||||
property: "opacity"
|
||||
target: circle
|
||||
value: 1
|
||||
}
|
||||
NumberAnimation {
|
||||
id: radius_animation
|
||||
|
||||
target: circle
|
||||
properties: "radius"
|
||||
from: 0
|
||||
to: ripple.maxRadius
|
||||
duration: ripple.maxRadius / ripple.radiusAnimationRate
|
||||
from: 0
|
||||
properties: "radius"
|
||||
target: circle
|
||||
to: ripple.maxRadius
|
||||
|
||||
easing {
|
||||
type: Easing.OutQuad
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
Transition {
|
||||
from: "ACTIVE"
|
||||
|
@ -93,37 +91,42 @@ Item {
|
|||
NumberAnimation {
|
||||
id: radius_tail_animation
|
||||
|
||||
target: circle
|
||||
properties: "radius"
|
||||
to: ripple.maxRadius
|
||||
duration: ripple.maxRadius / ripple.radiusTailAnimationRate
|
||||
properties: "radius"
|
||||
target: circle
|
||||
to: ripple.maxRadius
|
||||
|
||||
easing {
|
||||
type: Easing.Linear
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: opacity_animation
|
||||
|
||||
target: circle
|
||||
properties: "opacity"
|
||||
to: 0
|
||||
duration: ripple.opacityAnimationDuration
|
||||
properties: "opacity"
|
||||
target: circle
|
||||
to: 0
|
||||
|
||||
easing {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,8 @@ import im.nheko 1.0
|
|||
Popup {
|
||||
id: snackbar
|
||||
|
||||
// Workaround palettes not inheriting for popups
|
||||
palette: timelineRoot.palette
|
||||
|
||||
property var messages: []
|
||||
property string currentMessage: ""
|
||||
property var messages: []
|
||||
|
||||
function showNotification(msg) {
|
||||
messages.push(msg);
|
||||
|
@ -24,10 +21,61 @@ Popup {
|
|||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: dismissTimer
|
||||
interval: 10000
|
||||
onTriggered: snackbar.close()
|
||||
opacity: 0
|
||||
padding: Nheko.paddingLarge
|
||||
|
||||
// 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: {
|
||||
|
@ -41,61 +89,11 @@ Popup {
|
|||
}
|
||||
}
|
||||
|
||||
parent: Overlay.overlay
|
||||
opacity: 0
|
||||
y: -100
|
||||
x: (parent.width - width)/2
|
||||
padding: Nheko.paddingLarge
|
||||
Timer {
|
||||
id: dismissTimer
|
||||
|
||||
contentItem: Label {
|
||||
color: palette.light
|
||||
width: Math.max(snackbar.Overlay.overlay? snackbar.Overlay.overlay.width/2 : 0, 400)
|
||||
text: snackbar.currentMessage
|
||||
font.bold: true
|
||||
}
|
||||
interval: 10000
|
||||
|
||||
background: Rectangle {
|
||||
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
|
||||
}
|
||||
onTriggered: snackbar.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -9,15 +9,15 @@ import QtQuick.Effects
|
|||
Item {
|
||||
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 var colors: ["#c0def5", "#87aade", "white"]
|
||||
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 pauseDuration: barCount * 150
|
||||
property bool running: true
|
||||
property int spacing: 0
|
||||
|
||||
height: 40
|
||||
width: barCount * (height * 0.375)
|
||||
|
@ -25,131 +25,116 @@ Item {
|
|||
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 {
|
||||
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 {
|
||||
anchors.fill: row
|
||||
shadowBlur: 14
|
||||
shadowEnabled: true
|
||||
shadowColor: spinner.foreground
|
||||
shadowEnabled: true
|
||||
source: row
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,136 +7,134 @@ import QtQuick.Particles 2.15
|
|||
|
||||
Item {
|
||||
id: effectRoot
|
||||
|
||||
readonly property int maxLifespan: Math.max(confettiEmitter.lifeSpan, rainfallEmitter.lifeSpan)
|
||||
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
|
||||
|
||||
function pulseConfetti()
|
||||
{
|
||||
confettiEmitter.pulse(effectRoot.height * 2)
|
||||
}
|
||||
|
||||
function pulseRainfall()
|
||||
{
|
||||
rainfallEmitter.pulse(effectRoot.height * 3.3)
|
||||
}
|
||||
|
||||
function removeParticles()
|
||||
{
|
||||
particleSystem.reset()
|
||||
}
|
||||
|
||||
ParticleSystem {
|
||||
id: particleSystem
|
||||
|
||||
Component.onCompleted: stop();
|
||||
paused: !effectRoot.shouldEffectsRun
|
||||
running: effectRoot.shouldEffectsRun
|
||||
}
|
||||
|
||||
Component.onCompleted: stop()
|
||||
}
|
||||
Emitter {
|
||||
id: confettiEmitter
|
||||
|
||||
group: "confetti"
|
||||
width: effectRoot.width * 3/4
|
||||
enabled: false
|
||||
anchors.horizontalCenter: effectRoot.horizontalCenter
|
||||
y: effectRoot.height
|
||||
emitRate: Math.min(400 * Math.sqrt(effectRoot.width * effectRoot.height) / 870, 1000)
|
||||
enabled: false
|
||||
group: "confetti"
|
||||
lifeSpan: 15000
|
||||
system: particleSystem
|
||||
maximumEmitted: 500
|
||||
velocityFromMovement: 8
|
||||
size: 16
|
||||
sizeVariation: 4
|
||||
system: particleSystem
|
||||
velocityFromMovement: 8
|
||||
width: effectRoot.width * 3 / 4
|
||||
y: effectRoot.height
|
||||
|
||||
velocity: PointDirection {
|
||||
x: 0
|
||||
y: -Math.min(450 * effectRoot.height / 700, 1000)
|
||||
xVariation: Math.min(4 * effectRoot.width / 7, 450)
|
||||
y: -Math.min(450 * effectRoot.height / 700, 1000)
|
||||
yVariation: 250
|
||||
}
|
||||
}
|
||||
|
||||
ImageParticle {
|
||||
system: particleSystem
|
||||
color: "white"
|
||||
colorVariation: 1
|
||||
entryEffect: ImageParticle.None
|
||||
groups: ["confetti"]
|
||||
source: "qrc:/confettiparticle.svg"
|
||||
rotationVelocity: 0
|
||||
rotationVelocityVariation: 360
|
||||
colorVariation: 1
|
||||
color: "white"
|
||||
entryEffect: ImageParticle.None
|
||||
source: "qrc:/confettiparticle.svg"
|
||||
system: particleSystem
|
||||
|
||||
xVector: PointDirection {
|
||||
x: 1
|
||||
y: 0
|
||||
xVariation: 0.2
|
||||
y: 0
|
||||
yVariation: 0.2
|
||||
}
|
||||
yVector: PointDirection {
|
||||
x: 0
|
||||
y: 0.5
|
||||
xVariation: 0.2
|
||||
y: 0.5
|
||||
yVariation: 0.2
|
||||
}
|
||||
}
|
||||
|
||||
Gravity {
|
||||
system: particleSystem
|
||||
groups: ["confetti"]
|
||||
anchors.fill: effectRoot
|
||||
magnitude: 350
|
||||
angle: 90
|
||||
groups: ["confetti"]
|
||||
magnitude: 350
|
||||
system: particleSystem
|
||||
}
|
||||
|
||||
Emitter {
|
||||
id: rainfallEmitter
|
||||
|
||||
group: "rain"
|
||||
width: effectRoot.width
|
||||
enabled: false
|
||||
anchors.horizontalCenter: effectRoot.horizontalCenter
|
||||
y: -60
|
||||
emitRate: effectRoot.width / 30
|
||||
enabled: false
|
||||
group: "rain"
|
||||
lifeSpan: 10000
|
||||
system: particleSystem
|
||||
width: effectRoot.width
|
||||
y: -60
|
||||
|
||||
velocity: PointDirection {
|
||||
x: 0
|
||||
y: 400
|
||||
xVariation: 0
|
||||
y: 400
|
||||
yVariation: 75
|
||||
}
|
||||
|
||||
// causes high CPU load, see: https://bugreports.qt.io/browse/QTBUG-117923
|
||||
//ItemParticle {
|
||||
// system: particleSystem
|
||||
// groups: ["rain"]
|
||||
// fade: false
|
||||
// visible: effectRoot.shouldEffectsRun
|
||||
// delegate: Rectangle {
|
||||
// width: 2
|
||||
// height: 30 + 30 * Math.random()
|
||||
// radius: 2
|
||||
// color: "#0099ff"
|
||||
// }
|
||||
//}
|
||||
// system: particleSystem
|
||||
// groups: ["rain"]
|
||||
// fade: false
|
||||
// visible: effectRoot.shouldEffectsRun
|
||||
// delegate: Rectangle {
|
||||
// width: 2
|
||||
// height: 30 + 30 * Math.random()
|
||||
// radius: 2
|
||||
// color: "#0099ff"
|
||||
// }
|
||||
//}
|
||||
|
||||
ImageParticle {
|
||||
system: particleSystem
|
||||
groups: ["rain"]
|
||||
source: "qrc:/confettiparticle.svg"
|
||||
rotationVelocity: 0
|
||||
rotationVelocityVariation: 0
|
||||
colorVariation: 0
|
||||
color: "#0099ff"
|
||||
entryEffect: ImageParticle.None
|
||||
xVector: PointDirection {
|
||||
x: 0.01
|
||||
y: 0
|
||||
}
|
||||
yVector: PointDirection {
|
||||
x: 0
|
||||
y: 5
|
||||
}
|
||||
ImageParticle {
|
||||
color: "#0099ff"
|
||||
colorVariation: 0
|
||||
entryEffect: ImageParticle.None
|
||||
groups: ["rain"]
|
||||
rotationVelocity: 0
|
||||
rotationVelocityVariation: 0
|
||||
source: "qrc:/confettiparticle.svg"
|
||||
system: particleSystem
|
||||
|
||||
xVector: PointDirection {
|
||||
x: 0.01
|
||||
y: 0
|
||||
}
|
||||
yVector: PointDirection {
|
||||
x: 0
|
||||
y: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,27 +5,24 @@
|
|||
import QtQuick
|
||||
|
||||
SequentialAnimation {
|
||||
property alias target: numberAnimation.target
|
||||
property alias glowDuration: numberAnimation.duration
|
||||
property int pauseDuration: 150
|
||||
property double offset: 0
|
||||
property int pauseDuration: 150
|
||||
property alias target: numberAnimation.target
|
||||
|
||||
loops: Animation.Infinite
|
||||
|
||||
PauseAnimation {
|
||||
duration: pauseDuration * offset
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: numberAnimation
|
||||
|
||||
property: "opacity"
|
||||
from: 0
|
||||
property: "opacity"
|
||||
to: 1
|
||||
}
|
||||
|
||||
PauseAnimation {
|
||||
duration: pauseDuration * (1 - offset)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,27 +14,22 @@ Rectangle {
|
|||
id: control
|
||||
|
||||
property alias desiredVolume: volumeSlider.desiredVolume
|
||||
property var duration
|
||||
property bool mediaLoaded: false
|
||||
property var mediaState
|
||||
property bool muted: false
|
||||
property bool playingVideo: false
|
||||
property var mediaState
|
||||
property bool mediaLoaded: false
|
||||
property var duration
|
||||
property var positionValue: 0
|
||||
property var position
|
||||
property var positionValue: 0
|
||||
property bool shouldShowControls: !playingVideo || playerMouseArea.shouldShowControls || volumeSlider.state == "shown"
|
||||
|
||||
signal playPauseActivated()
|
||||
signal loadActivated()
|
||||
|
||||
function showControls() {
|
||||
controlHideTimer.restart();
|
||||
}
|
||||
signal loadActivated
|
||||
signal playPauseActivated
|
||||
|
||||
function durationToString(duration) {
|
||||
function maybeZeroPrepend(time) {
|
||||
return (time < 10) ? "0" + time.toString() : time.toString();
|
||||
}
|
||||
|
||||
var totalSeconds = Math.floor(duration / 1000);
|
||||
var seconds = totalSeconds % 60;
|
||||
var minutes = (Math.floor(totalSeconds / 60)) % 60;
|
||||
|
@ -45,16 +40,25 @@ Rectangle {
|
|||
var hh = hours.toString();
|
||||
if (hours < 1)
|
||||
return mm + ":" + ss;
|
||||
|
||||
return hh + ":" + mm + ":" + ss;
|
||||
}
|
||||
function showControls() {
|
||||
controlHideTimer.restart();
|
||||
}
|
||||
|
||||
color: {
|
||||
var wc = palette.alternateBase;
|
||||
return Qt.rgba(wc.r, wc.g, wc.b, 0.5);
|
||||
}
|
||||
opacity: control.shouldShowControls ? 1 : 0
|
||||
height: controlLayout.implicitHeight
|
||||
opacity: control.shouldShowControls ? 1 : 0
|
||||
|
||||
// Fade controls in/out
|
||||
Behavior on opacity {
|
||||
OpacityAnimator {
|
||||
duration: 100
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: playerMouseArea
|
||||
|
@ -63,41 +67,40 @@ Rectangle {
|
|||
|
||||
onHoveredChanged: showControls()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: controlLayout
|
||||
|
||||
enabled: control.shouldShowControls
|
||||
spacing: 0
|
||||
anchors.bottom: control.bottom
|
||||
anchors.left: control.left
|
||||
anchors.right: control.right
|
||||
enabled: control.shouldShowControls
|
||||
spacing: 0
|
||||
|
||||
NhekoSlider {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Nheko.paddingSmall
|
||||
Layout.rightMargin: Nheko.paddingSmall
|
||||
alwaysShowSlider: false
|
||||
enabled: control.mediaLoaded
|
||||
value: control.positionValue
|
||||
onMoved: control.position = value
|
||||
from: 0
|
||||
to: control.duration
|
||||
alwaysShowSlider: false
|
||||
}
|
||||
value: control.positionValue
|
||||
|
||||
onMoved: control.position = value
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Nheko.paddingSmall
|
||||
spacing: Nheko.paddingSmall
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Cache/Play/pause button
|
||||
ImageButton {
|
||||
id: playbackStateImage
|
||||
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
buttonTextColor: palette.text
|
||||
Layout.preferredHeight: 24
|
||||
Layout.preferredWidth: 24
|
||||
buttonTextColor: palette.text
|
||||
image: {
|
||||
if (control.mediaLoaded) {
|
||||
if (control.mediaState == MediaPlayer.PlayingState)
|
||||
|
@ -108,38 +111,47 @@ Rectangle {
|
|||
return ":/icons/icons/ui/download.svg";
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: control.mediaLoaded ? control.playPauseActivated() : control.loadActivated()
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: volumeButton
|
||||
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
buttonTextColor: palette.text
|
||||
Layout.preferredHeight: 24
|
||||
Layout.preferredWidth: 24
|
||||
buttonTextColor: palette.text
|
||||
image: {
|
||||
if (control.muted || control.desiredVolume <= 0)
|
||||
return ":/icons/icons/ui/volume-off-indicator.svg";
|
||||
else
|
||||
return ":/icons/icons/ui/volume-up.svg";
|
||||
}
|
||||
|
||||
onClicked: control.muted = !control.muted
|
||||
}
|
||||
|
||||
NhekoSlider {
|
||||
id: volumeSlider
|
||||
|
||||
property real desiredVolume: volumeSlider.value
|
||||
|
||||
state: ""
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.preferredWidth: 0
|
||||
opacity: 0
|
||||
orientation: Qt.Horizontal
|
||||
state: ""
|
||||
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: [
|
||||
Transition {
|
||||
|
@ -150,20 +162,16 @@ Rectangle {
|
|||
PauseAnimation {
|
||||
duration: 50
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
properties: "opacity"
|
||||
easing.type: Easing.InQuad
|
||||
properties: "opacity"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
properties: "Layout.preferredWidth"
|
||||
duration: 150
|
||||
properties: "Layout.preferredWidth"
|
||||
}
|
||||
|
||||
},
|
||||
Transition {
|
||||
from: "shown"
|
||||
|
@ -173,54 +181,34 @@ Rectangle {
|
|||
PauseAnimation {
|
||||
duration: 100
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
duration: 100
|
||||
properties: "opacity"
|
||||
easing.type: Easing.InQuad
|
||||
properties: "opacity"
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
properties: "Layout.preferredWidth"
|
||||
duration: 150
|
||||
properties: "Layout.preferredWidth"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
states: State {
|
||||
name: "shown"
|
||||
when: Settings.mobileMode || volumeButton.hovered || volumeSlider.hovered || volumeSlider.pressed
|
||||
|
||||
PropertyChanges {
|
||||
volumeSlider.implicitWidth: 100
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
volumeSlider.opacity: 1
|
||||
}
|
||||
|
||||
onDesiredVolumeChanged: {
|
||||
control.muted = !(desiredVolume > 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: (!control.mediaLoaded ? "-- " : durationToString(control.positionValue)) + " / " + durationToString(control.duration)
|
||||
color: palette.text
|
||||
text: (!control.mediaLoaded ? "-- " : durationToString(control.positionValue)) + " / " + durationToString(control.duration)
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// For hiding controls on stationary cursor
|
||||
|
@ -230,13 +218,4 @@ Rectangle {
|
|||
interval: 1500 //ms
|
||||
repeat: false
|
||||
}
|
||||
|
||||
// Fade controls in/out
|
||||
Behavior on opacity {
|
||||
OpacityAnimator {
|
||||
duration: 100
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,51 +9,48 @@ import QtQuick.Layouts 1.2
|
|||
import im.nheko 1.0
|
||||
|
||||
Rectangle {
|
||||
visible: CallManager.isOnCall
|
||||
color: callInviteBar.color
|
||||
implicitHeight: visible ? rowLayout.height + 8 : 0
|
||||
visible: CallManager.isOnCall
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
onClicked: {
|
||||
if (CallManager.callType != Voip.VOICE)
|
||||
stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 8
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: 8
|
||||
|
||||
Avatar {
|
||||
implicitWidth: Nheko.avatarSize
|
||||
displayName: CallManager.callPartyDisplayName
|
||||
implicitHeight: Nheko.avatarSize
|
||||
implicitWidth: Nheko.avatarSize
|
||||
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
userid: CallManager.callParty
|
||||
displayName: CallManager.callPartyDisplayName
|
||||
|
||||
onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId)
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.leftMargin: 8
|
||||
color: "#000000"
|
||||
font.pointSize: fontMetrics.font.pointSize * 1.1
|
||||
text: CallManager.callPartyDisplayName
|
||||
color: "#000000"
|
||||
}
|
||||
|
||||
Image {
|
||||
id: callTypeIcon
|
||||
|
||||
Layout.leftMargin: 4
|
||||
Layout.preferredWidth: 24
|
||||
Layout.preferredHeight: 24
|
||||
Layout.preferredWidth: 24
|
||||
}
|
||||
|
||||
Item {
|
||||
states: [
|
||||
State {
|
||||
|
@ -63,7 +60,6 @@ Rectangle {
|
|||
PropertyChanges {
|
||||
callTypeIcon.source: "qrc:/icons/icons/ui/place-call.svg"
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "VIDEO"
|
||||
|
@ -72,7 +68,6 @@ Rectangle {
|
|||
PropertyChanges {
|
||||
callTypeIcon.source: "qrc:/icons/icons/ui/video.svg"
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "SCREEN"
|
||||
|
@ -81,18 +76,15 @@ Rectangle {
|
|||
PropertyChanges {
|
||||
callTypeIcon.source: "qrc:/icons/icons/ui/screen-share.svg"
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Label {
|
||||
id: callStateLabel
|
||||
|
||||
font.pointSize: fontMetrics.font.pointSize * 1.1
|
||||
color: "#000000"
|
||||
font.pointSize: fontMetrics.font.pointSize * 1.1
|
||||
}
|
||||
|
||||
Item {
|
||||
states: [
|
||||
State {
|
||||
|
@ -102,7 +94,6 @@ Rectangle {
|
|||
PropertyChanges {
|
||||
callStateLabel.text: qsTr("Calling...")
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "CONNECTING"
|
||||
|
@ -111,7 +102,6 @@ Rectangle {
|
|||
PropertyChanges {
|
||||
callStateLabel.text: qsTr("Connecting...")
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "ANSWERSENT"
|
||||
|
@ -120,7 +110,6 @@ Rectangle {
|
|||
PropertyChanges {
|
||||
callStateLabel.text: qsTr("Connecting...")
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "CONNECTED"
|
||||
|
@ -129,15 +118,12 @@ Rectangle {
|
|||
PropertyChanges {
|
||||
callStateLabel.text: "00:00"
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
callTimer.startTime: Math.floor((new Date()).getTime() / 1000)
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
stackLayout.currentIndex: CallManager.callType != Voip.VOICE ? 1 : 0
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "DISCONNECTED"
|
||||
|
@ -152,14 +138,12 @@ Rectangle {
|
|||
// stackLayout.currentIndex: 0
|
||||
//}
|
||||
PropertyChanges {
|
||||
target: stackLayout
|
||||
currentIndex: 0 // qmllint disable Quick.property-changes-parsed
|
||||
target: stackLayout
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: callTimer
|
||||
|
||||
|
@ -170,8 +154,9 @@ Rectangle {
|
|||
}
|
||||
|
||||
interval: 1000
|
||||
running: CallManager.callState == Voip.CONNECTED
|
||||
repeat: true
|
||||
running: CallManager.callState == Voip.CONNECTED
|
||||
|
||||
onTriggered: {
|
||||
var d = new Date();
|
||||
let seconds = Math.floor(d.getTime() / 1000 - startTime);
|
||||
|
@ -181,44 +166,40 @@ Rectangle {
|
|||
callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s);
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
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"
|
||||
font.pointSize: fontMetrics.font.pointSize * 1.1
|
||||
text: qsTr("You are screen sharing")
|
||||
visible: CallManager.callType == Voip.SCREEN && CallManager.callState == Voip.CONNECTED
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
visible: CallManager.haveLocalPiP
|
||||
Layout.preferredWidth: 24
|
||||
Layout.preferredHeight: 24
|
||||
buttonTextColor: "#000000"
|
||||
image: ":/icons/icons/ui/picture-in-picture.svg"
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
Layout.preferredWidth: 24
|
||||
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()
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 16
|
||||
Layout.preferredWidth: 24
|
||||
Layout.preferredHeight: 24
|
||||
buttonTextColor: "#000000"
|
||||
image: CallManager.isMicMuted ? ":/icons/icons/ui/microphone-unmute.svg" : ":/icons/icons/ui/microphone-mute.svg"
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
Layout.preferredWidth: 24
|
||||
Layout.rightMargin: 16
|
||||
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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,79 +9,70 @@ import im.nheko 1.0
|
|||
|
||||
Popup {
|
||||
modal: true
|
||||
|
||||
background: Rectangle {
|
||||
border.color: palette.windowText
|
||||
color: palette.window
|
||||
}
|
||||
|
||||
// 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
|
||||
Component.onCompleted: {
|
||||
if (anchors)
|
||||
anchors.centerIn = parent;
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 16
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 8
|
||||
Layout.topMargin: 8
|
||||
spacing: 8
|
||||
|
||||
RowLayout {
|
||||
Image {
|
||||
Layout.preferredWidth: 22
|
||||
Layout.preferredHeight: 22
|
||||
Layout.preferredWidth: 22
|
||||
source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + palette.windowText
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: micCombo
|
||||
|
||||
Layout.fillWidth: true
|
||||
model: CallManager.mics
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0
|
||||
|
||||
Image {
|
||||
Layout.preferredWidth: 22
|
||||
Layout.preferredHeight: 22
|
||||
Layout.preferredWidth: 22
|
||||
source: "image://colorimage/:/icons/icons/ui/video-call.svg?" + palette.windowText
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: cameraCombo
|
||||
|
||||
Layout.fillWidth: true
|
||||
model: CallManager.cameras
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DialogButtonBox {
|
||||
Layout.leftMargin: 128
|
||||
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
|
||||
|
||||
onAccepted: {
|
||||
Settings.microphone = micCombo.currentText;
|
||||
if (cameraCombo.visible)
|
||||
Settings.camera = cameraCombo.currentText;
|
||||
|
||||
close();
|
||||
}
|
||||
onRejected: {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: palette.window
|
||||
border.color: palette.windowText
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,51 +12,50 @@ Popup {
|
|||
id: callInv
|
||||
|
||||
closePolicy: Popup.NoAutoClose
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
|
||||
background: Rectangle {
|
||||
border.color: palette.windowText
|
||||
color: palette.window
|
||||
}
|
||||
|
||||
Component {
|
||||
id: deviceError
|
||||
|
||||
DeviceError {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onNewInviteState() {
|
||||
if (!CallManager.haveCallInvite)
|
||||
close();
|
||||
|
||||
}
|
||||
|
||||
target: CallManager
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.topMargin: callInv.parent.height / 25
|
||||
Layout.fillWidth: true
|
||||
text: CallManager.callPartyDisplayName
|
||||
font.pointSize: fontMetrics.font.pointSize * 2
|
||||
Layout.topMargin: callInv.parent.height / 25
|
||||
color: palette.windowText
|
||||
font.pointSize: fontMetrics.font.pointSize * 2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: CallManager.callPartyDisplayName
|
||||
}
|
||||
|
||||
Avatar {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.preferredHeight: callInv.height / 5
|
||||
Layout.preferredWidth: callInv.height / 5
|
||||
displayName: CallManager.callPartyDisplayName
|
||||
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
userid: CallManager.callParty
|
||||
displayName: CallManager.callPartyDisplayName
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
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"
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.preferredWidth: callInv.height / 10
|
||||
Layout.preferredHeight: callInv.height / 10
|
||||
Layout.preferredWidth: callInv.height / 10
|
||||
source: "image://colorimage/" + image + "?" + palette.windowText
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call")
|
||||
font.pointSize: fontMetrics.font.pointSize * 2
|
||||
color: palette.windowText
|
||||
font.pointSize: fontMetrics.font.pointSize * 2
|
||||
text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: deviceCombos
|
||||
|
||||
|
@ -91,41 +87,34 @@ Popup {
|
|||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
Image {
|
||||
Layout.preferredWidth: deviceCombos.imageSize
|
||||
Layout.preferredHeight: deviceCombos.imageSize
|
||||
Layout.preferredWidth: deviceCombos.imageSize
|
||||
source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + palette.windowText
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: micCombo
|
||||
|
||||
Layout.fillWidth: true
|
||||
model: CallManager.mics
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
visible: CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0
|
||||
|
||||
Image {
|
||||
Layout.preferredWidth: deviceCombos.imageSize
|
||||
Layout.preferredHeight: deviceCombos.imageSize
|
||||
Layout.preferredWidth: deviceCombos.imageSize
|
||||
source: "image://colorimage/:/icons/icons/ui/video.svg?" + palette.windowText
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: cameraCombo
|
||||
|
||||
Layout.fillWidth: true
|
||||
model: CallManager.cameras
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: buttonLayout
|
||||
|
||||
|
@ -134,9 +123,9 @@ Popup {
|
|||
function validateMic() {
|
||||
if (CallManager.mics.length == 0) {
|
||||
var dialog = deviceError.createObject(timelineRoot, {
|
||||
"errorString": qsTr("No microphone found."),
|
||||
"image": ":/icons/icons/ui/place-call.svg"
|
||||
});
|
||||
"errorString": qsTr("No microphone found."),
|
||||
"image": ":/icons/icons/ui/place-call.svg"
|
||||
});
|
||||
dialog.open();
|
||||
timelineRoot.destroyOnClose(dialog);
|
||||
return false;
|
||||
|
@ -148,60 +137,48 @@ Popup {
|
|||
spacing: callInv.height / 6
|
||||
|
||||
RoundButton {
|
||||
implicitWidth: buttonLayout.buttonSize
|
||||
implicitHeight: buttonLayout.buttonSize
|
||||
onClicked: {
|
||||
CallManager.rejectInvite();
|
||||
close();
|
||||
}
|
||||
implicitWidth: buttonLayout.buttonSize
|
||||
|
||||
background: Rectangle {
|
||||
radius: buttonLayout.buttonSize / 2
|
||||
color: "#ff0000"
|
||||
radius: buttonLayout.buttonSize / 2
|
||||
}
|
||||
|
||||
contentItem: Image {
|
||||
source: "image://colorimage/:/icons/icons/ui/end-call.svg?#ffffff"
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
CallManager.rejectInvite();
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
RoundButton {
|
||||
id: acceptButton
|
||||
|
||||
property string image: CallManager.callType == Voip.VIDEO ? ":/icons/icons/ui/video.svg" : ":/icons/icons/ui/place-call.svg"
|
||||
|
||||
implicitWidth: 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: {
|
||||
if (buttonLayout.validateMic()) {
|
||||
Settings.microphone = micCombo.currentText;
|
||||
if (cameraCombo.visible)
|
||||
Settings.camera = cameraCombo.currentText;
|
||||
|
||||
CallManager.acceptInvite();
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,127 +9,118 @@ import QtQuick.Layouts
|
|||
import im.nheko
|
||||
|
||||
Rectangle {
|
||||
visible: CallManager.haveCallInvite && !Settings.mobileMode
|
||||
color: "#2ECC71"
|
||||
implicitHeight: visible ? rowLayout.height + 8 : 0
|
||||
visible: CallManager.haveCallInvite && !Settings.mobileMode
|
||||
|
||||
Component {
|
||||
id: devicesDialog
|
||||
|
||||
CallDevices {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: deviceError
|
||||
|
||||
DeviceError {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 8
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: 8
|
||||
|
||||
Avatar {
|
||||
implicitWidth: Nheko.avatarSize
|
||||
displayName: CallManager.callPartyDisplayName
|
||||
implicitHeight: Nheko.avatarSize
|
||||
implicitWidth: Nheko.avatarSize
|
||||
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
userid: CallManager.callParty
|
||||
displayName: CallManager.callPartyDisplayName
|
||||
|
||||
onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId)
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.leftMargin: 8
|
||||
color: "#000000"
|
||||
font.pointSize: fontMetrics.font.pointSize * 1.1
|
||||
text: CallManager.callPartyDisplayName
|
||||
color: "#000000"
|
||||
}
|
||||
|
||||
Image {
|
||||
Layout.leftMargin: 4
|
||||
Layout.preferredWidth: 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"
|
||||
}
|
||||
|
||||
Label {
|
||||
color: "#000000"
|
||||
font.pointSize: fontMetrics.font.pointSize * 1.1
|
||||
text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call")
|
||||
color: "#000000"
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
Layout.rightMargin: 16
|
||||
Layout.preferredWidth: 20
|
||||
Layout.preferredHeight: 20
|
||||
buttonTextColor: "#000000"
|
||||
image: ":/icons/icons/ui/settings.svg"
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
Layout.preferredWidth: 20
|
||||
Layout.rightMargin: 16
|
||||
ToolTip.text: qsTr("Devices")
|
||||
ToolTip.visible: hovered
|
||||
buttonTextColor: "#000000"
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/settings.svg"
|
||||
|
||||
onClicked: {
|
||||
var dialog = devicesDialog.createObject(timelineRoot);
|
||||
dialog.open();
|
||||
timelineRoot.destroyOnClose(dialog);
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.rightMargin: 4
|
||||
icon.source: CallManager.callType == Voip.VIDEO ? "qrc:/icons/icons/ui/video.svg" : "qrc:/icons/icons/ui/place-call.svg"
|
||||
text: qsTr("Accept")
|
||||
|
||||
onClicked: {
|
||||
if (CallManager.mics.length == 0) {
|
||||
var dialog = deviceError.createObject(timelineRoot, {
|
||||
"errorString": qsTr("No microphone found."),
|
||||
"image": ":/icons/icons/ui/place-call.svg"
|
||||
});
|
||||
"errorString": qsTr("No microphone found."),
|
||||
"image": ":/icons/icons/ui/place-call.svg"
|
||||
});
|
||||
dialog.open();
|
||||
timelineRoot.destroyOnClose(dialog);
|
||||
return ;
|
||||
timelineRoot.destroyOnClose(dialog);
|
||||
return;
|
||||
} else if (!CallManager.mics.includes(Settings.microphone)) {
|
||||
var dialog = deviceError.createObject(timelineRoot, {
|
||||
"errorString": qsTr("Unknown microphone: %1").arg(Settings.microphone),
|
||||
"image": ":/icons/icons/ui/place-call.svg"
|
||||
});
|
||||
"errorString": qsTr("Unknown microphone: %1").arg(Settings.microphone),
|
||||
"image": ":/icons/icons/ui/place-call.svg"
|
||||
});
|
||||
dialog.open();
|
||||
timelineRoot.destroyOnClose(dialog);
|
||||
return ;
|
||||
timelineRoot.destroyOnClose(dialog);
|
||||
return;
|
||||
}
|
||||
if (CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0 && !CallManager.cameras.includes(Settings.camera)) {
|
||||
var dialog = deviceError.createObject(timelineRoot, {
|
||||
"errorString": qsTr("Unknown camera: %1").arg(Settings.camera),
|
||||
"image": ":/icons/icons/ui/video.svg"
|
||||
});
|
||||
"errorString": qsTr("Unknown camera: %1").arg(Settings.camera),
|
||||
"image": ":/icons/icons/ui/video.svg"
|
||||
});
|
||||
dialog.open();
|
||||
timelineRoot.destroyOnClose(dialog);
|
||||
return ;
|
||||
timelineRoot.destroyOnClose(dialog);
|
||||
return;
|
||||
}
|
||||
CallManager.acceptInvite();
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.rightMargin: 16
|
||||
icon.source: "qrc:/icons/icons/ui/end-call.svg"
|
||||
text: qsTr("Decline")
|
||||
|
||||
onClicked: {
|
||||
CallManager.rejectInvite();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,35 +8,33 @@ import QtQuick.Layouts 1.2
|
|||
|
||||
Popup {
|
||||
id: r
|
||||
|
||||
property string errorString
|
||||
property var image
|
||||
|
||||
modal: true
|
||||
|
||||
background: Rectangle {
|
||||
border.color: palette.windowText
|
||||
color: palette.window
|
||||
}
|
||||
|
||||
// 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
|
||||
Component.onCompleted: {
|
||||
if (anchors)
|
||||
anchors.centerIn = parent;
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Image {
|
||||
Layout.preferredWidth: 16
|
||||
Layout.preferredHeight: 16
|
||||
Layout.preferredWidth: 16
|
||||
source: "image://colorimage/" + r.image + "?" + palette.windowText
|
||||
}
|
||||
|
||||
Label {
|
||||
text: r.errorString
|
||||
color: palette.windowText
|
||||
text: r.errorString
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: palette.window
|
||||
border.color: palette.windowText
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,12 +10,17 @@ import im.nheko 1.0
|
|||
|
||||
Popup {
|
||||
modal: true
|
||||
|
||||
background: Rectangle {
|
||||
border.color: palette.windowText
|
||||
color: palette.window
|
||||
}
|
||||
|
||||
// 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
|
||||
Component.onCompleted: {
|
||||
if (anchors)
|
||||
anchors.centerIn = parent;
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
|
@ -23,38 +28,33 @@ Popup {
|
|||
|
||||
DeviceError {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: columnLayout
|
||||
|
||||
spacing: 16
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 8
|
||||
Layout.topMargin: 8
|
||||
|
||||
Label {
|
||||
text: qsTr("Place a call to %1?").arg(room.roomName)
|
||||
color: palette.windowText
|
||||
text: qsTr("Place a call to %1?").arg(room.roomName)
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: buttonLayout
|
||||
|
||||
function validateMic() {
|
||||
if (CallManager.mics.length == 0) {
|
||||
var dialog = deviceError.createObject(timelineRoot, {
|
||||
"errorString": qsTr("No microphone found."),
|
||||
"image": ":/icons/icons/ui/place-call.svg"
|
||||
});
|
||||
"errorString": qsTr("No microphone found."),
|
||||
"image": ":/icons/icons/ui/place-call.svg"
|
||||
});
|
||||
dialog.open();
|
||||
timelineRoot.destroyOnClose(dialog);
|
||||
return false;
|
||||
|
@ -66,18 +66,19 @@ Popup {
|
|||
Layout.rightMargin: 8
|
||||
|
||||
Avatar {
|
||||
Layout.rightMargin: cameraCombo.visible ? 16 : 64
|
||||
Layout.preferredWidth: 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
|
||||
roomid: room.roomId
|
||||
url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
|
||||
onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId)
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Voice")
|
||||
icon.source: "qrc:/icons/icons/ui/place-call.svg"
|
||||
text: qsTr("Voice")
|
||||
|
||||
onClicked: {
|
||||
if (buttonLayout.validateMic()) {
|
||||
Settings.microphone = micCombo.currentText;
|
||||
|
@ -86,11 +87,11 @@ Popup {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
visible: CallManager.cameras.length > 0
|
||||
text: qsTr("Video")
|
||||
icon.source: "qrc:/icons/icons/ui/video.svg"
|
||||
text: qsTr("Video")
|
||||
visible: CallManager.cameras.length > 0
|
||||
|
||||
onClicked: {
|
||||
if (buttonLayout.validateMic()) {
|
||||
Settings.microphone = micCombo.currentText;
|
||||
|
@ -100,15 +101,14 @@ Popup {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Screen")
|
||||
icon.source: "qrc:/icons/icons/ui/screen-share.svg"
|
||||
text: qsTr("Screen")
|
||||
|
||||
onClicked: {
|
||||
if (buttonLayout.validateMic()) {
|
||||
Settings.microphone = micCombo.currentText;
|
||||
Settings.camera = cameraCombo.currentText;
|
||||
|
||||
var dialog = screenShareDialog.createObject(timelineRoot);
|
||||
dialog.open();
|
||||
timelineRoot.destroyOnClose(dialog);
|
||||
|
@ -116,67 +116,52 @@ Popup {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Cancel")
|
||||
|
||||
onClicked: {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 8
|
||||
|
||||
RowLayout {
|
||||
Layout.bottomMargin: cameraCombo.visible ? 0 : 8
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 8
|
||||
Layout.bottomMargin: cameraCombo.visible ? 0 : 8
|
||||
|
||||
Image {
|
||||
Layout.preferredWidth: 22
|
||||
Layout.preferredHeight: 22
|
||||
Layout.preferredWidth: 22
|
||||
source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + palette.windowText
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: micCombo
|
||||
|
||||
Layout.fillWidth: true
|
||||
model: CallManager.mics
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: CallManager.cameras.length > 0
|
||||
Layout.bottomMargin: 8
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 8
|
||||
Layout.bottomMargin: 8
|
||||
visible: CallManager.cameras.length > 0
|
||||
|
||||
Image {
|
||||
Layout.preferredWidth: 22
|
||||
Layout.preferredHeight: 22
|
||||
Layout.preferredWidth: 22
|
||||
source: "image://colorimage/:/icons/icons/ui/video.svg?" + palette.windowText
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: cameraCombo
|
||||
|
||||
Layout.fillWidth: true
|
||||
model: CallManager.cameras
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: palette.window
|
||||
border.color: palette.windowText
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,9 +9,13 @@ import QtQuick.Layouts
|
|||
import im.nheko
|
||||
|
||||
Popup {
|
||||
anchors.centerIn: parent
|
||||
modal: true
|
||||
|
||||
anchors.centerIn: parent;
|
||||
background: Rectangle {
|
||||
border.color: palette.windowText
|
||||
color: palette.window
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
frameRateCombo.currentIndex = frameRateCombo.find(Settings.screenShareFrameRate);
|
||||
|
@ -22,176 +26,151 @@ Popup {
|
|||
|
||||
ColumnLayout {
|
||||
Label {
|
||||
Layout.topMargin: 16
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.bottomMargin: 16
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 8
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.topMargin: 16
|
||||
color: palette.windowText
|
||||
text: qsTr("Share desktop with %1?").arg(room.roomName)
|
||||
color: palette.windowText
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.bottomMargin: 8
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 8
|
||||
Layout.bottomMargin: 8
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: qsTr("Method:")
|
||||
color: palette.windowText
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: screenshareType
|
||||
|
||||
Layout.fillWidth: true
|
||||
model: CallManager.screenShareTypeList()
|
||||
onCurrentIndexChanged: CallManager.setScreenShareType(currentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 8
|
||||
Layout.bottomMargin: 8
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: qsTr("Window:")
|
||||
color: palette.windowText
|
||||
text: qsTr("Method:")
|
||||
}
|
||||
ComboBox {
|
||||
id: screenshareType
|
||||
|
||||
Layout.fillWidth: true
|
||||
model: CallManager.screenShareTypeList()
|
||||
|
||||
onCurrentIndexChanged: CallManager.setScreenShareType(currentIndex)
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.bottomMargin: 8
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 8
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
color: palette.windowText
|
||||
text: qsTr("Window:")
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
visible: CallManager.screenShareType == Voip.X11
|
||||
id: windowCombo
|
||||
|
||||
Layout.fillWidth: true
|
||||
model: CallManager.windowList()
|
||||
visible: CallManager.screenShareType == Voip.X11
|
||||
}
|
||||
|
||||
Button {
|
||||
visible: CallManager.screenShareType == Voip.XDP
|
||||
highlighted: !CallManager.screenShareReady
|
||||
text: qsTr("Request screencast")
|
||||
visible: CallManager.screenShareType == Voip.XDP
|
||||
|
||||
onClicked: {
|
||||
Settings.screenShareHideCursor = hideCursorCheckBox.checked;
|
||||
CallManager.setupScreenShareXDP();
|
||||
Settings.screenShareHideCursor = hideCursorCheckBox.checked;
|
||||
CallManager.setupScreenShareXDP();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.bottomMargin: 8
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 8
|
||||
Layout.bottomMargin: 8
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: qsTr("Frame rate:")
|
||||
color: palette.windowText
|
||||
text: qsTr("Frame rate:")
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: frameRateCombo
|
||||
|
||||
Layout.fillWidth: true
|
||||
model: ["25", "20", "15", "10", "5", "2", "1"]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
Layout.margins: 8
|
||||
columns: 2
|
||||
rowSpacing: 10
|
||||
Layout.margins: 8
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("Include your camera picture-in-picture")
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
id: pipCheckBox
|
||||
|
||||
enabled: CallManager.cameras.length > 0
|
||||
checked: CallManager.cameras.length > 0 && Settings.screenSharePiP
|
||||
Layout.alignment: Qt.AlignRight
|
||||
checked: CallManager.cameras.length > 0 && Settings.screenSharePiP
|
||||
enabled: CallManager.cameras.length > 0
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("Request remote camera")
|
||||
ToolTip.text: qsTr("View your callee's camera like a regular video call")
|
||||
ToolTip.visible: hovered
|
||||
text: qsTr("Request remote camera")
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
id: remoteVideoCheckBox
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
checked: Settings.screenShareRemoteVideo
|
||||
ToolTip.text: qsTr("View your callee's camera like a regular video call")
|
||||
ToolTip.visible: hovered
|
||||
checked: Settings.screenShareRemoteVideo
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("Hide mouse cursor")
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
id: hideCursorCheckBox
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
checked: Settings.screenShareHideCursor
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.margins: 8
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Button {
|
||||
visible: CallManager.screenShareReady
|
||||
text: qsTr("Share")
|
||||
icon.source: "qrc:/icons/icons/ui/screen-share.svg"
|
||||
text: qsTr("Share")
|
||||
visible: CallManager.screenShareReady
|
||||
|
||||
onClicked: {
|
||||
Settings.screenShareFrameRate = frameRateCombo.currentText;
|
||||
Settings.screenSharePiP = pipCheckBox.checked;
|
||||
Settings.screenShareRemoteVideo = remoteVideoCheckBox.checked;
|
||||
Settings.screenShareHideCursor = hideCursorCheckBox.checked;
|
||||
|
||||
CallManager.sendInvite(room.roomId, Voip.SCREEN, windowCombo.currentIndex);
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
visible: CallManager.screenShareReady
|
||||
text: qsTr("Preview")
|
||||
visible: CallManager.screenShareReady
|
||||
|
||||
onClicked: {
|
||||
CallManager.previewWindow(windowCombo.currentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Cancel")
|
||||
|
||||
onClicked: {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: palette.window
|
||||
border.color: palette.windowText
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue