Enable qmlformat in linting

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

View file

@ -15,6 +15,14 @@ do
clang-format -i "$f"
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

View file

@ -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

View file

@ -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

View file

@ -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();

View file

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

View file

@ -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();

View file

@ -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);

View file

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

View file

@ -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

View file

@ -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

View file

@ -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 {
}
]
}

View file

@ -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,8 +69,6 @@ TimelineEvent {
userName: wrapper.userName
userPowerlevel: wrapper.userPowerlevel
}
visible: status == Loader.Ready
z: 4
},
Rectangle {
anchors.fill: gridContainer
@ -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.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

View file

@ -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

View file

@ -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 {
}
}
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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
}

View file

@ -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;
});
}
}

View file

@ -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
}
}
}
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}
}

View file

@ -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
}
}

View file

@ -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
}
}
}

View file

@ -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);
}
}
}

View file

@ -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)
}
}

View file

@ -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
}
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}
}

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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
}
}
}

View file

@ -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
}
}
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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)
}
}
]
}
}

View file

@ -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()
}
}
}

View file

@ -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
}
}
}

View file

@ -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()
}
}
}

View file

@ -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()
}
}
}

View file

@ -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()
}
}
}

View file

@ -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()
}
}
}

View file

@ -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
}
}
}

View file

@ -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();
}
}

View file

@ -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
}
}
}

View file

@ -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
}
}
}

View file

@ -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
}
}
}

View file

@ -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
}
}
}

View file

@ -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();
}
}

View file

@ -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.")
}
}

View file

@ -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();
}
}

View file

@ -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
}
}
}
}

View file

@ -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,9 +154,9 @@ Window {
}
ImageButton {
height: 48
width: 48
hoverEnabled: true
image: ":/icons/icons/ui/dismiss.svg"
width: 48
//ToolTip.visible: hovered
//ToolTip.delay: Nheko.tooltipDelay
@ -176,5 +165,4 @@ Window {
onClicked: imageOverlay.close()
}
}
}

View file

@ -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()
}
}

View file

@ -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()
}
}
}

View file

@ -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()
}
}
}

View file

@ -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()
}
}
}

View file

@ -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();
}
}
}
}

View file

@ -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);
}
}

View file

@ -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

View file

@ -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
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
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: model.isType ? qsTr("Remove event type") : qsTr("Add event type")
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();
}
}

View file

@ -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
}
}
}
}
}

View file

@ -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()
}
}

View file

@ -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")
}
}

View file

@ -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()
}
}

View file

@ -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();

View file

@ -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
}
}
}

View file

@ -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()
}
}

View file

@ -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()
}
}

View file

@ -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
}
}
}
}

View file

@ -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)
}
}
}
}

View file

@ -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()
}
}

View file

@ -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()
}
}

View file

@ -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()
}
}

View file

@ -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 {

View file

@ -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
}
}

View file

@ -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;
}
}
}

View file

@ -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()
}
}

View file

@ -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)
}
}
}

View file

@ -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
}
}
}
}

View file

@ -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)
}
}

View file

@ -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
}
}
}

View file

@ -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()
}
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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();
}
}
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}