summary refs log tree commit diff
path: root/resources/qml
diff options
context:
space:
mode:
authorDeepBlueV7.X <nicolas.werner@hotmail.de>2020-11-25 22:01:19 +0100
committerGitHub <noreply@github.com>2020-11-25 22:01:19 +0100
commitc44513614f00512326877f544a55680d18fef41c (patch)
tree6e89ae6e98928beb00a35e815b8fafbd7cd49a61 /resources/qml
parentFix ActiveCallBar (diff)
parentMerge pull request #335 from Nheko-Reborn/qml-text-input (diff)
downloadnheko-c44513614f00512326877f544a55680d18fef41c.tar.xz
Merge branch 'master' into fix-call-bar
Diffstat (limited to 'resources/qml')
-rw-r--r--resources/qml/ActiveCallBar.qml10
-rw-r--r--resources/qml/Completer.qml178
-rw-r--r--resources/qml/ImageButton.qml1
-rw-r--r--resources/qml/MatrixText.qml1
-rw-r--r--resources/qml/MessageInput.qml168
-rw-r--r--resources/qml/MessageView.qml2
-rw-r--r--resources/qml/NhekoBusyIndicator.qml64
-rw-r--r--resources/qml/TimelineView.qml16
-rw-r--r--resources/qml/VideoCall.qml3
-rw-r--r--resources/qml/emoji/EmojiButton.qml4
-rw-r--r--resources/qml/emoji/EmojiPicker.qml12
11 files changed, 437 insertions, 22 deletions
diff --git a/resources/qml/ActiveCallBar.qml b/resources/qml/ActiveCallBar.qml

