Qml message input mockup

pull/313/head
Nicolas Werner 4 years ago
parent da085db6d6
commit 3a0e55e97b
  1. 2
      resources/qml/Avatar.qml
  2. 2
      resources/qml/Reactions.qml
  3. 2
      resources/qml/TimelineRow.qml
  4. 519
      resources/qml/TimelineView.qml
  5. 2
      resources/qml/delegates/FileMessage.qml
  6. 2
      resources/qml/delegates/Pill.qml
  7. 2
      resources/qml/delegates/PlayableMediaMessage.qml
  8. 4
      resources/qml/emoji/EmojiPicker.qml
  9. 20
      src/UserSettingsPage.cpp

@ -13,7 +13,7 @@ Rectangle {
width: 48 width: 48
height: 48 height: 48
radius: Settings.avatarCircles ? height / 2 : 3 radius: Settings.avatarCircles ? height / 2 : 3
color: colors.base color: colors.alternateBase
Label { Label {
anchors.fill: parent anchors.fill: parent

@ -83,7 +83,7 @@ Flow {
implicitWidth: reaction.implicitWidth implicitWidth: reaction.implicitWidth
implicitHeight: reaction.implicitHeight implicitHeight: reaction.implicitHeight
border.color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text border.color: (reaction.hovered || modelData.selfReactedEvent !== '') ? colors.highlight : colors.text
color: modelData.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.2) : colors.base color: modelData.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.2) : colors.window
border.width: 1 border.width: 1
radius: reaction.height / 2 radius: reaction.height / 2
} }

