Add typing notification.

This commit is contained in:
Black Hat 2018-12-23 11:24:01 +08:00
parent e5fbdc15ff
commit 316d1429fa
4 changed files with 333 additions and 278 deletions

View File

@ -69,307 +69,368 @@ Item {
onClicked: roomDrawer.open() onClicked: roomDrawer.open()
} }
AutoListView { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumWidth: 960
Layout.fillHeight: true Layout.fillHeight: true
Layout.maximumWidth: 960
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.alignment: Qt.AlignHCenter Layout.bottomMargin: 16
id: messageListView spacing: 16
spacing: 4 AutoListView {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignHCenter
displayMarginBeginning: 100 id: messageListView
displayMarginEnd: 100
verticalLayoutDirection: ListView.BottomToTop
highlightMoveDuration: 500
boundsBehavior: Flickable.DragOverBounds spacing: 4
model: SortFilterProxyModel { displayMarginBeginning: 100
id: sortedMessageEventModel displayMarginEnd: 100
verticalLayoutDirection: ListView.BottomToTop
highlightMoveDuration: 500
sourceModel: messageEventModel boundsBehavior: Flickable.DragOverBounds
filters: ExpressionFilter { model: SortFilterProxyModel {
expression: marks !== 0x10 && eventType !== "other" id: sortedMessageEventModel
}
onModelReset: { sourceModel: messageEventModel
if (currentRoom) {
var lastScrollPosition = sortedMessageEventModel.mapFromSource(currentRoom.savedTopVisibleIndex()) filters: ExpressionFilter {
messageListView.currentIndex = lastScrollPosition expression: marks !== 0x10 && eventType !== "other"
if (messageListView.contentY < messageListView.originY + 10 || currentRoom.timelineSize < 20)
currentRoom.getPreviousContent(50)
} }
}
}
property int largestVisibleIndex: count > 0 ? indexAt(contentX, contentY + height - 1) : -1 onModelReset: {
if (currentRoom) {
onContentYChanged: { var lastScrollPosition = sortedMessageEventModel.mapFromSource(currentRoom.savedTopVisibleIndex())
if(currentRoom && contentY - 5000 < originY) messageListView.currentIndex = lastScrollPosition
currentRoom.getPreviousContent(20); if (messageListView.contentY < messageListView.originY + 10 || currentRoom.timelineSize < 20)
} currentRoom.getPreviousContent(50)
displaced: Transition {
NumberAnimation {
property: "y"; duration: 200
easing.type: Easing.OutQuad
}
}
delegate: DelegateChooser {
role: "eventType"
DelegateChoice {
roleValue: "state"
delegate: ColumnLayout {
width: messageListView.width
spacing: 4
SectionDelegate {
Layout.alignment: Qt.AlignHCenter
Layout.margins: 16
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
}
StateDelegate {
Layout.maximumWidth: parent.width
Layout.alignment: Qt.AlignHCenter
} }
} }
} }
DelegateChoice { property int largestVisibleIndex: count > 0 ? indexAt(contentX, contentY + height - 1) : -1
roleValue: "emote"
delegate: ColumnLayout {
width: messageListView.width
spacing: 4
SectionDelegate { onContentYChanged: {
Layout.alignment: Qt.AlignHCenter if(currentRoom && contentY - 5000 < originY)
Layout.margins: 16 currentRoom.getPreviousContent(20);
}
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000 displaced: Transition {
} NumberAnimation {
property: "y"; duration: 200
StateDelegate { easing.type: Easing.OutQuad
Layout.maximumWidth: parent.width
Layout.alignment: Qt.AlignHCenter
}
} }
} }
DelegateChoice { delegate: DelegateChooser {
roleValue: "message" role: "eventType"
delegate: ColumnLayout {
width: messageListView.width
spacing: 4
SectionDelegate { DelegateChoice {
Layout.alignment: Qt.AlignHCenter roleValue: "state"
Layout.margins: 16 delegate: ColumnLayout {
width: messageListView.width
spacing: 4
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000 SectionDelegate {
} Layout.alignment: Qt.AlignHCenter
Layout.margins: 16
MessageDelegate { visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
}
}
}
DelegateChoice {
roleValue: "notice"
delegate: ColumnLayout {
width: messageListView.width
spacing: 4
SectionDelegate {
Layout.alignment: Qt.AlignHCenter
Layout.margins: 16
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
}
MessageDelegate {
}
}
}
DelegateChoice {
roleValue: "image"
delegate: ColumnLayout {
width: messageListView.width
spacing: 4
SectionDelegate {
Layout.alignment: Qt.AlignHCenter
Layout.margins: 16
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
}
ImageDelegate {
Layout.maximumWidth: parent.width
}
}
}
DelegateChoice {
roleValue: "file"
delegate: ColumnLayout {
width: messageListView.width
spacing: 4
SectionDelegate {
Layout.alignment: Qt.AlignHCenter
Layout.margins: 16
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
}
FileDelegate {
Layout.maximumWidth: parent.width
}
}
}
}
RoundButton {
width: 64
height: 64
anchors.right: parent.right
anchors.top: parent.top
id: goBottomFab
visible: currentRoom && currentRoom.hasUnreadMessages
contentItem: MaterialIcon {
anchors.fill: parent
icon: "\ue316"
color: "white"
}
Material.background: Material.accent
onClicked: goToEvent(currentRoom.readMarkerEventId)
}
RoundButton {
width: 64
height: 64
anchors.right: parent.right
anchors.bottom: parent.bottom
id: goTopFab
visible: !messageListView.atYEnd
contentItem: MaterialIcon {
anchors.fill: parent
icon: "\ue313"
color: "white"
}
Material.background: Material.accent
onClicked: messageListView.positionViewAtBeginning()
}
Popup {
property string sourceText
anchors.centerIn: parent
width: 480
id: sourceDialog
parent: ApplicationWindow.overlay
padding: 16
closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside
contentItem: ScrollView {
clip: true
TextArea {
readOnly: true
selectByMouse: true
text: sourceDialog.sourceText
}
}
}
Popup {
property alias listModel: readMarkerListView.model
x: (window.width - width) / 2
y: (window.height - height) / 2
width: 320
id: readMarkerDialog
parent: ApplicationWindow.overlay
modal: true
padding: 16
closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside
contentItem: AutoListView {
implicitHeight: Math.min(window.height - 64,
readMarkerListView.contentHeight)
id: readMarkerListView
clip: true
boundsBehavior: Flickable.DragOverBounds
delegate: ItemDelegate {
width: parent.width
height: 48
RowLayout {
anchors.fill: parent
anchors.margins: 8
spacing: 12
Avatar {
Layout.preferredWidth: height
Layout.fillHeight: true
source: modelData.avatar
hint: modelData.displayName
} }
Label { StateDelegate {
Layout.fillWidth: true Layout.maximumWidth: parent.width
Layout.alignment: Qt.AlignHCenter
text: modelData.displayName
} }
} }
} }
ScrollBar.vertical: ScrollBar {} DelegateChoice {
roleValue: "emote"
delegate: ColumnLayout {
width: messageListView.width
spacing: 4
SectionDelegate {
Layout.alignment: Qt.AlignHCenter
Layout.margins: 16
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
}
StateDelegate {
Layout.maximumWidth: parent.width
Layout.alignment: Qt.AlignHCenter
}
}
}
DelegateChoice {
roleValue: "message"
delegate: ColumnLayout {
width: messageListView.width
spacing: 4
SectionDelegate {
Layout.alignment: Qt.AlignHCenter
Layout.margins: 16
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
}
MessageDelegate {
}
}
}
DelegateChoice {
roleValue: "notice"
delegate: ColumnLayout {
width: messageListView.width
spacing: 4
SectionDelegate {
Layout.alignment: Qt.AlignHCenter
Layout.margins: 16
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
}
MessageDelegate {
}
}
}
DelegateChoice {
roleValue: "image"
delegate: ColumnLayout {
width: messageListView.width
spacing: 4
SectionDelegate {
Layout.alignment: Qt.AlignHCenter
Layout.margins: 16
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
}
ImageDelegate {
Layout.maximumWidth: parent.width
}
}
}
DelegateChoice {
roleValue: "file"
delegate: ColumnLayout {
width: messageListView.width
spacing: 4
SectionDelegate {
Layout.alignment: Qt.AlignHCenter
Layout.margins: 16
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
}
FileDelegate {
Layout.maximumWidth: parent.width
}
}
}
}
RoundButton {
width: 64
height: 64
anchors.right: parent.right
anchors.top: parent.top
id: goBottomFab
visible: currentRoom && currentRoom.hasUnreadMessages
contentItem: MaterialIcon {
anchors.fill: parent
icon: "\ue316"
color: "white"
}
Material.background: Material.accent
onClicked: goToEvent(currentRoom.readMarkerEventId)
}
RoundButton {
width: 64
height: 64
anchors.right: parent.right
anchors.bottom: parent.bottom
id: goTopFab
visible: !messageListView.atYEnd
contentItem: MaterialIcon {
anchors.fill: parent
icon: "\ue313"
color: "white"
}
Material.background: Material.accent
onClicked: messageListView.positionViewAtBeginning()
}
Popup {
property string sourceText
anchors.centerIn: parent
width: 480
id: sourceDialog
parent: ApplicationWindow.overlay
padding: 16
closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside
contentItem: ScrollView {
clip: true
TextArea {
readOnly: true
selectByMouse: true
text: sourceDialog.sourceText
}
}
}
Popup {
property alias listModel: readMarkerListView.model
x: (window.width - width) / 2
y: (window.height - height) / 2
width: 320
id: readMarkerDialog
parent: ApplicationWindow.overlay
modal: true
padding: 16
closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside
contentItem: AutoListView {
implicitHeight: Math.min(window.height - 64,
readMarkerListView.contentHeight)
id: readMarkerListView
clip: true
boundsBehavior: Flickable.DragOverBounds
delegate: ItemDelegate {
width: parent.width
height: 48
RowLayout {
anchors.fill: parent
anchors.margins: 8
spacing: 12
Avatar {
Layout.preferredWidth: height
Layout.fillHeight: true
source: modelData.avatar
hint: modelData.displayName
}
Label {
Layout.fillWidth: true
text: modelData.displayName
}
}
}
ScrollBar.vertical: ScrollBar {}
}
} }
} }
}
RoomPanelInput { Control {
Layout.fillWidth: true Layout.maximumWidth: parent.width * 0.8
Layout.margins: 16
Layout.maximumWidth: 960
Layout.alignment: Qt.AlignHCenter
id: roomPanelInput visible: currentRoom && currentRoom.hasUsersTyping
padding: 8
contentItem: RowLayout {
spacing: 8
Repeater {
model: currentRoom && currentRoom.hasUsersTyping ? currentRoom.usersTyping : null
delegate: Avatar {
Layout.preferredWidth: 24
Layout.preferredHeight: 24
source: modelData.avatarUrl
hint: modelData.displayName
}
}
Rectangle {
Layout.preferredWidth: 6
Layout.preferredHeight: 6
Layout.alignment: Qt.AlignCenter
color: MPalette.accent
radius: height / 2
}
Rectangle {
Layout.preferredWidth: 6
Layout.preferredHeight: 6
Layout.alignment: Qt.AlignCenter
color: MPalette.accent
radius: height / 2
}
Rectangle {
Layout.preferredWidth: 6
Layout.preferredHeight: 6
Layout.alignment: Qt.AlignCenter
color: MPalette.accent
radius: height / 2
}
}
background: Rectangle {
color: MPalette.banner
radius: height / 2
}
}
RoomPanelInput {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
id: roomPanelInput
}
} }
} }