index f68c531e..9bbac632 100644 --- a/resources/qml/ActiveCallBar.qml +++ b/resources/qml/ActiveCallBar.qml
@@ -12,8 +12,11 @@ Rectangle { MouseArea { anchors.fill: parent - onClicked: if (TimelineManager.onVideoCall) - stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1; + onClicked: { + if (TimelineManager.onVideoCall) + stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1; + + } } RowLayout { @@ -39,8 +42,7 @@ Rectangle { Image { Layout.preferredWidth: 24 Layout.preferredHeight: 24 - source: TimelineManager.onVideoCall ? - "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png" + source: TimelineManager.onVideoCall ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png" } Label { diff --git a/resources/qml/Completer.qml b/resources/qml/Completer.qml new file mode 100644
index 00000000..9703da64 --- /dev/null +++ b/resources/qml/Completer.qml
@@ -0,0 +1,178 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 +import im.nheko 1.0 + +Popup { + id: popup + + property int currentIndex: -1 + property string completerName + property var completer + property bool bottomToTop: true + + signal completionClicked(string completion) + + function up() { + if (bottomToTop) + down_(); + else + up_(); + } + + function down() { + if (bottomToTop) + up_(); + else + down_(); + } + + function up_() { + currentIndex = currentIndex - 1; + if (currentIndex == -2) + currentIndex = listView.count - 1; + + } + + function down_() { + currentIndex = currentIndex + 1; + if (currentIndex >= listView.count) + currentIndex = -1; + + } + + function currentCompletion() { + if (currentIndex > -1 && currentIndex < listView.count) + return completer.completionAt(currentIndex); + else + return null; + } + + onCompleterNameChanged: { + if (completerName) { + completer = TimelineManager.timeline.input.completerFor(completerName); + completer.setSearchString(""); + } else { + completer = undefined; + } + } + padding: 0 + onAboutToShow: currentIndex = -1 + height: listView.contentHeight + + Connections { + onTimelineChanged: completer = null + target: TimelineManager + } + + ListView { + id: listView + + anchors.fill: parent + implicitWidth: contentItem.childrenRect.width + model: completer + verticalLayoutDirection: popup.bottomToTop ? ListView.BottomToTop : ListView.TopToBottom + + delegate: Rectangle { + color: model.index == popup.currentIndex ? colors.highlight : colors.base + height: chooser.childrenRect.height + 4 + implicitWidth: chooser.childrenRect.width + 4 + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: popup.currentIndex = model.index + onClicked: popup.completionClicked(completer.completionAt(model.index)) + } + + DelegateChooser { + id: chooser + + roleValue: popup.completerName + anchors.centerIn: parent + + DelegateChoice { + roleValue: "user" + + RowLayout { + id: del + + anchors.centerIn: parent + + Avatar { + height: 24 + width: 24 + displayName: model.displayName + url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + } + + Label { + text: model.displayName + color: model.index == popup.currentIndex ? colors.highlightedText : colors.text + } + + Label { + text: "(" + model.userid + ")" + color: model.index == popup.currentIndex ? colors.highlightedText : colors.buttonText + } + + } + + } + + DelegateChoice { + roleValue: "emoji" + + RowLayout { + id: del + + anchors.centerIn: parent + + Label { + text: model.unicode + color: model.index == popup.currentIndex ? colors.highlightedText : colors.text + font: Settings.emojiFont + } + + Label { + text: model.shortName + color: model.index == popup.currentIndex ? colors.highlightedText : colors.text + } + + } + + } + + } + + } + + } + + enter: Transition { + NumberAnimation { + property: "opacity" + from: 0 + to: 1 + duration: 100 + } + + } + + exit: Transition { + NumberAnimation { + property: "opacity" + from: 1 + to: 0 + duration: 100 + } + + } + + background: Rectangle { + color: colors.base + implicitHeight: popup.contentHeight + implicitWidth: popup.contentWidth + } + +} diff --git a/resources/qml/ImageButton.qml b/resources/qml/ImageButton.qml
index 49ddf671..4ebda680 100644 --- a/resources/qml/ImageButton.qml +++ b/resources/qml/ImageButton.qml
@@ -8,6 +8,7 @@ AbstractButton { property color highlightColor: colors.highlight property color buttonTextColor: colors.buttonText + focusPolicy: Qt.NoFocus width: 16 height: 16 diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml
index a5781c73..bb3b4296 100644 --- a/resources/qml/MatrixText.qml +++ b/resources/qml/MatrixText.qml
@@ -5,6 +5,7 @@ import im.nheko 1.0 TextEdit { textFormat: TextEdit.RichText readOnly: true + focus: false wrapMode: Text.Wrap selectByMouse: !Settings.mobileMode color: colors.text diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml
index 71da9cae..ae95cbb6 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml
@@ -2,6 +2,7 @@ import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 import QtQuick.Window 2.2 +import im.nheko 1.0 Rectangle { color: colors.window @@ -16,14 +17,18 @@ Rectangle { spacing: 16 ImageButton { + visible: TimelineManager.callsSupported Layout.alignment: Qt.AlignBottom hoverEnabled: true width: 22 height: 22 - image: ":/icons/icons/ui/place-call.png" + image: TimelineManager.isOnCall ? ":/icons/icons/ui/end-call.png" : ":/icons/icons/ui/place-call.png" + ToolTip.visible: hovered + ToolTip.text: TimelineManager.isOnCall ? qsTr("Hang up") : qsTr("Place a call") Layout.topMargin: 8 Layout.bottomMargin: 8 Layout.leftMargin: 16 + onClicked: TimelineManager.timeline.input.callButton() } ImageButton { @@ -34,6 +39,23 @@ Rectangle { image: ":/icons/icons/ui/paper-clip-outline.png" Layout.topMargin: 8 Layout.bottomMargin: 8 + Layout.leftMargin: TimelineManager.callsSupported ? 0 : 16 + onClicked: TimelineManager.timeline.input.openFileSelection() + ToolTip.visible: hovered + ToolTip.text: qsTr("Send a file") + + Rectangle { + anchors.fill: parent + color: colors.window + visible: TimelineManager.timeline.input.uploading + + NhekoBusyIndicator { + anchors.fill: parent + running: parent.visible + } + + } + } ScrollView { @@ -44,16 +66,145 @@ Rectangle { Layout.fillWidth: true TextArea { + id: textArea + + property int completerTriggeredAt: -1 + + function insertCompletion(completion) { + textArea.remove(completerTriggeredAt, cursorPosition); + textArea.insert(cursorPosition, completion); + } + + function openCompleter(pos, type) { + completerTriggeredAt = pos; + popup.completerName = type; + popup.open(); + popup.completer.setSearchString(textArea.getText(completerTriggeredAt, cursorPosition)); + } + placeholderText: qsTr("Write a message...") placeholderTextColor: colors.buttonText color: colors.text wrapMode: TextEdit.Wrap + focus: true + onTextChanged: TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text) + onCursorPositionChanged: { + TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text); + if (cursorPosition <= completerTriggeredAt) { + completerTriggeredAt = -1; + popup.close(); + } + if (popup.opened) + popup.completer.setSearchString(textArea.getText(completerTriggeredAt, cursorPosition)); + + } + onSelectionStartChanged: TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text) + onSelectionEndChanged: TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text) + // Ensure that we get escape key press events first. + Keys.onShortcutOverride: event.accepted = (completerTriggeredAt != -1 && (event.key === Qt.Key_Escape || event.key === Qt.Key_Tab || event.key === Qt.Key_Enter)) + Keys.onPressed: { + if (event.matches(StandardKey.Paste)) { + TimelineManager.timeline.input.paste(false); + event.accepted = true; + } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_U) { + textArea.clear(); + } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_P) { + textArea.text = TimelineManager.timeline.input.previousText(); + } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_N) { + textArea.text = TimelineManager.timeline.input.nextText(); + } else if (event.key == Qt.Key_At) { + textArea.openCompleter(cursorPosition, "user"); + popup.open(); + } else if (event.key == Qt.Key_Colon) { + textArea.openCompleter(cursorPosition, "emoji"); + popup.open(); + } else if (event.key == Qt.Key_Escape && popup.opened) { + completerTriggeredAt = -1; + popup.completerName = ""; + event.accepted = true; + popup.close(); + } else if (event.matches(StandardKey.InsertParagraphSeparator)) { + if (popup.opened) { + var currentCompletion = popup.currentCompletion(); + popup.completerName = ""; + popup.close(); + if (currentCompletion) { + textArea.insertCompletion(currentCompletion); + event.accepted = true; + return ; + } + } + TimelineManager.timeline.input.send(); + textArea.clear(); + event.accepted = true; + } else if (event.key == Qt.Key_Tab) { + event.accepted = true; + if (popup.opened) { + popup.up(); + } else { + var pos = cursorPosition - 1; + while (pos > -1) { + var t = textArea.getText(pos, pos + 1); + console.log('"' + t + '"'); + if (t == '@' || t == ' ' || t == '\t') { + textArea.openCompleter(pos, "user"); + return ; + } else if (t == ':') { + textArea.openCompleter(pos, "emoji"); + return ; + } + pos = pos - 1; + } + // At start of input + textArea.openCompleter(0, "user"); + } + } else if (event.key == Qt.Key_Up && popup.opened) { + event.accepted = true; + popup.up(); + } else if (event.key == Qt.Key_Down && popup.opened) { + event.accepted = true; + popup.down(); + } + } + + Connections { + onTimelineChanged: { + textArea.clear(); + textArea.append(TimelineManager.timeline.input.text()); + textArea.completerTriggeredAt = -1; + popup.completerName = ""; + } + target: TimelineManager + } + + Connections { + onCompletionClicked: textArea.insertCompletion(completion) + target: popup + } + + Completer { + id: popup + + x: textArea.positionToRectangle(textArea.completerTriggeredAt).x + y: textArea.positionToRectangle(textArea.completerTriggeredAt).y - height + } + + Connections { + onInsertText: textArea.insert(textArea.cursorPosition, text) + target: TimelineManager.timeline.input + } MouseArea { // workaround for wrong cursor shape on some platforms anchors.fill: parent - acceptedButtons: Qt.NoButton + acceptedButtons: Qt.MiddleButton cursorShape: Qt.IBeamCursor + onClicked: TimelineManager.timeline.input.paste(true) + } + + NhekoDropArea { + anchors.fill: parent + roomid: TimelineManager.timeline.roomId() } background: Rectangle { @@ -65,6 +216,8 @@ Rectangle { } ImageButton { + id: emojiButton + Layout.alignment: Qt.AlignRight | Qt.AlignBottom hoverEnabled: true width: 22 @@ -72,6 +225,11 @@ Rectangle { image: ":/icons/icons/ui/smile.png" Layout.topMargin: 8 Layout.bottomMargin: 8 + ToolTip.visible: hovered + ToolTip.text: qsTr("Emoji") + onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, function(emoji) { + textArea.insert(textArea.cursorPosition, emoji); + }) } ImageButton { @@ -83,6 +241,12 @@ Rectangle { Layout.topMargin: 8 Layout.bottomMargin: 8 Layout.rightMargin: 16 + ToolTip.visible: hovered + ToolTip.text: qsTr("Send") + onClicked: { + TimelineManager.timeline.input.send(); + textArea.clear(); + } } } diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml
index bc0acfa4..679c1f50 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml
@@ -28,6 +28,7 @@ ListView { ScrollHelper { flickable: parent anchors.fill: parent + enabled: !Settings.mobileMode } Shortcut { @@ -181,7 +182,6 @@ ListView { Connections { target: chat - onMovementEnded: { if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height) chat.model.currentIndex = index; diff --git a/resources/qml/NhekoBusyIndicator.qml b/resources/qml/NhekoBusyIndicator.qml new file mode 100644
index 00000000..89a40dd5 --- /dev/null +++ b/resources/qml/NhekoBusyIndicator.qml
@@ -0,0 +1,64 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 + +BusyIndicator { + id: control + + contentItem: Item { + implicitWidth: 64 + implicitHeight: 64 + + Item { + id: item + + height: Math.min(parent.height, parent.width) + width: height + opacity: control.running ? 1 : 0 + + RotationAnimator { + target: item + running: control.visible && control.running + from: 0 + to: 360 + loops: Animation.Infinite + duration: 2000 + } + + Repeater { + id: repeater + + model: 6 + + Rectangle { + implicitWidth: radius * 2 + implicitHeight: radius * 2 + radius: item.height / 6 + color: colors.text + opacity: (index + 2) / (repeater.count + 2) + transform: [ + Translate { + y: -Math.min(item.width, item.height) * 0.5 + item.height / 6 + }, + Rotation { + angle: index / repeater.count * 360 + origin.x: item.height / 2 + origin.y: item.height / 2 + } + ] + } + + } + + Behavior on opacity { + OpacityAnimator { + duration: 250 + } + + } + + } + + } + +} diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index 95143b18..4234bb6d 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml
@@ -72,7 +72,9 @@ Page { MenuItem { text: qsTr("React") - onClicked: emojiPopup.show(messageContextMenu.parent, messageContextMenu.eventId) + onClicked: emojiPopup.show(messageContextMenu.parent, function(emoji) { + TimelineManager.queueReactionMessage(messageContextMenu.eventId, emoji); + }) } MenuItem { @@ -95,6 +97,7 @@ Page { } MenuItem { + // TODO(Nico): Fix this still being iterated over, when using keyboard to select options visible: messageContextMenu.isEncrypted height: visible ? implicitHeight : 0 text: qsTr("View decrypted raw message") @@ -129,7 +132,6 @@ Page { Connections { target: TimelineManager - onNewDeviceVerificationRequest: { var dialog = deviceVerificationDialog.createObject(timelineRoot, { "flow": flow @@ -140,7 +142,6 @@ Page { Connections { target: TimelineManager.timeline - onOpenProfile: { var userProfile = userProfileComponent.createObject(timelineRoot, { "profile": profile @@ -192,13 +193,15 @@ Page { StackLayout { id: stackLayout + currentIndex: 0 Connections { - target: TimelineManager function onActiveTimelineChanged() { stackLayout.currentIndex = 0; } + + target: TimelineManager } MessageView { @@ -210,6 +213,7 @@ Page { source: TimelineManager.onVideoCall ? "VideoCall.qml" : "" onLoaded: TimelineManager.setVideoCallItem() } + } TypingIndicator { @@ -234,8 +238,8 @@ Page { ReplyPopup { } - //MessageInput { - //} + MessageInput { + } } diff --git a/resources/qml/VideoCall.qml b/resources/qml/VideoCall.qml
index 69fc1a2b..14408b6e 100644 --- a/resources/qml/VideoCall.qml +++ b/resources/qml/VideoCall.qml
@@ -1,7 +1,6 @@ import QtQuick 2.9 - import org.freedesktop.gstreamer.GLVideoItem 1.0 GstGLVideoItem { - objectName: "videoCallItem" + objectName: "videoCallItem" } diff --git a/resources/qml/emoji/EmojiButton.qml b/resources/qml/emoji/EmojiButton.qml
index 1fcfc0c5..928d6226 100644 --- a/resources/qml/emoji/EmojiButton.qml +++ b/resources/qml/emoji/EmojiButton.qml
@@ -12,5 +12,7 @@ ImageButton { property string event_id image: ":/icons/icons/ui/smile.png" - onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.show(emojiButton, event_id) + onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.show(emojiButton, function(emoji) { + TimelineManager.queueReactionMessage(event_id, emoji); + }) } diff --git a/resources/qml/emoji/EmojiPicker.qml b/resources/qml/emoji/EmojiPicker.qml
index afe16350..e0e1ceaf 100644 --- a/resources/qml/emoji/EmojiPicker.qml +++ b/resources/qml/emoji/EmojiPicker.qml
@@ -9,7 +9,7 @@ import im.nheko.EmojiModel 1.0 Popup { id: emojiPopup - property string event_id + property var callback property var colors property alias model: gridView.model property var textArea @@ -18,14 +18,14 @@ Popup { property real highlightSat: colors.highlight.hslSaturation property real highlightLight: colors.highlight.hslLightness - function show(showAt, event_id) { - console.debug("Showing emojiPicker for " + event_id); + function show(showAt, callback) { + console.debug("Showing emojiPicker"); if (showAt) { parent = showAt; x = Math.round((showAt.width - width) / 2); y = showAt.height; } - emojiPopup.event_id = event_id; + emojiPopup.callback = callback; open(); } @@ -70,9 +70,9 @@ Popup { ToolTip.visible: hovered // TODO: maybe add favorites at some point? onClicked: { - console.debug("Picked " + model.unicode + "in response to " + emojiPopup.event_id); + console.debug("Picked " + model.unicode); emojiPopup.close(); - TimelineManager.queueReactionMessage(emojiPopup.event_id, model.unicode); + callback(model.unicode); } // give the emoji a little oomf