@ -48,7 +48,7 @@ Item {
Reply { Reply {
visible: model.replyTo visible: model.replyTo
modelData: chat.model.getDump(model.replyTo, model.id) modelData: chat.model.getDump(model.replyTo, model.id)
userColor: TimelineManager.userColor(modelData.userId, colors.window) userColor: TimelineManager.userColor(modelData.userId, colors.base)
} }
// actual message content // actual message content

@ -161,6 +161,7 @@ Page {
ColumnLayout { ColumnLayout {
visible: TimelineManager.timeline != null visible: TimelineManager.timeline != null
anchors.fill: parent anchors.fill: parent
spacing: 0
Rectangle { Rectangle {
id: topBar id: topBar
@ -168,7 +169,7 @@ Page {
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: topLayout.height + 16 implicitHeight: topLayout.height + 16
z: 3 z: 3
color: colors.base color: colors.window
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@ -283,143 +284,156 @@ Page {
} }
ListView { Rectangle {
id: chat Layout.fillWidth: true
height: 1
property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2) z: 3
color: colors.mid
}
cacheBuffer: 400 Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
model: TimelineManager.timeline color: colors.base
boundsBehavior: Flickable.StopAtBounds
pixelAligned: true
spacing: 4
verticalLayoutDirection: ListView.BottomToTop
onCountChanged: {
if (atYEnd)
model.currentIndex = 0;
} // Mark last event as read, since we are at the bottom
ScrollHelper {
flickable: parent
anchors.fill: parent
}
Shortcut { ListView {
sequence: StandardKey.MoveToPreviousPage id: chat
onActivated: {
chat.contentY = chat.contentY - chat.height / 2;
chat.returnToBounds();
}
}
Shortcut { property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2)
sequence: StandardKey.MoveToNextPage
onActivated: { cacheBuffer: 400
chat.contentY = chat.contentY + chat.height / 2; anchors.fill: parent
chat.returnToBounds(); model: TimelineManager.timeline
boundsBehavior: Flickable.StopAtBounds
pixelAligned: true
spacing: 4
verticalLayoutDirection: ListView.BottomToTop
onCountChanged: {
if (atYEnd)
model.currentIndex = 0;
} // Mark last event as read, since we are at the bottom
ScrollHelper {
flickable: parent
anchors.fill: parent
} }
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.MoveToPreviousPage
onActivated: chat.model.reply = undefined onActivated: {
} chat.contentY = chat.contentY - chat.height / 2;
chat.returnToBounds();
}
}
Shortcut { Shortcut {
sequence: "Alt+Up" sequence: StandardKey.MoveToNextPage
onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0) onActivated: {
} chat.contentY = chat.contentY + chat.height / 2;
chat.returnToBounds();
}
}
Shortcut { Shortcut {
sequence: "Alt+Down" sequence: StandardKey.Cancel
onActivated: { onActivated: chat.model.reply = undefined
var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1;
chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : undefined;
} }
}
Component { Shortcut {
id: userProfileComponent sequence: "Alt+Up"
onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0)
}
UserProfile { Shortcut {
sequence: "Alt+Down"
onActivated: {
var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1;
chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : undefined;
}
} }
} Component {
id: userProfileComponent
section { UserProfile {
property: "section" }
}
Component { }
id: sectionHeader
Column { section {
property var modelData property: "section"
property string section }
property string nextSection
topPadding: 4 Component {
bottomPadding: 4 id: sectionHeader
spacing: 8
visible: !!modelData
width: parent.width
height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
Label { Column {
id: dateBubble property var modelData
property string section
property string nextSection
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined topPadding: 4
visible: section.includes(" ") bottomPadding: 4
text: chat.model.formatDateSeparator(modelData.timestamp) spacing: 8
color: colors.text visible: !!modelData
height: fontMetrics.height * 1.4 width: parent.width
width: contentWidth * 1.2 height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter Label {
id: dateBubble
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
visible: section.includes(" ")
text: chat.model.formatDateSeparator(modelData.timestamp)
color: colors.text
height: fontMetrics.height * 1.4
width: contentWidth * 1.2
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
background: Rectangle {
radius: parent.height / 2
color: colors.window
}
background: Rectangle {
radius: parent.height / 2
color: colors.base
} }
} Row {
height: userName.height
spacing: 8
Row { Avatar {
height: userName.height width: avatarSize
spacing: 8 height: avatarSize
url: chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/")
displayName: modelData.userName
userid: modelData.userId
MouseArea {
anchors.fill: parent
onClicked: chat.model.openUserProfile(modelData.userId)
cursorShape: Qt.PointingHandCursor
propagateComposedEvents: true
}
Avatar {
width: avatarSize
height: avatarSize
url: chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/")
displayName: modelData.userName
userid: modelData.userId
MouseArea {
anchors.fill: parent
onClicked: chat.model.openUserProfile(modelData.userId)
cursorShape: Qt.PointingHandCursor
propagateComposedEvents: true
} }
} Label {
id: userName
text: TimelineManager.escapeEmoji(modelData.userName)
color: TimelineManager.userColor(modelData.userId, colors.window)
textFormat: Text.RichText
MouseArea {
anchors.fill: parent
Layout.alignment: Qt.AlignHCenter
onClicked: chat.model.openUserProfile(modelData.userId)
cursorShape: Qt.PointingHandCursor
propagateComposedEvents: true
}
Label {
id: userName
text: TimelineManager.escapeEmoji(modelData.userName)
color: TimelineManager.userColor(modelData.userId, colors.window)
textFormat: Text.RichText
MouseArea {
anchors.fill: parent
Layout.alignment: Qt.AlignHCenter
onClicked: chat.model.openUserProfile(modelData.userId)
cursorShape: Qt.PointingHandCursor
propagateComposedEvents: true
} }
} }
@ -428,140 +442,130 @@ Page {
} }
} ScrollBar.vertical: ScrollBar {
id: scrollbar
ScrollBar.vertical: ScrollBar { }
id: scrollbar
}
delegate: Item { delegate: Item {
id: wrapper id: wrapper
// This would normally be previousSection, but our model's order is inverted. // This would normally be previousSection, but our model's order is inverted.
property bool sectionBoundary: (ListView.nextSection != "" && ListView.nextSection !== ListView.section) || model.index === chat.count - 1 property bool sectionBoundary: (ListView.nextSection != "" && ListView.nextSection !== ListView.section) || model.index === chat.count - 1
property Item section property Item section
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
width: chat.delegateMaxWidth width: chat.delegateMaxWidth
height: section ? section.height + timelinerow.height : timelinerow.height height: section ? section.height + timelinerow.height : timelinerow.height
onSectionBoundaryChanged: { onSectionBoundaryChanged: {
if (sectionBoundary) { if (sectionBoundary) {
var properties = { var properties = {
"modelData": model.dump, "modelData": model.dump,
"section": ListView.section, "section": ListView.section,
"nextSection": ListView.nextSection "nextSection": ListView.nextSection
}; };
section = sectionHeader.createObject(wrapper, properties); section = sectionHeader.createObject(wrapper, properties);
} else { } else {
section.destroy(); section.destroy();
section = null; section = null;
}
} }
}
TimelineRow { TimelineRow {
id: timelinerow id: timelinerow
y: section ? section.y + section.height : 0 y: section ? section.y + section.height : 0
} }
Connections { Connections {
function onMovementEnded() { function onMovementEnded() {
if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height) if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
chat.model.currentIndex = index; chat.model.currentIndex = index;
}
target: chat
} }
target: chat
} }
} footer: BusyIndicator {
anchors.horizontalCenter: parent.horizontalCenter
running: chat.model && chat.model.paginationInProgress
height: 50
width: 50
z: 3
}
footer: BusyIndicator {
anchors.horizontalCenter: parent.horizontalCenter
running: chat.model && chat.model.paginationInProgress
height: 50
width: 50
z: 3
} }
} }
Item { Item {
id: chatFooter implicitHeight: Math.max(fontMetrics.height * 1.2, typingDisplay.height)
implicitHeight: Math.max(fontMetrics.height * 1.2, footerContent.height)
Layout.fillWidth: true Layout.fillWidth: true
z: 3
Column { Rectangle {
id: footerContent id: typingRect
color: colors.base
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom height: (chat.model && chat.model.typingUsers.length > 0) ? typingDisplay.height : 0
z: 3
Rectangle { Label {
id: typingRect id: typingDisplay
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 10
anchors.right: parent.right anchors.right: parent.right
color: (chat.model && chat.model.typingUsers.length > 0) ? colors.window : "transparent" anchors.rightMargin: 10
height: typingDisplay.height anchors.bottom: parent.bottom
color: colors.text
Label { text: chat.model ? chat.model.formatTypingUsers(chat.model.typingUsers, colors.base) : ""
id: typingDisplay textFormat: Text.RichText
}
anchors.left: parent.left }
anchors.leftMargin: 10
anchors.right: parent.right
anchors.rightMargin: 10
color: colors.text
text: chat.model ? chat.model.formatTypingUsers(chat.model.typingUsers, colors.window) : ""
textFormat: Text.RichText
}
} }
Rectangle { Rectangle {
id: replyPopup id: replyPopup
anchors.left: parent.left Layout.fillWidth: true
anchors.right: parent.right visible: chat.model && chat.model.reply
visible: chat.model && chat.model.reply // Height of child, plus margins, plus border
// Height of child, plus margins, plus border implicitHeight: replyPreview.height + 10
height: replyPreview.height + 10 color: colors.window
color: colors.base z: 3
Reply {
id: replyPreview
anchors.left: parent.left
anchors.leftMargin: 10
anchors.right: closeReplyButton.left
anchors.rightMargin: 20
anchors.bottom: parent.bottom
modelData: chat.model ? chat.model.getDump(chat.model.reply, chat.model.id) : {
}
userColor: TimelineManager.userColor(modelData.userId, colors.window)
}
ImageButton { Reply {
id: closeReplyButton id: replyPreview
anchors.right: parent.right
anchors.rightMargin: 15
anchors.top: replyPreview.top
hoverEnabled: true
width: 16
height: 16
image: ":/icons/icons/ui/remove-symbol.png"
ToolTip.visible: closeReplyButton.hovered
ToolTip.text: qsTr("Close")
onClicked: chat.model.reply = undefined
}
anchors.left: parent.left
anchors.leftMargin: 10
anchors.right: closeReplyButton.left
anchors.rightMargin: 20
anchors.bottom: parent.bottom
modelData: chat.model ? chat.model.getDump(chat.model.reply, chat.model.id) : {
} }
userColor: TimelineManager.userColor(modelData.userId, colors.window)
}
ImageButton {
id: closeReplyButton
anchors.right: parent.right
anchors.rightMargin: 15
anchors.top: replyPreview.top
hoverEnabled: true
width: 16
height: 16
image: ":/icons/icons/ui/remove-symbol.png"
ToolTip.visible: closeReplyButton.hovered
ToolTip.text: qsTr("Close")
onClicked: chat.model.reply = undefined
} }
} }
@ -571,6 +575,99 @@ Page {
z: 3 z: 3
} }
Rectangle {
Layout.fillWidth: true
z: 3
height: 1
color: colors.mid
}
Rectangle {
color: colors.window
Layout.fillWidth: true
Layout.preferredHeight: textInput.height
Layout.minimumHeight: 40
RowLayout {
id: inputBar
anchors.fill: parent
spacing: 16
ImageButton {
Layout.alignment: Qt.AlignBottom
hoverEnabled: true
width: 22
height: 22
image: ":/icons/icons/ui/place-call.png"
Layout.topMargin: 8
Layout.bottomMargin: 8
Layout.leftMargin: 16
}
ImageButton {
Layout.alignment: Qt.AlignBottom
hoverEnabled: true
width: 22
height: 22
image: ":/icons/icons/ui/paper-clip-outline.png"
Layout.topMargin: 8
Layout.bottomMargin: 8
}
ScrollView {
id: textInput
Layout.alignment: Qt.AlignBottom
Layout.maximumHeight: Window.height / 4
Layout.fillWidth: true
TextArea {
placeholderText: qsTr("Write a message...")
placeholderTextColor: colors.buttonText
color: colors.text
wrapMode: TextEdit.Wrap
MouseArea {
// workaround for wrong cursor shape on some platforms
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: Qt.IBeamCursor
}
background: Rectangle {
color: colors.window
}
}
}
ImageButton {
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
hoverEnabled: true
width: 22
height: 22
image: ":/icons/icons/ui/smile.png"
Layout.topMargin: 8
Layout.bottomMargin: 8
}
ImageButton {
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
hoverEnabled: true
width: 22
height: 22
image: ":/icons/icons/ui/cursor.png"
Layout.topMargin: 8
Layout.bottomMargin: 8
Layout.rightMargin: 16
}
}
}
} }
} }

