From d075a90024388913e81c6c40607482252057d71a Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 12 Oct 2020 22:05:55 +0200 Subject: Fix emoji filter losing focus --- resources/qml/emoji/EmojiPicker.qml | 1 + 1 file changed, 1 insertion(+) (limited to 'resources/qml') diff --git a/resources/qml/emoji/EmojiPicker.qml b/resources/qml/emoji/EmojiPicker.qml index 3a5ee57a..0e277415 100644 --- a/resources/qml/emoji/EmojiPicker.qml +++ b/resources/qml/emoji/EmojiPicker.qml @@ -59,6 +59,7 @@ Popup { cellHeight: 52 boundsBehavior: Flickable.StopAtBounds clip: true + currentIndex: -1 // prevent sorting from stealing focus // Individual emoji delegate: AbstractButton { -- cgit 1.5.1 From a0c2a174eaf1446e8ed42a2e396c1bb9ce885b7e Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Tue, 13 Oct 2020 16:24:42 -0400 Subject: Display filename on mouse hover --- resources/qml/delegates/ImageMessage.qml | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) (limited to 'resources/qml') diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml index e2c78fbe..8307bdd6 100644 --- a/resources/qml/delegates/ImageMessage.qml +++ b/resources/qml/delegates/ImageMessage.qml @@ -31,11 +31,40 @@ Item { fillMode: Image.PreserveAspectFit MouseArea { + id: mouseArea enabled: model.data.type == MtxEvent.ImageMessage && img.status == Image.Ready + hoverEnabled: true anchors.fill: parent onClicked: TimelineManager.openImageOverlay(model.data.url, model.data.id) } - } + Item { + id: overlay + + anchors.fill: parent + visible: mouseArea.containsMouse + + Rectangle { + id: container + width: parent.width + implicitHeight: imgcaption.implicitHeight + anchors.bottom: overlay.bottom + color: "black" + opacity: 0.75 + + Text { + id: imgcaption + anchors.fill: parent + elide: Text.ElideMiddle + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + // See this MSC: https://github.com/matrix-org/matrix-doc/pull/2530 + text: model.data.filename ? model.data.filename : model.data.body + font.pointSize: 11 + color: "white" + } + } + } + } } -- cgit 1.5.1 From e3c665661393b7495d275bc23fb68a158c5df3fb Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Wed, 14 Oct 2020 18:07:09 -0400 Subject: Avoid hardcoded font size --- resources/qml/delegates/ImageMessage.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'resources/qml') diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml index 8307bdd6..3f6d9951 100644 --- a/resources/qml/delegates/ImageMessage.qml +++ b/resources/qml/delegates/ImageMessage.qml @@ -61,10 +61,10 @@ Item { verticalAlignment: Text.AlignVCenter // See this MSC: https://github.com/matrix-org/matrix-doc/pull/2530 text: model.data.filename ? model.data.filename : model.data.body - font.pointSize: 11 color: "white" } } } + } } -- cgit 1.5.1 From e1c4f7d516b2096c5b133fb7610a8a83755503a0 Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Thu, 15 Oct 2020 11:52:49 -0400 Subject: Make Text element a sibling of the Rectangle to avoid the text from depending on the rectangle's opacity property. Switch to Nheko theme colors --- resources/qml/delegates/ImageMessage.qml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'resources/qml') diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml index 3f6d9951..6ac5ee15 100644 --- a/resources/qml/delegates/ImageMessage.qml +++ b/resources/qml/delegates/ImageMessage.qml @@ -43,28 +43,28 @@ Item { anchors.fill: parent visible: mouseArea.containsMouse - + Rectangle { id: container + width: parent.width implicitHeight: imgcaption.implicitHeight anchors.bottom: overlay.bottom - color: "black" + color: colors.window opacity: 0.75 + } - Text { - id: imgcaption - - anchors.fill: parent - elide: Text.ElideMiddle - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - // See this MSC: https://github.com/matrix-org/matrix-doc/pull/2530 - text: model.data.filename ? model.data.filename : model.data.body - color: "white" - } + Text { + id: imgcaption + + anchors.fill: container + elide: Text.ElideMiddle + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + // See this MSC: https://github.com/matrix-org/matrix-doc/pull/2530 + text: model.data.filename ? model.data.filename : model.data.body + color: colors.text } } - } } -- cgit 1.5.1 From b3a7f0b88807a1d3e2a4d7acf121db0aedfc157f Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 18 Oct 2020 22:30:42 +0200 Subject: Hide room name, if not loaded yet --- resources/qml/TimelineView.qml | 2 +- resources/qml/delegates/ImageMessage.qml | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) (limited to 'resources/qml') diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index ab0148e9..1f5f406a 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -159,6 +159,7 @@ Page { } ColumnLayout { + visible: TimelineManager.timeline != null anchors.fill: parent Rectangle { @@ -287,7 +288,6 @@ Page { property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2) - visible: TimelineManager.timeline != null cacheBuffer: 400 Layout.fillWidth: true Layout.fillHeight: true diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml index 6ac5ee15..5c3dac95 100644 --- a/resources/qml/delegates/ImageMessage.qml +++ b/resources/qml/delegates/ImageMessage.qml @@ -32,6 +32,7 @@ Item { MouseArea { id: mouseArea + enabled: model.data.type == MtxEvent.ImageMessage && img.status == Image.Ready hoverEnabled: true anchors.fill: parent @@ -40,23 +41,23 @@ Item { Item { id: overlay - + anchors.fill: parent visible: mouseArea.containsMouse Rectangle { id: container - + width: parent.width implicitHeight: imgcaption.implicitHeight - anchors.bottom: overlay.bottom + anchors.bottom: overlay.bottom color: colors.window opacity: 0.75 } Text { id: imgcaption - + anchors.fill: container elide: Text.ElideMiddle horizontalAlignment: Text.AlignHCenter @@ -65,6 +66,9 @@ Item { text: model.data.filename ? model.data.filename : model.data.body color: colors.text } + } + } + } -- cgit 1.5.1 From e959443831c35f5e5478c049f007b961e55356c0 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 22 Oct 2020 21:02:39 +0200 Subject: Speed up rendering the timeline by a LOT by reducing clipping Sadly still required for replies, otherwise this would be perfect. --- resources/qml/delegates/NoticeMessage.qml | 2 +- resources/qml/delegates/TextMessage.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'resources/qml') diff --git a/resources/qml/delegates/NoticeMessage.qml b/resources/qml/delegates/NoticeMessage.qml index d9a7a3e7..67a69055 100644 --- a/resources/qml/delegates/NoticeMessage.qml +++ b/resources/qml/delegates/NoticeMessage.qml @@ -2,5 +2,5 @@ TextMessage { font.italic: true color: colors.buttonText height: isReply ? Math.min(chat.height / 8, implicitHeight) : undefined - clip: true + clip: isReply } diff --git a/resources/qml/delegates/TextMessage.qml b/resources/qml/delegates/TextMessage.qml index 69f2f0e3..8a1c116e 100644 --- a/resources/qml/delegates/TextMessage.qml +++ b/resources/qml/delegates/TextMessage.qml @@ -7,6 +7,6 @@ MatrixText { text: "" + formatted.replace("
", "
")
     width: parent ? parent.width : undefined
     height: isReply ? Math.round(Math.min(timelineRoot.height / 8, implicitHeight)) : undefined
-    clip: true
+    clip: isReply
     font.pointSize: (Settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize
 }
-- 
cgit 1.5.1


From 53734607cf93d626c16f1b2ae72a7dea24d8460e Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Sat, 24 Oct 2020 14:42:18 +0200
Subject: Fix copying text

---
 resources/qml/MatrixText.qml | 2 --
 1 file changed, 2 deletions(-)

(limited to 'resources/qml')

diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml
index 6c96a539..f1ccf561 100644
--- a/resources/qml/MatrixText.qml
+++ b/resources/qml/MatrixText.qml
@@ -7,7 +7,6 @@ TextEdit {
     readOnly: true
     wrapMode: Text.Wrap
     selectByMouse: true
-    activeFocusOnPress: false
     color: colors.text
     onLinkActivated: {
         if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) {
@@ -29,7 +28,6 @@ TextEdit {
         id: ma
 
         anchors.fill: parent
-        propagateComposedEvents: true
         acceptedButtons: Qt.NoButton
         cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
     }
-- 
cgit 1.5.1


From 3172811ca784c7bdaa02ad4e0a59a8f0ef381280 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Sat, 24 Oct 2020 16:21:00 +0200
Subject: Add mobile mode which improves scrolling

---
 resources/qml/MatrixText.qml |  2 +-
 src/UserSettingsPage.cpp     | 21 +++++++++++++++++++++
 src/UserSettingsPage.h       |  6 ++++++
 3 files changed, 28 insertions(+), 1 deletion(-)

(limited to 'resources/qml')

diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml
index f1ccf561..a5781c73 100644
--- a/resources/qml/MatrixText.qml
+++ b/resources/qml/MatrixText.qml
@@ -6,7 +6,7 @@ TextEdit {
     textFormat: TextEdit.RichText
     readOnly: true
     wrapMode: Text.Wrap
-    selectByMouse: true
+    selectByMouse: !Settings.mobileMode
     color: colors.text
     onLinkActivated: {
         if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) {
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 5558b246..6fd31de3 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -75,6 +75,7 @@ UserSettings::load()
         decryptSidebar_      = settings.value("user/decrypt_sidebar", true).toBool();
         shareKeysWithTrustedUsers_ =
           settings.value("user/share_keys_with_trusted_users", true).toBool();
+        mobileMode_   = settings.value("user/mobile_mode", false).toBool();
         emojiFont_    = settings.value("user/emoji_font_family", "default").toString();
         baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble();
         presence_ =
@@ -123,6 +124,16 @@ UserSettings::setStartInTray(bool state)
         save();
 }
 
+void
+UserSettings::setMobileMode(bool state)
+{
+        if (state == mobileMode_)
+                return;
+        mobileMode_ = state;
+        emit mobileModeChanged(state);
+        save();
+}
+
 void
 UserSettings::setGroupView(bool state)
 {
@@ -389,6 +400,7 @@ UserSettings::save()
         settings.setValue("avatar_circles", avatarCircles_);
         settings.setValue("decrypt_sidebar", decryptSidebar_);
         settings.setValue("share_keys_with_trusted_users", shareKeysWithTrustedUsers_);
+        settings.setValue("mobile_mode", mobileMode_);
         settings.setValue("font_size", baseFontSize_);
         settings.setValue("typing_notifications", typingNotifications_);
         settings.setValue("minor_events", sortByImportance_);
@@ -470,6 +482,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
         desktopNotifications_      = new Toggle{this};
         alertOnNotification_       = new Toggle{this};
         useStunServer_             = new Toggle{this};
+        mobileMode_                = new Toggle{this};
         scaleFactorCombo_          = new QComboBox{this};
         fontSizeCombo_             = new QComboBox{this};
         fontSelectionCombo_        = new QFontComboBox{this};
@@ -637,6 +650,9 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
         formLayout_->addRow(uiLabel_);
         formLayout_->addRow(new HorizontalLine{this});
 
+        boxWrap(tr("Mobile mode"),
+                mobileMode_,
+                tr("Will prevent text selection in the timeline to make scrolling easier."));
 #if !defined(Q_OS_MAC)
         boxWrap(tr("Scale factor"),
                 scaleFactorCombo_,
@@ -728,6 +744,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge
                 settings_->setStartInTray(!disabled);
         });
 
+        connect(mobileMode_, &Toggle::toggled, this, [this](bool disabled) {
+                settings_->setMobileMode(!disabled);
+        });
+
         connect(groupViewToggle_, &Toggle::toggled, this, [this](bool disabled) {
                 settings_->setGroupView(!disabled);
         });
@@ -820,6 +840,7 @@ UserSettingsPage::showEvent(QShowEvent *)
         typingNotifications_->setState(!settings_->typingNotifications());
         sortByImportance_->setState(!settings_->sortByImportance());
         timelineButtonsToggle_->setState(!settings_->buttonsInTimeline());
+        mobileMode_->setState(!settings_->mobileMode());
         readReceipts_->setState(!settings_->readReceipts());
         markdown_->setState(!settings_->markdown());
         desktopNotifications_->setState(!settings_->hasDesktopNotifications());
diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index 37355602..ffe98542 100644
--- a/src/UserSettingsPage.h
+++ b/src/UserSettingsPage.h
@@ -67,6 +67,7 @@ class UserSettings : public QObject
                      decryptSidebarChanged)
         Q_PROPERTY(int timelineMaxWidth READ timelineMaxWidth WRITE setTimelineMaxWidth NOTIFY
                      timelineMaxWidthChanged)
+        Q_PROPERTY(bool mobileMode READ mobileMode WRITE setMobileMode NOTIFY mobileModeChanged)
         Q_PROPERTY(double fontSize READ fontSize WRITE setFontSize NOTIFY fontSizeChanged)
         Q_PROPERTY(QString font READ font WRITE setFontFamily NOTIFY fontChanged)
         Q_PROPERTY(
@@ -99,6 +100,7 @@ public:
         void setEnlargeEmojiOnlyMessages(bool state);
         void setTray(bool state);
         void setStartInTray(bool state);
+        void setMobileMode(bool mode);
         void setFontSize(double size);
         void setFontFamily(QString family);
         void setEmojiFontFamily(QString family);
@@ -130,6 +132,7 @@ public:
         bool typingNotifications() const { return typingNotifications_; }
         bool sortByImportance() const { return sortByImportance_; }
         bool buttonsInTimeline() const { return buttonsInTimeline_; }
+        bool mobileMode() const { return mobileMode_; }
         bool readReceipts() const { return readReceipts_; }
         bool hasDesktopNotifications() const { return hasDesktopNotifications_; }
         bool hasAlertOnNotification() const { return hasAlertOnNotification_; }
@@ -163,6 +166,7 @@ signals:
         void avatarCirclesChanged(bool state);
         void decryptSidebarChanged(bool state);
         void timelineMaxWidthChanged(int state);
+        void mobileModeChanged(bool mode);
         void fontSizeChanged(double state);
         void fontChanged(QString state);
         void emojiFontChanged(QString state);
@@ -193,6 +197,7 @@ private:
         bool avatarCircles_;
         bool decryptSidebar_;
         bool shareKeysWithTrustedUsers_;
+        bool mobileMode_;
         int timelineMaxWidth_;
         double baseFontSize_;
         QString font_;
@@ -256,6 +261,7 @@ private:
         Toggle *useStunServer_;
         Toggle *decryptSidebar_;
         Toggle *shareKeysWithTrustedUsers_;
+        Toggle *mobileMode_;
         QLabel *deviceFingerprintValue_;
         QLabel *deviceIdValue_;
 
-- 
cgit 1.5.1


From 3a0e55e97b667feaf3d9e9e0296664db246d4033 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Mon, 26 Oct 2020 13:50:44 +0100
Subject: Qml message input mockup

---
 resources/qml/Avatar.qml                         |   2 +-
 resources/qml/Reactions.qml                      |   2 +-
 resources/qml/TimelineRow.qml                    |   2 +-
 resources/qml/TimelineView.qml                   | 519 ++++++++++++++---------
 resources/qml/delegates/FileMessage.qml          |   2 +-
 resources/qml/delegates/Pill.qml                 |   2 +-
 resources/qml/delegates/PlayableMediaMessage.qml |   2 +-
 resources/qml/emoji/EmojiPicker.qml              |   4 +-
 src/UserSettingsPage.cpp                         |  20 +-
 9 files changed, 327 insertions(+), 228 deletions(-)

(limited to 'resources/qml')

diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml
index a247bffe..34b029a6 100644
--- a/resources/qml/Avatar.qml
+++ b/resources/qml/Avatar.qml
@@ -13,7 +13,7 @@ Rectangle {
     width: 48
     height: 48
     radius: Settings.avatarCircles ? height / 2 : 3
-    color: colors.base
+    color: colors.alternateBase
 
     Label {
         anchors.fill: parent
diff --git a/resources/qml/Reactions.qml b/resources/qml/Reactions.qml
index 6487f512..836087ef 100644
--- a/resources/qml/Reactions.qml
+++ b/resources/qml/Reactions.qml
@@ -83,7 +83,7 @@ Flow {
                 implicitWidth: reaction.implicitWidth
                 implicitHeight: reaction.implicitHeight
                 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
                 radius: reaction.height / 2
             }
diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml
index 38597673..57fded90 100644
--- a/resources/qml/TimelineRow.qml
+++ b/resources/qml/TimelineRow.qml
@@ -48,7 +48,7 @@ Item {
             Reply {
                 visible: model.replyTo
                 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
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index 1f5f406a..3124d462 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -161,6 +161,7 @@ Page {
         ColumnLayout {
             visible: TimelineManager.timeline != null
             anchors.fill: parent
+            spacing: 0
 
             Rectangle {
                 id: topBar
@@ -168,7 +169,7 @@ Page {
                 Layout.fillWidth: true
                 implicitHeight: topLayout.height + 16
                 z: 3
-                color: colors.base
+                color: colors.window
 
                 MouseArea {
                     anchors.fill: parent
@@ -283,143 +284,156 @@ Page {
 
             }
 
-            ListView {
-                id: chat
-
-                property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2)
+            Rectangle {
+                Layout.fillWidth: true
+                height: 1
+                z: 3
+                color: colors.mid
+            }
 
-                cacheBuffer: 400
+            Rectangle {
                 Layout.fillWidth: true
                 Layout.fillHeight: true
-                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
-                }
+                color: colors.base
 
-                Shortcut {
-                    sequence: StandardKey.MoveToPreviousPage
-                    onActivated: {
-                        chat.contentY = chat.contentY - chat.height / 2;
-                        chat.returnToBounds();
-                    }
-                }
+                ListView {
+                    id: chat
 
-                Shortcut {
-                    sequence: StandardKey.MoveToNextPage
-                    onActivated: {
-                        chat.contentY = chat.contentY + chat.height / 2;
-                        chat.returnToBounds();
+                    property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2)
+
+                    cacheBuffer: 400
+                    anchors.fill: parent
+                    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 {
-                    sequence: StandardKey.Cancel
-                    onActivated: chat.model.reply = undefined
-                }
+                    Shortcut {
+                        sequence: StandardKey.MoveToPreviousPage
+                        onActivated: {
+                            chat.contentY = chat.contentY - chat.height / 2;
+                            chat.returnToBounds();
+                        }
+                    }
 
-                Shortcut {
-                    sequence: "Alt+Up"
-                    onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0)
-                }
+                    Shortcut {
+                        sequence: StandardKey.MoveToNextPage
+                        onActivated: {
+                            chat.contentY = chat.contentY + chat.height / 2;
+                            chat.returnToBounds();
+                        }
+                    }
 
-                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;
+                    Shortcut {
+                        sequence: StandardKey.Cancel
+                        onActivated: chat.model.reply = undefined
                     }
-                }
 
-                Component {
-                    id: userProfileComponent
+                    Shortcut {
+                        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 {
-                    property: "section"
-                }
+                        UserProfile {
+                        }
 
-                Component {
-                    id: sectionHeader
+                    }
 
-                    Column {
-                        property var modelData
-                        property string section
-                        property string nextSection
+                    section {
+                        property: "section"
+                    }
 
-                        topPadding: 4
-                        bottomPadding: 4
-                        spacing: 8
-                        visible: !!modelData
-                        width: parent.width
-                        height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
+                    Component {
+                        id: sectionHeader
 
-                        Label {
-                            id: dateBubble
+                        Column {
+                            property var modelData
+                            property string section
+                            property string nextSection
 
-                            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
+                            topPadding: 4
+                            bottomPadding: 4
+                            spacing: 8
+                            visible: !!modelData
+                            width: parent.width
+                            height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
+
+                            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 {
-                            height: userName.height
-                            spacing: 8
+                                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
+                                    }
 
-                            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 {
-                    id: wrapper
-
-                    // 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 Item section
-
-                    anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
-                    width: chat.delegateMaxWidth
-                    height: section ? section.height + timelinerow.height : timelinerow.height
-                    onSectionBoundaryChanged: {
-                        if (sectionBoundary) {
-                            var properties = {
-                                "modelData": model.dump,
-                                "section": ListView.section,
-                                "nextSection": ListView.nextSection
-                            };
-                            section = sectionHeader.createObject(wrapper, properties);
-                        } else {
-                            section.destroy();
-                            section = null;
+                    delegate: Item {
+                        id: wrapper
+
+                        // 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 Item section
+
+                        anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
+                        width: chat.delegateMaxWidth
+                        height: section ? section.height + timelinerow.height : timelinerow.height
+                        onSectionBoundaryChanged: {
+                            if (sectionBoundary) {
+                                var properties = {
+                                    "modelData": model.dump,
+                                    "section": ListView.section,
+                                    "nextSection": ListView.nextSection
+                                };
+                                section = sectionHeader.createObject(wrapper, properties);
+                            } else {
+                                section.destroy();
+                                section = null;
+                            }
                         }
-                    }
 
-                    TimelineRow {
-                        id: timelinerow
+                        TimelineRow {
+                            id: timelinerow
 
-                        y: section ? section.y + section.height : 0
-                    }
+                            y: section ? section.y + section.height : 0
+                        }
 
-                    Connections {
-                        function onMovementEnded() {
-                            if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
-                                chat.model.currentIndex = index;
+                        Connections {
+                            function onMovementEnded() {
+                                if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
+                                    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 {
-                id: chatFooter
-
-                implicitHeight: Math.max(fontMetrics.height * 1.2, footerContent.height)
+                implicitHeight: Math.max(fontMetrics.height * 1.2, typingDisplay.height)
                 Layout.fillWidth: true
-                z: 3
 
-                Column {
-                    id: footerContent
+                Rectangle {
+                    id: typingRect
 
+                    color: colors.base
                     anchors.left: parent.left
                     anchors.right: parent.right
-                    anchors.bottom: parent.bottom
+                    height: (chat.model && chat.model.typingUsers.length > 0) ? typingDisplay.height : 0
+                    z: 3
 
-                    Rectangle {
-                        id: typingRect
+                    Label {
+                        id: typingDisplay
 
                         anchors.left: parent.left
+                        anchors.leftMargin: 10
                         anchors.right: parent.right
-                        color: (chat.model && chat.model.typingUsers.length > 0) ? colors.window : "transparent"
-                        height: typingDisplay.height
-
-                        Label {
-                            id: typingDisplay
+                        anchors.rightMargin: 10
+                        anchors.bottom: parent.bottom
+                        color: colors.text
+                        text: chat.model ? chat.model.formatTypingUsers(chat.model.typingUsers, colors.base) : ""
+                        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 {
-                        id: replyPopup
+            Rectangle {
+                id: replyPopup
 
-                        anchors.left: parent.left
-                        anchors.right: parent.right
-                        visible: chat.model && chat.model.reply
-                        // Height of child, plus margins, plus border
-                        height: replyPreview.height + 10
-                        color: colors.base
-
-                        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)
-                        }
+                Layout.fillWidth: true
+                visible: chat.model && chat.model.reply
+                // Height of child, plus margins, plus border
+                implicitHeight: replyPreview.height + 10
+                color: colors.window
+                z: 3
 
-                        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
-                        }
+                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 {
+                    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
             }
 
+            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
+                    }
+
+                }
+
+            }
+
         }
 
     }
diff --git a/resources/qml/delegates/FileMessage.qml b/resources/qml/delegates/FileMessage.qml
index c6f213ee..ffd1e82b 100644
--- a/resources/qml/delegates/FileMessage.qml
+++ b/resources/qml/delegates/FileMessage.qml
@@ -65,7 +65,7 @@ Item {
     }
 
     Rectangle {
-        color: colors.dark
+        color: colors.alternateBase
         z: -1
         radius: 10
         height: row.height + 24
diff --git a/resources/qml/delegates/Pill.qml b/resources/qml/delegates/Pill.qml
index 4acf2bef..88f6c7fd 100644
--- a/resources/qml/delegates/Pill.qml
+++ b/resources/qml/delegates/Pill.qml
@@ -9,7 +9,7 @@ Label {
 
     background: Rectangle {
         radius: parent.height / 2
-        color: colors.dark
+        color: colors.alternateBase
     }
 
 }
diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml
index 9ad115c7..be22687f 100644
--- a/resources/qml/delegates/PlayableMediaMessage.qml
+++ b/resources/qml/delegates/PlayableMediaMessage.qml
@@ -8,7 +8,7 @@ Rectangle {
     id: bg
 
     radius: 10
-    color: colors.dark
+    color: colors.alternateBase
     height: Math.round(content.height + 24)
     width: parent ? parent.width : undefined
 
diff --git a/resources/qml/emoji/EmojiPicker.qml b/resources/qml/emoji/EmojiPicker.qml
index 0e277415..afe16350 100644
--- a/resources/qml/emoji/EmojiPicker.qml
+++ b/resources/qml/emoji/EmojiPicker.qml
@@ -161,7 +161,7 @@ Popup {
         Rectangle {
             Layout.fillWidth: true
             Layout.preferredHeight: 1
-            color: emojiPopup.colors.dark
+            color: emojiPopup.colors.alternateBase
         }
 
         // Category picker row
@@ -281,7 +281,7 @@ Popup {
                 Layout.preferredWidth: 1
                 implicitWidth: 1
                 height: parent.height
-                color: emojiPopup.colors.dark
+                color: emojiPopup.colors.alternateBase
             }
 
             // Search Button is special
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 53539407..d4d5dcb9 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -339,17 +339,18 @@ UserSettings::applyTheme()
                   /*windowText*/ QColor("#333"),
                   /*button*/ QColor("#333"),
                   /*light*/ QColor(0xef, 0xef, 0xef),
-                  /*dark*/ QColor(220, 220, 220),
-                  /*mid*/ QColor(110, 110, 110),
+                  /*dark*/ QColor(110, 110, 110),
+                  /*mid*/ QColor(220, 220, 220),
                   /*text*/ QColor("#333"),
                   /*bright_text*/ QColor("#333"),
-                  /*base*/ QColor("#eee"),
+                  /*base*/ QColor("#fff"),
                   /*window*/ QColor("white"));
+                lightActive.setColor(QPalette::AlternateBase, QColor("#eee"));
                 lightActive.setColor(QPalette::Highlight, QColor("#38a3d8"));
                 lightActive.setColor(QPalette::ToolTipBase, lightActive.base().color());
                 lightActive.setColor(QPalette::ToolTipText, lightActive.text().color());
                 lightActive.setColor(QPalette::Link, QColor("#0077b5"));
-                lightActive.setColor(QPalette::ButtonText, QColor(Qt::gray));
+                lightActive.setColor(QPalette::ButtonText, QColor("#495057"));
                 QApplication::setPalette(lightActive);
         } else if (this->theme() == "dark") {
                 stylefile.setFileName(":/styles/styles/nheko-dark.qss");
@@ -357,17 +358,18 @@ UserSettings::applyTheme()
                   /*windowText*/ QColor("#caccd1"),
                   /*button*/ QColor(0xff, 0xff, 0xff),
                   /*light*/ QColor("#caccd1"),
-                  /*dark*/ QColor("#2d3139"),
-                  /*mid*/ QColor(110, 110, 110),
+                  /*dark*/ QColor(110, 110, 110),
+                  /*mid*/ QColor("#202228"),
                   /*text*/ QColor("#caccd1"),
                   /*bright_text*/ QColor(0xff, 0xff, 0xff),
-                  /*base*/ QColor("#2d3139"),
-                  /*window*/ QColor("#202228"));
+                  /*base*/ QColor("#202228"),
+                  /*window*/ QColor("#2d3139"));
+                darkActive.setColor(QPalette::AlternateBase, QColor("#2d3139"));
                 darkActive.setColor(QPalette::Highlight, QColor("#38a3d8"));
                 darkActive.setColor(QPalette::ToolTipBase, darkActive.base().color());
                 darkActive.setColor(QPalette::ToolTipText, darkActive.text().color());
                 darkActive.setColor(QPalette::Link, QColor("#38a3d8"));
-                darkActive.setColor(QPalette::ButtonText, QColor(0x90, 0x90, 0x90));
+                darkActive.setColor(QPalette::ButtonText, "#727274");
                 QApplication::setPalette(darkActive);
         } else {
                 stylefile.setFileName(":/styles/styles/system.qss");
-- 
cgit 1.5.1


From 3d64df41dac45693d1e2626909f0e55d4b7a6f47 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Mon, 26 Oct 2020 14:57:54 +0100
Subject: Split up TimelineView into separate components

---
 resources/qml/ActiveCallBar.qml   |   2 +-
 resources/qml/MessageInput.qml    |  90 +++++++
 resources/qml/MessageView.qml     | 202 ++++++++++++++++
 resources/qml/ReplyPopup.qml      |  47 ++++
 resources/qml/TimelineView.qml    | 495 ++------------------------------------
 resources/qml/TopBar.qml          | 127 ++++++++++
 resources/qml/TypingIndicator.qml |  35 +++
 resources/res.qrc                 |   5 +
 8 files changed, 531 insertions(+), 472 deletions(-)
 create mode 100644 resources/qml/MessageInput.qml
 create mode 100644 resources/qml/MessageView.qml
 create mode 100644 resources/qml/ReplyPopup.qml
 create mode 100644 resources/qml/TopBar.qml
 create mode 100644 resources/qml/TypingIndicator.qml

(limited to 'resources/qml')

diff --git a/resources/qml/ActiveCallBar.qml b/resources/qml/ActiveCallBar.qml
index 9344738e..dcdec9ef 100644
--- a/resources/qml/ActiveCallBar.qml
+++ b/resources/qml/ActiveCallBar.qml
@@ -8,7 +8,7 @@ Rectangle {
 
     visible: TimelineManager.callState != WebRTCState.DISCONNECTED
     color: "#2ECC71"
-    implicitHeight: rowLayout.height + 8
+    implicitHeight: visible ? rowLayout.height + 8 : 0
 
     RowLayout {
         id: rowLayout
diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml
new file mode 100644
index 00000000..71da9cae
--- /dev/null
+++ b/resources/qml/MessageInput.qml
@@ -0,0 +1,90 @@
+import QtQuick 2.9
+import QtQuick.Controls 2.3
+import QtQuick.Layouts 1.2
+import QtQuick.Window 2.2
+
+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
+        }
+
+    }
+
+}
diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml
new file mode 100644
index 00000000..09220a71
--- /dev/null
+++ b/resources/qml/MessageView.qml
@@ -0,0 +1,202 @@
+import "./delegates"
+import QtGraphicalEffects 1.0
+import QtQuick 2.9
+import QtQuick.Controls 2.3
+import QtQuick.Layouts 1.2
+import QtQuick.Window 2.2
+import im.nheko 1.0
+
+ListView {
+    id: chat
+
+    property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2)
+
+    Layout.fillWidth: true
+    Layout.fillHeight: true
+    cacheBuffer: 400
+    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 {
+        sequence: StandardKey.MoveToPreviousPage
+        onActivated: {
+            chat.contentY = chat.contentY - chat.height / 2;
+            chat.returnToBounds();
+        }
+    }
+
+    Shortcut {
+        sequence: StandardKey.MoveToNextPage
+        onActivated: {
+            chat.contentY = chat.contentY + chat.height / 2;
+            chat.returnToBounds();
+        }
+    }
+
+    Shortcut {
+        sequence: StandardKey.Cancel
+        onActivated: chat.model.reply = undefined
+    }
+
+    Shortcut {
+        sequence: "Alt+Up"
+        onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0)
+    }
+
+    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;
+        }
+    }
+
+    section {
+        property: "section"
+    }
+
+    Component {
+        id: sectionHeader
+
+        Column {
+            property var modelData
+            property string section
+            property string nextSection
+
+            topPadding: 4
+            bottomPadding: 4
+            spacing: 8
+            visible: !!modelData
+            width: parent.width
+            height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
+
+            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
+                }
+
+            }
+
+            Row {
+                height: userName.height
+                spacing: 8
+
+                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
+                    }
+
+                }
+
+            }
+
+        }
+
+    }
+
+    ScrollBar.vertical: ScrollBar {
+        id: scrollbar
+    }
+
+    delegate: Item {
+        id: wrapper
+
+        // 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 Item section
+
+        anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
+        width: chat.delegateMaxWidth
+        height: section ? section.height + timelinerow.height : timelinerow.height
+        onSectionBoundaryChanged: {
+            if (sectionBoundary) {
+                var properties = {
+                    "modelData": model.dump,
+                    "section": ListView.section,
+                    "nextSection": ListView.nextSection
+                };
+                section = sectionHeader.createObject(wrapper, properties);
+            } else {
+                section.destroy();
+                section = null;
+            }
+        }
+
+        TimelineRow {
+            id: timelinerow
+
+            y: section ? section.y + section.height : 0
+        }
+
+        Connections {
+            function onMovementEnded() {
+                if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
+                    chat.model.currentIndex = index;
+
+            }
+
+            target: chat
+        }
+
+    }
+
+    footer: BusyIndicator {
+        anchors.horizontalCenter: parent.horizontalCenter
+        running: chat.model && chat.model.paginationInProgress
+        height: 50
+        width: 50
+        z: 3
+    }
+
+}
diff --git a/resources/qml/ReplyPopup.qml b/resources/qml/ReplyPopup.qml
new file mode 100644
index 00000000..4659e075
--- /dev/null
+++ b/resources/qml/ReplyPopup.qml
@@ -0,0 +1,47 @@
+import "./delegates/"
+import QtQuick 2.9
+import QtQuick.Controls 2.3
+import QtQuick.Layouts 1.2
+import im.nheko 1.0
+
+Rectangle {
+    id: replyPopup
+
+    property var room: TimelineManager.timeline
+
+    Layout.fillWidth: true
+    visible: room && room.reply
+    // Height of child, plus margins, plus border
+    implicitHeight: replyPreview.height + 10
+    color: colors.window
+    z: 3
+
+    Reply {
+        id: replyPreview
+
+        anchors.left: parent.left
+        anchors.leftMargin: 2 * 22 + 3 * 16
+        anchors.right: closeReplyButton.left
+        anchors.rightMargin: 2 * 22 + 3 * 16
+        anchors.bottom: parent.bottom
+        modelData: room ? room.getDump(room.reply, room.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: room.reply = undefined
+    }
+
+}
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index 3124d462..023bae00 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -43,12 +43,21 @@ Page {
 
     }
 
+    Component {
+        id: userProfileComponent
+
+        UserProfile {
+        }
+
+    }
+
     Menu {
         id: messageContextMenu
 
         property string eventId
         property int eventType
         property bool isEncrypted
+        property var room: TimelineManager.timeline
 
         function show(eventId_, eventType_, isEncrypted_, showAt_, position) {
             eventId = eventId_;
@@ -69,12 +78,12 @@ Page {
 
         MenuItem {
             text: qsTr("Reply")
-            onClicked: chat.model.replyAction(messageContextMenu.eventId)
+            onClicked: room.replyAction(messageContextMenu.eventId)
         }
 
         MenuItem {
             text: qsTr("Read receipts")
-            onTriggered: chat.model.readReceiptsAction(messageContextMenu.eventId)
+            onTriggered: room.readReceiptsAction(messageContextMenu.eventId)
         }
 
         MenuItem {
@@ -83,19 +92,19 @@ Page {
 
         MenuItem {
             text: qsTr("View raw message")
-            onTriggered: chat.model.viewRawMessage(messageContextMenu.eventId)
+            onTriggered: room.viewRawMessage(messageContextMenu.eventId)
         }
 
         MenuItem {
             visible: messageContextMenu.isEncrypted
             height: visible ? implicitHeight : 0
             text: qsTr("View decrypted raw message")
-            onTriggered: chat.model.viewDecryptedRawMessage(messageContextMenu.eventId)
+            onTriggered: room.viewDecryptedRawMessage(messageContextMenu.eventId)
         }
 
         MenuItem {
             text: qsTr("Redact message")
-            onTriggered: chat.model.redactEvent(messageContextMenu.eventId)
+            onTriggered: room.redactEvent(messageContextMenu.eventId)
         }
 
         MenuItem {
@@ -163,125 +172,7 @@ Page {
             anchors.fill: parent
             spacing: 0
 
-            Rectangle {
-                id: topBar
-
-                Layout.fillWidth: true
-                implicitHeight: topLayout.height + 16
-                z: 3
-                color: colors.window
-
-                MouseArea {
-                    anchors.fill: parent
-                    onClicked: TimelineManager.openRoomSettings()
-                }
-
-                GridLayout {
-                    //Layout.margins: 8
-
-                    id: topLayout
-
-                    anchors.left: parent.left
-                    anchors.right: parent.right
-                    anchors.margins: 8
-                    anchors.verticalCenter: parent.verticalCenter
-
-                    ImageButton {
-                        id: backToRoomsButton
-
-                        Layout.column: 0
-                        Layout.row: 0
-                        Layout.rowSpan: 2
-                        Layout.alignment: Qt.AlignVCenter
-                        visible: TimelineManager.isNarrowView
-                        image: ":/icons/icons/ui/angle-pointing-to-left.png"
-                        ToolTip.visible: hovered
-                        ToolTip.text: qsTr("Back to room list")
-                        onClicked: TimelineManager.backToRooms()
-                    }
-
-                    Avatar {
-                        Layout.column: 1
-                        Layout.row: 0
-                        Layout.rowSpan: 2
-                        Layout.alignment: Qt.AlignVCenter
-                        width: avatarSize
-                        height: avatarSize
-                        url: chat.model ? chat.model.roomAvatarUrl.replace("mxc://", "image://MxcImage/") : ""
-                        displayName: chat.model ? chat.model.roomName : qsTr("No room selected")
-
-                        MouseArea {
-                            anchors.fill: parent
-                            onClicked: TimelineManager.openRoomSettings()
-                        }
-
-                    }
-
-                    Label {
-                        Layout.fillWidth: true
-                        Layout.column: 2
-                        Layout.row: 0
-                        color: colors.text
-                        font.pointSize: fontMetrics.font.pointSize * 1.1
-                        text: chat.model ? chat.model.roomName : qsTr("No room selected")
-
-                        MouseArea {
-                            anchors.fill: parent
-                            onClicked: TimelineManager.openRoomSettings()
-                        }
-
-                    }
-
-                    MatrixText {
-                        Layout.fillWidth: true
-                        Layout.column: 2
-                        Layout.row: 1
-                        Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
-                        clip: true
-                        text: chat.model ? chat.model.roomTopic : ""
-                    }
-
-                    ImageButton {
-                        id: roomOptionsButton
-
-                        Layout.column: 3
-                        Layout.row: 0
-                        Layout.rowSpan: 2
-                        Layout.alignment: Qt.AlignVCenter
-                        image: ":/icons/icons/ui/vertical-ellipsis.png"
-                        ToolTip.visible: hovered
-                        ToolTip.text: qsTr("Room options")
-                        onClicked: roomOptionsMenu.popup(roomOptionsButton)
-
-                        Menu {
-                            id: roomOptionsMenu
-
-                            MenuItem {
-                                text: qsTr("Invite users")
-                                onTriggered: TimelineManager.openInviteUsersDialog()
-                            }
-
-                            MenuItem {
-                                text: qsTr("Members")
-                                onTriggered: TimelineManager.openMemberListDialog()
-                            }
-
-                            MenuItem {
-                                text: qsTr("Leave room")
-                                onTriggered: TimelineManager.openLeaveRoomDialog()
-                            }
-
-                            MenuItem {
-                                text: qsTr("Settings")
-                                onTriggered: TimelineManager.openRoomSettings()
-                            }
-
-                        }
-
-                    }
-
-                }
-
+            TopBar {
             }
 
             Rectangle {
@@ -296,276 +187,18 @@ Page {
                 Layout.fillHeight: true
                 color: colors.base
 
-                ListView {
-                    id: chat
-
-                    property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2)
-
-                    cacheBuffer: 400
+                ColumnLayout {
                     anchors.fill: parent
-                    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 {
-                        sequence: StandardKey.MoveToPreviousPage
-                        onActivated: {
-                            chat.contentY = chat.contentY - chat.height / 2;
-                            chat.returnToBounds();
-                        }
-                    }
-
-                    Shortcut {
-                        sequence: StandardKey.MoveToNextPage
-                        onActivated: {
-                            chat.contentY = chat.contentY + chat.height / 2;
-                            chat.returnToBounds();
-                        }
-                    }
-
-                    Shortcut {
-                        sequence: StandardKey.Cancel
-                        onActivated: chat.model.reply = undefined
-                    }
-
-                    Shortcut {
-                        sequence: "Alt+Up"
-                        onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0)
-                    }
-
-                    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
-
-                        UserProfile {
-                        }
+                    spacing: 0
 
+                    MessageView {
+                        Layout.fillWidth: true
+                        Layout.fillHeight: true
                     }
 
-                    section {
-                        property: "section"
-                    }
-
-                    Component {
-                        id: sectionHeader
-
-                        Column {
-                            property var modelData
-                            property string section
-                            property string nextSection
-
-                            topPadding: 4
-                            bottomPadding: 4
-                            spacing: 8
-                            visible: !!modelData
-                            width: parent.width
-                            height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
-
-                            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
-                                }
-
-                            }
-
-                            Row {
-                                height: userName.height
-                                spacing: 8
-
-                                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
-                                    }
-
-                                }
-
-                            }
-
-                        }
-
-                    }
-
-                    ScrollBar.vertical: ScrollBar {
-                        id: scrollbar
-                    }
-
-                    delegate: Item {
-                        id: wrapper
-
-                        // 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 Item section
-
-                        anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
-                        width: chat.delegateMaxWidth
-                        height: section ? section.height + timelinerow.height : timelinerow.height
-                        onSectionBoundaryChanged: {
-                            if (sectionBoundary) {
-                                var properties = {
-                                    "modelData": model.dump,
-                                    "section": ListView.section,
-                                    "nextSection": ListView.nextSection
-                                };
-                                section = sectionHeader.createObject(wrapper, properties);
-                            } else {
-                                section.destroy();
-                                section = null;
-                            }
-                        }
-
-                        TimelineRow {
-                            id: timelinerow
-
-                            y: section ? section.y + section.height : 0
-                        }
-
-                        Connections {
-                            function onMovementEnded() {
-                                if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
-                                    chat.model.currentIndex = index;
-
-                            }
-
-                            target: chat
-                        }
-
-                    }
-
-                    footer: BusyIndicator {
-                        anchors.horizontalCenter: parent.horizontalCenter
-                        running: chat.model && chat.model.paginationInProgress
-                        height: 50
-                        width: 50
-                        z: 3
-                    }
-
-                }
-
-            }
-
-            Item {
-                implicitHeight: Math.max(fontMetrics.height * 1.2, typingDisplay.height)
-                Layout.fillWidth: true
-
-                Rectangle {
-                    id: typingRect
-
-                    color: colors.base
-                    anchors.left: parent.left
-                    anchors.right: parent.right
-                    height: (chat.model && chat.model.typingUsers.length > 0) ? typingDisplay.height : 0
-                    z: 3
-
-                    Label {
-                        id: typingDisplay
-
-                        anchors.left: parent.left
-                        anchors.leftMargin: 10
-                        anchors.right: parent.right
-                        anchors.rightMargin: 10
-                        anchors.bottom: parent.bottom
-                        color: colors.text
-                        text: chat.model ? chat.model.formatTypingUsers(chat.model.typingUsers, colors.base) : ""
-                        textFormat: Text.RichText
-                    }
-
-                }
-
-            }
-
-            Rectangle {
-                id: replyPopup
-
-                Layout.fillWidth: true
-                visible: chat.model && chat.model.reply
-                // Height of child, plus margins, plus border
-                implicitHeight: replyPreview.height + 10
-                color: colors.window
-                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) : {
+                    TypingIndicator {
                     }
-                    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
                 }
 
             }
@@ -582,90 +215,10 @@ Page {
                 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
-                    }
-
-                }
+            ReplyPopup {
+            }
 
+            MessageInput {
             }
 
         }
diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml
new file mode 100644
index 00000000..181b9ba4
--- /dev/null
+++ b/resources/qml/TopBar.qml
@@ -0,0 +1,127 @@
+import QtQuick 2.9
+import QtQuick.Controls 2.3
+import QtQuick.Layouts 1.2
+import im.nheko 1.0
+
+Rectangle {
+    id: topBar
+
+    property var room: TimelineManager.timeline
+
+    Layout.fillWidth: true
+    implicitHeight: topLayout.height + 16
+    z: 3
+    color: colors.window
+
+    MouseArea {
+        anchors.fill: parent
+        onClicked: TimelineManager.openRoomSettings()
+    }
+
+    GridLayout {
+        //Layout.margins: 8
+
+        id: topLayout
+
+        anchors.left: parent.left
+        anchors.right: parent.right
+        anchors.margins: 8
+        anchors.verticalCenter: parent.verticalCenter
+
+        ImageButton {
+            id: backToRoomsButton
+
+            Layout.column: 0
+            Layout.row: 0
+            Layout.rowSpan: 2
+            Layout.alignment: Qt.AlignVCenter
+            visible: TimelineManager.isNarrowView
+            image: ":/icons/icons/ui/angle-pointing-to-left.png"
+            ToolTip.visible: hovered
+            ToolTip.text: qsTr("Back to room list")
+            onClicked: TimelineManager.backToRooms()
+        }
+
+        Avatar {
+            Layout.column: 1
+            Layout.row: 0
+            Layout.rowSpan: 2
+            Layout.alignment: Qt.AlignVCenter
+            width: avatarSize
+            height: avatarSize
+            url: room ? room.roomAvatarUrl.replace("mxc://", "image://MxcImage/") : ""
+            displayName: room ? room.roomName : qsTr("No room selected")
+
+            MouseArea {
+                anchors.fill: parent
+                onClicked: TimelineManager.openRoomSettings()
+            }
+
+        }
+
+        Label {
+            Layout.fillWidth: true
+            Layout.column: 2
+            Layout.row: 0
+            color: colors.text
+            font.pointSize: fontMetrics.font.pointSize * 1.1
+            text: room ? room.roomName : qsTr("No room selected")
+
+            MouseArea {
+                anchors.fill: parent
+                onClicked: TimelineManager.openRoomSettings()
+            }
+
+        }
+
+        MatrixText {
+            Layout.fillWidth: true
+            Layout.column: 2
+            Layout.row: 1
+            Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
+            clip: true
+            text: room ? room.roomTopic : ""
+        }
+
+        ImageButton {
+            id: roomOptionsButton
+
+            Layout.column: 3
+            Layout.row: 0
+            Layout.rowSpan: 2
+            Layout.alignment: Qt.AlignVCenter
+            image: ":/icons/icons/ui/vertical-ellipsis.png"
+            ToolTip.visible: hovered
+            ToolTip.text: qsTr("Room options")
+            onClicked: roomOptionsMenu.popup(roomOptionsButton)
+
+            Menu {
+                id: roomOptionsMenu
+
+                MenuItem {
+                    text: qsTr("Invite users")
+                    onTriggered: TimelineManager.openInviteUsersDialog()
+                }
+
+                MenuItem {
+                    text: qsTr("Members")
+                    onTriggered: TimelineManager.openMemberListDialog()
+                }
+
+                MenuItem {
+                    text: qsTr("Leave room")
+                    onTriggered: TimelineManager.openLeaveRoomDialog()
+                }
+
+                MenuItem {
+                    text: qsTr("Settings")
+                    onTriggered: TimelineManager.openRoomSettings()
+                }
+
+            }
+
+        }
+
+    }
+
+}
diff --git a/resources/qml/TypingIndicator.qml b/resources/qml/TypingIndicator.qml
new file mode 100644
index 00000000..239fd662
--- /dev/null
+++ b/resources/qml/TypingIndicator.qml
@@ -0,0 +1,35 @@
+import QtQuick 2.9
+import QtQuick.Controls 2.3
+import QtQuick.Layouts 1.2
+import im.nheko 1.0
+
+Item {
+    property var room: TimelineManager.timeline
+
+    implicitHeight: Math.max(fontMetrics.height * 1.2, typingDisplay.height)
+    Layout.fillWidth: true
+
+    Rectangle {
+        id: typingRect
+
+        visible: (room && room.typingUsers.length > 0)
+        color: colors.base
+        anchors.fill: parent
+        z: 3
+
+        Label {
+            id: typingDisplay
+
+            anchors.left: parent.left
+            anchors.leftMargin: 10
+            anchors.right: parent.right
+            anchors.rightMargin: 10
+            anchors.bottom: parent.bottom
+            color: colors.text
+            text: room ? room.formatTypingUsers(room.typingUsers, colors.base) : ""
+            textFormat: Text.RichText
+        }
+
+    }
+
+}
diff --git a/resources/res.qrc b/resources/res.qrc
index 87216e30..d11b78bc 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -121,6 +121,11 @@
         qtquickcontrols2.conf
 
         qml/TimelineView.qml
+        qml/TopBar.qml
+        qml/MessageView.qml
+        qml/MessageInput.qml
+        qml/TypingIndicator.qml
+        qml/ReplyPopup.qml
         qml/ActiveCallBar.qml
         qml/Avatar.qml
         qml/ImageButton.qml
-- 
cgit 1.5.1


From 0e7baa21aba53f5f5f0c96592c4ab1bff2316430 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Mon, 26 Oct 2020 21:32:08 +0100
Subject: Disable new MessageInput, until you can paste images

---
 resources/qml/TimelineView.qml  | 4 ++--
 resources/styles/nheko-dark.qss | 5 +----
 resources/styles/nheko.qss      | 6 ------
 resources/styles/system.qss     | 4 +---
 4 files changed, 4 insertions(+), 15 deletions(-)

(limited to 'resources/qml')

diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index 023bae00..516c6860 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -218,8 +218,8 @@ Page {
             ReplyPopup {
             }
 
-            MessageInput {
-            }
+            //MessageInput {
+            //}
 
         }
 
diff --git a/resources/styles/nheko-dark.qss b/resources/styles/nheko-dark.qss
index 3676f874..547e4256 100644
--- a/resources/styles/nheko-dark.qss
+++ b/resources/styles/nheko-dark.qss
@@ -205,9 +205,7 @@ TextField {
     qproperty-labelColor: #caccd1;
 }
 
-SideBarActions,
-TopRoomBar
-{
+SideBarActions {
     border: none;
     border-top: 1px solid #202228;
     background-color: #2d3139;
@@ -215,7 +213,6 @@ TopRoomBar
 
 TextInputWidget {
     border: none;
-    border-top: 1px solid #2d3139;
 }
 
 TextInputWidget,
diff --git a/resources/styles/nheko.qss b/resources/styles/nheko.qss
index 8604ad30..81d97f65 100644
--- a/resources/styles/nheko.qss
+++ b/resources/styles/nheko.qss
@@ -233,7 +233,6 @@ QLineEdit {
 
 TextInputWidget {
     border: none;
-    border-top: 1px solid #dcdcdc;
 }
 
 SideBarActions {
@@ -241,11 +240,6 @@ SideBarActions {
     border-top: 1px solid #dcdcdc;
 }
 
-TopRoomBar {
-    border: none;
-    border-bottom: 1px solid #dcdcdc;
-}
-
 Toggle {
     qproperty-activeColor: #38a3d8;
     qproperty-disabledColor: gray;
diff --git a/resources/styles/system.qss b/resources/styles/system.qss
index 01951aff..d65c8baa 100644
--- a/resources/styles/system.qss
+++ b/resources/styles/system.qss
@@ -28,11 +28,9 @@ UserMentionsWidget > TimelineItem {
 SideBarActions,
 TextInputWidget {
     border: none;
-    border-top: 1px solid palette(mid);
 }
 
-UserInfoWidget,
-TopRoomBar {
+UserInfoWidget {
     border: none;
     border-bottom: 1px solid palette(mid);
 }
-- 
cgit 1.5.1


From 7f2d18c36dabbcae0d0637d489371c2b71869e67 Mon Sep 17 00:00:00 2001
From: Nicolas Werner 
Date: Mon, 26 Oct 2020 21:44:31 +0100
Subject: Fix small scope issue

---
 resources/qml/TimelineView.qml | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

(limited to 'resources/qml')

diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index 516c6860..de047e8a 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -57,7 +57,6 @@ Page {
         property string eventId
         property int eventType
         property bool isEncrypted
-        property var room: TimelineManager.timeline
 
         function show(eventId_, eventType_, isEncrypted_, showAt_, position) {
             eventId = eventId_;
@@ -78,12 +77,12 @@ Page {
 
         MenuItem {
             text: qsTr("Reply")
-            onClicked: room.replyAction(messageContextMenu.eventId)
+            onClicked: TimelineManager.timeline.replyAction(messageContextMenu.eventId)
         }
 
         MenuItem {
             text: qsTr("Read receipts")
-            onTriggered: room.readReceiptsAction(messageContextMenu.eventId)
+            onTriggered: TimelineManager.timeline.readReceiptsAction(messageContextMenu.eventId)
         }
 
         MenuItem {
@@ -92,19 +91,19 @@ Page {
 
         MenuItem {
             text: qsTr("View raw message")
-            onTriggered: room.viewRawMessage(messageContextMenu.eventId)
+            onTriggered: TimelineManager.timeline.viewRawMessage(messageContextMenu.eventId)
         }
 
         MenuItem {
             visible: messageContextMenu.isEncrypted
             height: visible ? implicitHeight : 0
             text: qsTr("View decrypted raw message")
-            onTriggered: room.viewDecryptedRawMessage(messageContextMenu.eventId)
+            onTriggered: TimelineManager.timeline.viewDecryptedRawMessage(messageContextMenu.eventId)
         }
 
         MenuItem {
             text: qsTr("Redact message")
-            onTriggered: room.redactEvent(messageContextMenu.eventId)
+            onTriggered: TimelineManager.timeline.redactEvent(messageContextMenu.eventId)
         }
 
         MenuItem {
-- 
cgit 1.5.1