summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2017-08-16 23:27:51 -0600
committerChristian Mollekopf <chrigi_1@fastmail.fm>2017-08-17 20:02:43 -0600
commitde4be82bf141f1eb5f57189bf251123e57996f28 (patch)
tree28701506e5e77af0120b25f52aed02f252cb12ac
parenta855b61ace6572e19c305cdee7bd080c5f89eb51 (diff)
downloadkube-de4be82bf141f1eb5f57189bf251123e57996f28.tar.gz
kube-de4be82bf141f1eb5f57189bf251123e57996f28.zip
Non listview based conversationview
The listview deals badly with non uniformly sized items. We use the buffer hack to ensure all items are loaded so it works at all, and setting the current index resulted in unpredictable scrolling. With this new approach we manage everything ourselves in a Flickable, and just always load all delegates (which we also did before, but with a hack). As an optimization it should be possible to avoid loading some delegates until they become visible. Note that ConversationView is thightly coupled to ConversationListView due to dependencies on some properties in the delegate. This could be handled more elegantly with attached properties. In any case, this seems to work much, much better.
-rw-r--r--framework/qml/ConversationListView.qml138
-rw-r--r--framework/qml/ConversationView.qml82
-rw-r--r--framework/qmldir1
3 files changed, 155 insertions, 66 deletions
diff --git a/framework/qml/ConversationListView.qml b/framework/qml/ConversationListView.qml
new file mode 100644
index 00000000..fb91e14d
--- /dev/null
+++ b/framework/qml/ConversationListView.qml
@@ -0,0 +1,138 @@
1/*
2 * Copyright (C) 2016 Michael Bohlender, <michael.bohlender@kdemail.net>
3 * Copyright (C) 2017 Christian Mollekopf, <mollekopf@kolabsystems.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20import QtQuick 2.7
21import QtQuick.Controls 2
22import QtQuick.Layouts 1.1
23import org.kube.framework 1.0 as Kube
24
25import QtQml 2.2 as QtQml
26
27Flickable {
28 id: root
29 focus: true
30 property alias model: repeater.model
31 property alias delegate: repeater.delegate
32 property int currentIndex: -1
33
34 property var currentItem: null
35
36 function setCurrentItem() {
37 if (currentItem) {
38 currentItem.isCurrentItem = false
39 }
40 if (currentIndex >= 0 && activeFocus) {
41 var item = repeater.itemAt(currentIndex)
42 if (item) {
43 item.isCurrentItem = true
44 currentItem = item
45 }
46 } else {
47 currentItem = null
48 }
49 }
50
51 onCurrentIndexChanged: {
52 setCurrentItem()
53 }
54
55 onActiveFocusChanged: {
56 setCurrentItem()
57 }
58
59 //Optimize for view quality
60 pixelAligned: true
61
62 contentWidth: width
63 contentHeight: col.height
64
65 function scrollToIndex(index) {
66 var item = repeater.itemAt(index)
67 var pos = item.y
68 var scrollToEndPos = (root.contentHeight - root.height)
69 //Avoid scrolling past the end
70 if (pos < scrollToEndPos) {
71 root.contentY = pos
72 } else {
73 root.contentY = scrollToEndPos
74 }
75 }
76
77 onContentHeightChanged: {
78 if (repeater.count) {
79 //Scroll to the last item
80 currentIndex = repeater.count - 1
81 scrollToIndex(repeater.count - 1)
82 }
83 }
84
85 property real span : contentY + height
86 Column {
87 id: col
88 width: parent.width
89 spacing: 2
90 Repeater {
91 id: repeater
92 onCountChanged: {
93 for (var i = 0; i < count; i++) {
94 itemAt(i).index = i
95 }
96 }
97 }
98 }
99
100 function incrementCurrentIndex() {
101 if (currentIndex < repeater.count - 1) {
102 currentIndex = currentIndex + 1
103 }
104 }
105
106 function decrementCurrentIndex() {
107 if (currentIndex > 0) {
108 currentIndex = currentIndex - 1
109 }
110 }
111
112 Keys.onDownPressed: {
113 incrementCurrentIndex()
114 scrollToIndex(currentIndex)
115 }
116
117 Keys.onUpPressed: {
118 decrementCurrentIndex()
119 scrollToIndex(currentIndex)
120 }
121
122 Kube.ScrollHelper {
123 id: scrollHelper
124 flickable: root
125 anchors.fill: parent
126 }
127
128 //Intercept all scroll events,
129 //necessary due to the webengineview
130 Kube.MouseProxy {
131 anchors.fill: parent
132 target: scrollHelper
133 forwardWheelEvents: true
134 }
135
136 ScrollBar.vertical: ScrollBar {}
137
138}
diff --git a/framework/qml/ConversationView.qml b/framework/qml/ConversationView.qml
index 85707300..03ea3065 100644
--- a/framework/qml/ConversationView.qml
+++ b/framework/qml/ConversationView.qml
@@ -36,7 +36,6 @@ FocusScope {
36 filter: Kube.Messages.mailSelection 36 filter: Kube.Messages.mailSelection
37 onMessageReceived: { 37 onMessageReceived: {
38 root.mail = message.mail 38 root.mail = message.mail
39 listView.forceLayout()
40 } 39 }
41 } 40 }
42 41
@@ -52,10 +51,9 @@ FocusScope {
52 anchors.fill: parent 51 anchors.fill: parent
53 color: Kube.Colors.backgroundColor 52 color: Kube.Colors.backgroundColor
54 53
55 Kube.ListView { 54 Kube.ConversationListView {
56 id: listView 55 id: listView
57 focus: true 56 focus: true
58 currentIndex: -1
59 57
60 anchors { 58 anchors {
61 top: parent.top 59 top: parent.top
@@ -65,69 +63,33 @@ FocusScope {
65 //Shrink the listview if the content doesn't fill the full height, so the email appears on top instead of on the bottom. 63 //Shrink the listview if the content doesn't fill the full height, so the email appears on top instead of on the bottom.
66 height: Math.min(contentHeight, parent.height) 64 height: Math.min(contentHeight, parent.height)
67 65
68 verticalLayoutDirection: ListView.BottomToTop
69
70 onActiveFocusChanged: {
71 if (activeFocus) {
72 if (currentIndex < 0) {
73 currentIndex = 0
74 }
75 } else {
76 currentIndex = -1
77 }
78 }
79
80 //Position view so the last email begins on top of the view
81 onContentHeightChanged: {
82 //FIXME This doesn't work quite correctly when we have headers and footers in the listview and the mail loads to slowly and only one item to show.
83 listView.positionViewAtIndex(currentIndex, ListView.End)
84 }
85
86 Keys.onDownPressed: {
87 decrementCurrentIndex()
88 positionViewAtIndex(listView.currentIndex, ListView.End)
89 }
90
91 Keys.onUpPressed: {
92 incrementCurrentIndex()
93 positionViewAtIndex(listView.currentIndex, ListView.End)
94 }
95
96 onCurrentIndexChanged: {
97 markAsReadTimer.restart();
98 }
99
100 model: Kube.MailListModel { 66 model: Kube.MailListModel {
101 mail: root.mail 67 mail: root.mail
102 } 68 }
103 69
104 header: Item {
105 height: Kube.Units.gridUnit * 0.5
106 width: parent.width
107 }
108
109 footer: Item {
110 height: Kube.Units.gridUnit
111 width: parent.width
112 }
113
114 delegate: FocusScope { 70 delegate: FocusScope {
115 id: delegateRoot 71 id: delegateRoot
116 72
117 property var currentData: model 73 property var currentData: model
74 property bool isCurrentItem: false
75 property int index: -1
118 76
119 focus: false 77 focus: false
120 activeFocusOnTab: false 78 activeFocusOnTab: false
121 79
122 height: sheet.height + Kube.Units.gridUnit 80 height: sheet.height + Kube.Units.gridUnit
123 width: parent.width 81 width: listView.width
124 visible: !((root.hideTrash && model.trash) || (root.hideNonTrash && !model.trash)) 82 visible: !((root.hideTrash && model.trash) || (root.hideNonTrash && !model.trash))
125 83
126 MouseArea { 84 MouseArea {
127 anchors.fill: parent 85 anchors.fill: parent
128 hoverEnabled: true 86 hoverEnabled: true
129 onEntered: delegateRoot.ListView.view.currentIndex = index 87 onEntered: listView.currentIndex = delegateRoot.index
130 onClicked: delegateRoot.ListView.view.currentIndex = index 88 onClicked: {
89 listView.currentIndex = delegateRoot.index
90 listView.forceActiveFocus(Qt.MouseFocusReason)
91 }
92 z: 1.0
131 } 93 }
132 94
133 MailViewer { 95 MailViewer {
@@ -147,19 +109,15 @@ FocusScope {
147 draft: model.draft 109 draft: model.draft
148 sent: model.sent 110 sent: model.sent
149 incomplete: model.incomplete 111 incomplete: model.incomplete
150 current: delegateRoot.ListView.isCurrentItem 112 current: delegateRoot.isCurrentItem
151 } 113 }
152 } 114 }
153 115
154 116 onCurrentItemChanged: {
155 //Optimize for view quality 117 if (currentItem) {
156 pixelAligned: true 118 markAsReadTimer.restart()
157 119 }
158 120 }
159 //The cacheBuffer needs to be large enough to fit the whole thread.
160 //Otherwise the contentHeight will constantly increase and decrease,
161 //which will break lot's of things.
162 cacheBuffer: 100000
163 121
164 Timer { 122 Timer {
165 id: markAsReadTimer 123 id: markAsReadTimer
@@ -172,14 +130,6 @@ FocusScope {
172 } 130 }
173 } 131 }
174 } 132 }
175
176 //Intercept all scroll events,
177 //necessary due to the webengineview
178 Kube.MouseProxy {
179 anchors.fill: parent
180 target: listView.mouseProxy
181 forwardWheelEvents: true
182 }
183 } 133 }
184 } 134 }
185} 135}
diff --git a/framework/qmldir b/framework/qmldir
index 02a66bb6..7ce99e2a 100644
--- a/framework/qmldir
+++ b/framework/qmldir
@@ -1,6 +1,7 @@
1module org.kube.framework 1module org.kube.framework
2 2
3ConversationView 1.0 ConversationView.qml 3ConversationView 1.0 ConversationView.qml
4ConversationListView 1.0 ConversationListView.qml
4FolderListView 1.0 FolderListView.qml 5FolderListView 1.0 FolderListView.qml
5MailListView 1.0 MailListView.qml 6MailListView 1.0 MailListView.qml
6InlineAccountSwitcher 1.0 InlineAccountSwitcher.qml 7InlineAccountSwitcher 1.0 InlineAccountSwitcher.qml