@ -65,7 +65,7 @@ Item {
} }
Rectangle { Rectangle {
color: colors.dark color: colors.alternateBase
z: -1 z: -1
radius: 10 radius: 10
height: row.height + 24 height: row.height + 24

@ -9,7 +9,7 @@ Label {
background: Rectangle { background: Rectangle {
radius: parent.height / 2 radius: parent.height / 2
color: colors.dark color: colors.alternateBase
} }
} }

@ -8,7 +8,7 @@ Rectangle {
id: bg id: bg
radius: 10 radius: 10
color: colors.dark color: colors.alternateBase
height: Math.round(content.height + 24) height: Math.round(content.height + 24)
width: parent ? parent.width : undefined width: parent ? parent.width : undefined

@ -161,7 +161,7 @@ Popup {
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 1 Layout.preferredHeight: 1
color: emojiPopup.colors.dark color: emojiPopup.colors.alternateBase
} }
// Category picker row // Category picker row
@ -281,7 +281,7 @@ Popup {
Layout.preferredWidth: 1 Layout.preferredWidth: 1
implicitWidth: 1 implicitWidth: 1
height: parent.height height: parent.height
color: emojiPopup.colors.dark color: emojiPopup.colors.alternateBase
} }
// Search Button is special // Search Button is special

@ -339,17 +339,18 @@ UserSettings::applyTheme()
/*windowText*/ QColor("#333"), /*windowText*/ QColor("#333"),
/*button*/ QColor("#333"), /*button*/ QColor("#333"),
/*light*/ QColor(0xef, 0xef, 0xef), /*light*/ QColor(0xef, 0xef, 0xef),
/*dark*/ QColor(220, 220, 220), /*dark*/ QColor(110, 110, 110),
/*mid*/ QColor(110, 110, 110), /*mid*/ QColor(220, 220, 220),
/*text*/ QColor("#333"), /*text*/ QColor("#333"),
/*bright_text*/ QColor("#333"), /*bright_text*/ QColor("#333"),
/*base*/ QColor("#eee"), /*base*/ QColor("#fff"),
/*window*/ QColor("white")); /*window*/ QColor("white"));
lightActive.setColor(QPalette::AlternateBase, QColor("#eee"));
lightActive.setColor(QPalette::Highlight, QColor("#38a3d8")); lightActive.setColor(QPalette::Highlight, QColor("#38a3d8"));
lightActive.setColor(QPalette::ToolTipBase, lightActive.base().color()); lightActive.setColor(QPalette::ToolTipBase, lightActive.base().color());
lightActive.setColor(QPalette::ToolTipText, lightActive.text().color()); lightActive.setColor(QPalette::ToolTipText, lightActive.text().color());
lightActive.setColor(QPalette::Link, QColor("#0077b5")); lightActive.setColor(QPalette::Link, QColor("#0077b5"));
lightActive.setColor(QPalette::ButtonText, QColor(Qt::gray)); lightActive.setColor(QPalette::ButtonText, QColor("#495057"));
QApplication::setPalette(lightActive); QApplication::setPalette(lightActive);
} else if (this->theme() == "dark") { } else if (this->theme() == "dark") {
stylefile.setFileName(":/styles/styles/nheko-dark.qss"); stylefile.setFileName(":/styles/styles/nheko-dark.qss");
@ -357,17 +358,18 @@ UserSettings::applyTheme()
/*windowText*/ QColor("#caccd1"), /*windowText*/ QColor("#caccd1"),
/*button*/ QColor(0xff, 0xff, 0xff), /*button*/ QColor(0xff, 0xff, 0xff),
/*light*/ QColor("#caccd1"), /*light*/ QColor("#caccd1"),
/*dark*/ QColor("#2d3139"), /*dark*/ QColor(110, 110, 110),
/*mid*/ QColor(110, 110, 110), /*mid*/ QColor("#202228"),
/*text*/ QColor("#caccd1"), /*text*/ QColor("#caccd1"),
/*bright_text*/ QColor(0xff, 0xff, 0xff), /*bright_text*/ QColor(0xff, 0xff, 0xff),
/*base*/ QColor("#2d3139"), /*base*/ QColor("#202228"),
/*window*/ QColor("#202228")); /*window*/ QColor("#2d3139"));
darkActive.setColor(QPalette::AlternateBase, QColor("#2d3139"));
darkActive.setColor(QPalette::Highlight, QColor("#38a3d8")); darkActive.setColor(QPalette::Highlight, QColor("#38a3d8"));
darkActive.setColor(QPalette::ToolTipBase, darkActive.base().color()); darkActive.setColor(QPalette::ToolTipBase, darkActive.base().color());
darkActive.setColor(QPalette::ToolTipText, darkActive.text().color()); darkActive.setColor(QPalette::ToolTipText, darkActive.text().color());
darkActive.setColor(QPalette::Link, QColor("#38a3d8")); darkActive.setColor(QPalette::Link, QColor("#38a3d8"));
darkActive.setColor(QPalette::ButtonText, QColor(0x90, 0x90, 0x90)); darkActive.setColor(QPalette::ButtonText, "#727274");
QApplication::setPalette(darkActive); QApplication::setPalette(darkActive);
} else { } else {
stylefile.setFileName(":/styles/styles/system.qss"); stylefile.setFileName(":/styles/styles/system.qss");

Loading…
Cancel
Save