Dekko
Loading...
Searching...
No Matches
DekkoHeader.qml
1/* Copyright (C) 2014-2016 Dan Chapman <dpniel@ubuntu.com>
2
3 This file is part of Dekko email client for Ubuntu Devices/
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License as
7 published by the Free Software Foundation; either version 2 of
8 the License or (at your option) version 3
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
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17*/
18import QtQuick 2.4
19import QtQuick.Layouts 1.1
20import QtQuick.Controls.Suru 2.2
21import Lomiri.Components 1.3
22import Dekko.Components 1.0
23import Dekko.Lomiri.Constants 1.0
24
25PixelPerfectItem {
26 id: dekkoHeader
27 property string title
28 property color titleColor: theme.palette.normal.foregroundText
29 property Action backAction
30 property Action primaryAction
31 // List of actions to be used. If more than 1 action, the actions will be placed
32 // in the action drawer.
33 property list<Action> secondaryActions
34 // List of actions to use for the multi-select toolbar
35 property list<Action> multiSelectActionList
36 // List of filter sections to display
37 property alias filterSections: filters.filterSections
38 // The selected filter index. i.e ListView.currentIndex
39 property alias selectedIndex: filters.selectedIndex
40 // Property to add additional content inside the header
41 // This get's loaded when state === "custom",
42 // NOTE: this is loaded into an empty header so any back actions etc need implementing
43 // for each custom component
44 property Component customComponent
45 // Property to hide the bottom margin, will
46 // be useful when we push the mailbox filter up against the bottom
47 property bool showDivider: true
48 // If enableSearching is enabled the search action overrides the primaryAction
49 // And the primaryAction if also set get's place either in secondaryAction or the action drawer if there
50 // are already multiple secondaryAction set.
51 property bool enableSearching: false
52 property bool searchInProgress: false
53 // Setting the pageFlickable will make the child flickable take all available space below
54 // the header. For our usecase we aren't going to have a flickable that uses on a partial area
55 // of the screen so there is no need to configure it to do so here.
56 property Item pageFlickable: null
57 property bool composeButtonEnabled: false
58 property alias backgroundVisible: backgroundColor.visible
59 property alias backgroundColor: backgroundColor.color
60 // Useful readonly properties
61 readonly property bool isDefault: state === "default"
62 readonly property bool isSearchMode: state === "search"
63 readonly property bool isMultiSelect: state === "multi-select"
64 readonly property int dividerHeight: units.dp(1)
65 property bool extend: false
66 readonly property int extendHeight: extend ? units.gu(3) : 0
67 // Get's emitted when a search is activated from the enter key being pressed in the search field.
68 signal searchActivated(string searchString)
69 // emitted on close of search state.
70 signal searchCanceled()
71 // emitted on close of multi-select state
72 signal multiSelectCanceled()
73
74 signal composeNewMessage()
75
76 // function to start multi-select mode
77 function startMultiSelect() {
78 state = "multi-select"
79 }
80 onPageFlickableChanged: internal.checkPageFlickableAnchors();
81 onHeightChanged: internal.checkPageFlickableAnchors()
82
83 anchors {
84 left: parent.left
85 top: parent.top
86 right: parent.right
87 }
88 visible: true
89 height: visible ? units.gu(6) + filterColumn.height + btmMargin.height + extendHeight : 0
90
91
92 Action {
93 id: drawerAction
94 iconName: "contextual-menu"
95 onTriggered: actionDrawer.opened = true
96 }
97
98 Action {
99 id: searchAction
100 iconName: "find"
101 onTriggered: dekkoHeader.state = "search"
102 }
103
104 QtObject {
105 id: internal
106
107 function checkPageFlickableAnchors() {
108 if (dekkoHeader.pageFlickable) {
109 pageFlickable.anchors.left = pageFlickable.parent.left
110 pageFlickable.anchors.right = pageFlickable.parent.right
111 pageFlickable.anchors.bottom = pageFlickable.parent.bottom
112 pageFlickable.anchors.top = dekkoHeader.bottom;
113 }
114 }
115 }
116
117 Rectangle {
118 id: backgroundColor
119 anchors {
120 left: parent.left
121 top: parent.top
122 right: parent.right
123 }
124 height: units.gu(6)
125 color: Suru.backgroundColor
126 }
127
128 Component {
129 id: multiSelectComponent
130 Row {
131 property alias button: inner_button
132 anchors.fill: parent
133// anchors.leftMargin: backAction ? 0 : units.gu(2)
134 anchors.rightMargin: Style.defaultSpacing
135 spacing: Style.defaultSpacing
136 HeaderButton {
137 id: inner_button
138 anchors {
139 top: parent.top
140 bottom: parent.bottom
141 }
142 visible: backAction
143 action: Action {
144 iconName: "back"
145 onTriggered: {
146 dekkoHeader.state = "default"
147 multiSelectCanceled()
148 }
149 }
150 }
151 Repeater {
152 model: multiSelectActionList
153 delegate: HeaderButton {
154// verticalCenterOffset: -units.gu(0.5)
155 iconSize: units.gu(2.2)
156 action: modelData
157 anchors {
158 top: parent.top
159 bottom: parent.bottom
160 }
161 }
162 Layout.fillWidth: true
163 }
164 }
165 }
166
167 Component {
168 id: searchComponent
169 RowLayout {
170 property alias button: inner_button
171
172 anchors.fill: parent
173 anchors.rightMargin: Style.defaultSpacing
174 spacing: 0
175
176 HeaderButton {
177 id: inner_button
178 anchors {
179 top: parent.top
180 bottom: parent.bottom
181 }
182 visible: action
183 action: Action {
184 iconName: "back"
185 onTriggered: {
186 dekkoHeader.state = "default"
187 searchCanceled()
188 }
189 }
190 }
191 TextField {
192 id: sf
193 onAccepted: {searchActivated(text); focus = false}
194 onTextChanged: {
195 if (text) {
196 if (searchTimer.running) {
197 searchTimer.restart()
198 } else {
199 searchTimer.start()
200 }
201 } else {
202 searchTimer.stop()
203 }
204 }
205
206 placeholderText: qsTr("Enter search...")
207 Layout.fillWidth: true
208 inputMethodHints: Qt.ImhNoPredictiveText
209 Component.onCompleted: forceActiveFocus()
210
211 secondaryItem: ActivityIndicator {
212 running: dekkoHeader.searchInProgress
213 opacity: running
214 visible: opacity !== 0
215 width: visible ? height : 0
216 height: parent.height / 1.8
217 Behavior on opacity {
218 NumberAnimation {duration: 250}
219 }
220 Behavior on width {
221 NumberAnimation {duration: 250}
222 }
223 }
224
225 Timer {
226 id: searchTimer
227 interval: 500
228 repeat: false
229 onTriggered: searchActivated(sf.text)
230 }
231 }
232 }
233 }
234
235 Component {
236 id: defaultHeadState
237 StretchRow {
238 property alias button: inner_button
239 height: parent ? parent.height : undefined
240 anchors {
241 fill: parent
242 leftMargin: backAction ? 0 : units.gu(1)
243 }
244 spacing: 0
245
246 HeaderButton {
247 id: inner_button
248 anchors {
249 top: parent.top
250 bottom: parent.bottom
251 }
252 implicitWidth: units.gu(4)
253 visible: backAction
254 action: backAction ? backAction : null
255 }
256
257 Stretcher {
258 height: parent.height
259 Label {
260 text: dekkoHeader.title
261 anchors.verticalCenter: parent.verticalCenter
262 anchors.left: parent.left
263 width: parent.width
264 fontSize: "large"
265 elide: Text.ElideRight
266 clip: true
267 color: dekkoHeader.titleColor
268 textFormat: Qt.RichText
269 Layout.fillWidth: true
270 }
271 }
272
273
274 HeaderButton {
275 id: compose_button
276 anchors {
277 top: parent.top
278 bottom: parent.bottom
279 }
280 width: units.gu(4)
281 implicitWidth: width
282 implicitHeight: height
283 visible: composeButtonEnabled
284 action: Action {
285 iconName: "edit"
286 onTriggered: composeNewMessage()
287 }
288 }
289
290 HeaderButton {
291 id: inner2_button
292 anchors {
293 top: parent.top
294 bottom: parent.bottom
295 }
296 width: units.gu(4)
297 implicitWidth: width
298 implicitHeight: height
299 visible: action ? action.visible : false
300 action: enableSearching ? searchAction : primaryAction
301 }
302
303 HeaderButton {
304 id: inner3_button
305 anchors {
306 top: parent.top
307 bottom: parent.bottom
308 }
309 width: units.gu(4)
310 implicitWidth: width
311 implicitHeight: height
312 visible: secondaryActions[0] ? secondaryActions[0].visible : false
313 action: secondaryActions.length > 1 ? drawerAction : secondaryActions.length === 1 ? secondaryActions[0] : null
314 }
315 }
316 }
317 Column {
318 id: filterColumn
319 anchors {
320 left: parent.left
321 top: backgroundColor.bottom
322 right: parent.right
323 }
324 height: filters.height
325 FilterBar {
326 id: filters
327 anchors {
328 left: parent.left
329 right: parent.right
330 }
331 height: filterSections.length && !isMultiSelect ? units.gu(3) : 0
332 }
333
334 }
335
336
337 Line {
338 id: btmMargin
339 anchors {
340 left: parent.left
341 right: parent.right
342 top: filterColumn.bottom
343 topMargin: extend ? extendHeight : 0
344 }
345 height: visible ? Style.lineSize : 0
346 visible: showDivider
347 }
348
349 Loader {
350 id: loader
351 anchors{
352 left: backgroundColor.left
353 right: backgroundColor.right
354 top: backgroundColor.top
355 bottom: backgroundColor.bottom
356 leftMargin: units.gu(1)
357 rightMargin: units.gu(1)
358 }
359 Binding {
360 target: loader
361 property: "sourceComponent"
362 value: defaultHeadState
363 when: loader && dekkoHeader.state === "default"
364 }
365 Binding {
366 target: loader
367 property: "sourceComponent"
368 value: searchComponent
369 when: loader && dekkoHeader.state === "search"
370 }
371 Binding {
372 target: loader
373 property: "sourceComponent"
374 value: multiSelectComponent
375 when: loader && dekkoHeader.state === "multi-select"
376 }
377 Binding {
378 target: loader
379 property: "sourceComponent"
380 value: customComponent
381 when: loader && dekkoHeader.state === "custom"
382 }
383 }
384
385 ActionDrawer {
386 id: actionDrawer
387 anchors {
388 top: parent.bottom
389 right: parent.right
390 }
391 actions: secondaryActions.length > 1 ? secondaryActions : []
392 }
393
394 state: "default"
395 states: [
396 State {
397 name: "default"
398 },
399 State {
400 name: "search"
401 },
402 State {
403 name: "multi-select"
404 PropertyChanges {
405 target: dekkoHeader
406 height: units.gu(6) + filterColumn.height
407 }
408 },
409 State {
410 name: "custom"
411 }
412 ]
413}