View File

@ -220,8 +220,7 @@ Control {
text: currentRoom != null ? currentRoom.cachedInput : "" text: currentRoom != null ? currentRoom.cachedInput : ""
background: Item { background: Item {}
}
Rectangle { Rectangle {
width: currentRoom && currentRoom.hasFileUploading ? parent.width * currentRoom.fileUploadingProgress / 100 : 0 width: currentRoom && currentRoom.hasFileUploading ? parent.width * currentRoom.fileUploadingProgress / 100 : 0
@ -251,9 +250,6 @@ Control {
onTriggered: currentRoom.sendTypingNotification(true) onTriggered: currentRoom.sendTypingNotification(true)
} }
ToolTip.visible: currentRoom && currentRoom.hasUsersTyping
ToolTip.text: currentRoom ? currentRoom.usersTyping : ""
Keys.onReturnPressed: { Keys.onReturnPressed: {
if (event.modifiers & Qt.ShiftModifier) { if (event.modifiers & Qt.ShiftModifier) {
insert(cursorPosition, "\n") insert(cursorPosition, "\n")

View File

@ -118,16 +118,14 @@ bool SpectralRoom::hasUsersTyping() {
return count != 0; return count != 0;
} }
QString SpectralRoom::getUsersTyping() { QVariantList SpectralRoom::getUsersTyping() {
QString usersTypingStr;
QList<User*> users = usersTyping(); QList<User*> users = usersTyping();
users.removeOne(localUser()); users.removeOne(localUser());
QVariantList out;
for (User* user : users) { for (User* user : users) {
usersTypingStr += user->displayname() + " "; out.append(QVariant::fromValue(user));
} }
usersTypingStr += users.count() < 2 ? "is" : "are"; return out;
usersTypingStr += " typing.";
return usersTypingStr;
} }
void SpectralRoom::sendTypingNotification(bool isTyping) { void SpectralRoom::sendTypingNotification(bool isTyping) {

View File

@ -13,7 +13,7 @@ using namespace QMatrixClient;
class SpectralRoom : public Room { class SpectralRoom : public Room {
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool hasUsersTyping READ hasUsersTyping NOTIFY typingChanged) Q_PROPERTY(bool hasUsersTyping READ hasUsersTyping NOTIFY typingChanged)
Q_PROPERTY(QString usersTyping READ getUsersTyping NOTIFY typingChanged) Q_PROPERTY(QVariantList usersTyping READ getUsersTyping NOTIFY typingChanged)
Q_PROPERTY(QString cachedInput READ cachedInput WRITE setCachedInput NOTIFY Q_PROPERTY(QString cachedInput READ cachedInput WRITE setCachedInput NOTIFY
cachedInputChanged) cachedInputChanged)
Q_PROPERTY(bool hasFileUploading READ hasFileUploading NOTIFY Q_PROPERTY(bool hasFileUploading READ hasFileUploading NOTIFY
@ -43,7 +43,7 @@ class SpectralRoom : public Room {
} }
bool hasUsersTyping(); bool hasUsersTyping();
QString getUsersTyping(); QVariantList getUsersTyping();
QString lastEvent(); QString lastEvent();
bool isEventHighlighted(const QMatrixClient::RoomEvent* e) const; bool isEventHighlighted(const QMatrixClient::RoomEvent* e) const;