Lomiri
Loading...
Searching...
No Matches
GreeterView.qml
1/*
2 * Copyright (C) 2015-2016 Canonical, Ltd.
3 * Copyright (C) 2021 UBports Foundation
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; version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18import QtQuick 2.12
19import QtQuick.Window 2.2
20import QtGraphicalEffects 1.12
21import Lomiri.Components 1.3
22import Lomiri.Telephony 0.1 as Telephony
23import "../Components"
24
25FocusScope {
26 id: root
27 objectName: "GreeterView"
28
29 focus: true
30
31 property url background
32 property real backgroundSourceSize
33 property real panelHeight
34 property bool hasCustomBackground
35 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
36 property var infographicModel
37 property alias launcherOffset: coverPage.launcherOffset
38 property alias currentIndex: loginList.currentIndex
39 property alias delayMinutes: delayedLockscreen.delayMinutes // TODO
40 property alias alphanumeric: loginList.alphanumeric
41 property alias hasKeyboard: loginList.hasKeyboard
42 property bool locked
43 property bool waiting
44 property var userModel // Set from outside
45 property bool multiUser: false
46 property int orientation
47 property bool isLandscape: root.orientation == Qt.LandscapeOrientation ||
48 root.orientation == Qt.InvertedLandscapeOrientation ||
49 usageMode == "desktop"
50 property bool isPortrait: (root.orientation == Qt.PortraitOrientation ||
51 root.orientation == Qt.InvertedPortraitOrientation) &&
52 usageMode != "desktop"
53
54 property string usageMode
55
56 readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
57 readonly property bool fullyShown: coverPage.showProgress === 1 || lockscreen.shown
58 readonly property bool required: coverPage.required || lockscreen.required
59 readonly property alias sessionToStart: loginList.currentSession
60
61 property rect inputMethodRect
62
63 signal selected(int index)
64 signal responded(string response)
65 signal tease()
66 signal emergencyCall() // unused
67
68 function notifyAuthenticationFailed() {
69 loginList.showError();
70 }
71
72 function forceShow() {
73 coverPage.show();
74 }
75
76 function tryToUnlock(toTheRight) {
77 var coverChanged = coverPage.shown;
78 if (toTheRight) {
79 coverPage.hideRight();
80 } else {
81 coverPage.hide();
82 }
83 if (root.locked) {
84 lockscreen.show();
85 loginList.tryToUnlock();
86 return false;
87 } else {
88 root.responded("");
89 return coverChanged;
90 }
91 }
92
93 function hide() {
94 if (coverPage.visible) {
95 lockscreen.hideNow();
96 } else {
97 lockscreen.hide();
98 }
99 coverPage.hide();
100 }
101
102 function showFakePassword() {
103 loginList.showFakePassword();
104 }
105
106 function showErrorMessage(msg) {
107 coverPage.showErrorMessage(msg);
108 }
109
110 onLockedChanged: changeLockscreenState()
111 onMultiUserChanged: changeLockscreenState()
112
113 function changeLockscreenState() {
114 if (locked || multiUser) {
115 lockscreen.maybeShow();
116 } else {
117 lockscreen.hide();
118 }
119 }
120
121 Keys.onSpacePressed: coverPage.hide();
122 Keys.onReturnPressed: coverPage.hide();
123 Keys.onEnterPressed: coverPage.hide();
124
125 CoverPage {
126 id: lockscreen
127 objectName: "lockscreen"
128 height: parent.height
129 width: parent.width
130 draggable: false
131 state: "LoginList"
132
133 background: root.background
134 backgroundSourceSize: root.backgroundSourceSize
135 panelHeight: root.panelHeight
136 hasCustomBackground: root.hasCustomBackground
137 backgroundShadeOpacity: 0.6
138
139 showInfographic: isLandscape && root.usageMode != "phone" && (root.usageMode != "tablet" || root.multiUser) && !delayedLockscreen.visible
140 infographicModel: root.infographicModel
141
142 shown: false
143 opacity: 0
144
145 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
146 hideAnimation: SequentialAnimation {
147 StandardAnimation { target: loginList; property: "opacity"; to: 0 }
148 StandardAnimation { property: "opacity"; to: 0.5 }
149 }
150
151 infographicsTopMargin: parent.height * 0.125
152 infographicsBottomMargin: parent.height * 0.125
153 infographicsLeftMargin: loginList.x + loginList.width
154
155 onTease: root.tease()
156
157 onShowProgressChanged: {
158 if (showProgress === 0 && !root.locked) {
159 root.responded("");
160 }
161 }
162
163 LoginList {
164 id: loginList
165 objectName: "loginList"
166
167 width: units.gu(40)
168 anchors {
169 top: parent.top
170 bottom: parent.bottom
171 }
172
173 boxVerticalOffset: (height - highlightedHeight -
174 inputMethodRect.height) / 2
175 Behavior on boxVerticalOffset { LomiriNumberAnimation {} }
176
177 enabled: !coverPage.shown && visible
178 visible: !delayedLockscreen.visible
179
180 model: root.userModel
181 onResponded: root.responded(response)
182 onSelected: root.selected(index)
183 onSessionChooserButtonClicked: parent.state = "SessionsList"
184 onCurrentIndexChanged: setCurrentSession()
185
186 locked: root.locked
187 waiting: root.waiting
188
189 Keys.forwardTo: [sessionChooserLoader.item]
190
191 Component.onCompleted: setCurrentSession()
192
193 function setCurrentSession() {
194 currentSession = LightDMService.users.data(currentIndex, LightDMService.userRoles.SessionRole);
195 }
196 }
197
198 DelayedLockscreen {
199 id: delayedLockscreen
200 objectName: "delayedLockscreen"
201 anchors.fill: parent
202 visible: delayMinutes > 0
203 alphaNumeric: loginList.alphanumeric
204 }
205
206 function maybeShow() {
207 if ((root.locked || root.multiUser) && !shown) {
208 showNow();
209 }
210 }
211
212 Loader {
213 id: sessionChooserLoader
214
215 height: loginList.height
216 width: loginList.width
217
218 anchors {
219 left: parent.left
220 leftMargin: Math.min(parent.width * 0.16, units.gu(20))
221 top: parent.top
222 }
223
224 active: false
225
226 onLoaded: sessionChooserLoader.item.forceActiveFocus();
227 onActiveChanged: {
228 if (!active) return;
229 item.updateHighlight(loginList.currentSession);
230 }
231
232 Connections {
233 target: sessionChooserLoader.item
234 onSessionSelected: loginList.currentSession = sessionKey
235 onShowLoginList: {
236 lockscreen.state = "LoginList"
237 loginList.tryToUnlock();
238 }
239 ignoreUnknownSignals: true
240 }
241 }
242
243 // Use an AbstractButton due to icon limitations with Button
244 AbstractButton {
245 id: sessionChooser
246 objectName: "sessionChooserButton"
247
248 readonly property url icon: LightDMService.sessions.iconUrl(loginList.currentSession)
249
250 visible: LightDMService.sessions.count > 1 &&
251 !LightDMService.users.data(loginList.currentUserIndex, LightDMService.userRoles.LoggedInRole)
252
253 height: units.gu(3.5)
254 width: units.gu(3.5)
255
256 activeFocusOnTab: true
257 anchors {
258 right: parent.right
259 rightMargin: units.gu(2)
260
261 bottom: parent.bottom
262 bottomMargin: units.gu(1.5)
263 }
264
265 Rectangle {
266 id: badgeHighlight
267
268 anchors.fill: parent
269 visible: parent.activeFocus
270 color: "transparent"
271 border.color: theme.palette.normal.focus
272 border.width: units.dp(1)
273 radius: 3
274 }
275
276 Icon {
277 id: badge
278 anchors.fill: parent
279 anchors.margins: units.dp(3)
280 keyColor: "#ffffff" // icon providers give us white icons
281 color: theme.palette.normal.raisedSecondaryText
282 source: sessionChooser.icon
283 }
284
285 Keys.onReturnPressed: {
286 parent.state = "SessionsList";
287 }
288
289 onClicked: {
290 parent.state = "SessionsList";
291 }
292
293 // Refresh the icon path if looking at different places at runtime
294 // this is mainly for testing
295 Connections {
296 target: LightDMService.sessions
297 onIconSearchDirectoriesChanged: {
298 badge.source = LightDMService.sessions.iconUrl(root.currentSession)
299 }
300 }
301 }
302
303 states: [
304 State {
305 name: "SessionsList"
306 PropertyChanges { target: loginList; opacity: 0 }
307 PropertyChanges { target: sessionChooserLoader;
308 active: true;
309 opacity: 1
310 source: "SessionsList.qml"
311 }
312 },
313
314 State {
315 name: "LoginList"
316 PropertyChanges { target: loginList; opacity: 1 }
317 PropertyChanges { target: sessionChooserLoader;
318 active: false;
319 opacity: 0
320 source: "";
321 }
322 }
323 ]
324
325 transitions: [
326 Transition {
327 from: "*"
328 to: "*"
329 LomiriNumberAnimation {
330 property: "opacity";
331 }
332 }
333 ]
334
335 Component.onCompleted: if (root.multiUser) showNow()
336 }
337
338 Rectangle {
339 anchors.fill: parent
340 color: "black"
341 opacity: coverPage.showProgress * 0.8
342 }
343
344 CoverPage {
345 id: coverPage
346 objectName: "coverPage"
347 height: parent.height
348 width: parent.width
349 background: root.background
350 hasCustomBackground: root.hasCustomBackground
351 backgroundShadeOpacity: 0.4
352 panelHeight: root.panelHeight
353 draggable: !root.waiting
354 onTease: root.tease()
355 onClicked: hide()
356 backgroundSourceSize: root.backgroundSourceSize
357 infographicModel: root.infographicModel
358
359 showInfographic: !root.multiUser && root.usageMode != "desktop"
360
361 onShowProgressChanged: {
362 if (showProgress === 0) {
363 if (lockscreen.shown) {
364 loginList.tryToUnlock();
365 } else {
366 root.responded("");
367 }
368 }
369 }
370
371 Clock {
372 id: clock
373 anchors.centerIn: parent
374 }
375
376 states: [
377 State {
378 name: "landscape-with-infographics"
379 when: isLandscape && coverPage.showInfographic
380 AnchorChanges {
381 target: clock
382 anchors.top: undefined
383 anchors.horizontalCenter: undefined
384 anchors.verticalCenter: undefined
385 }
386 PropertyChanges {
387 target: clock;
388 anchors.topMargin: undefined
389 anchors.centerIn: coverPage
390 anchors.horizontalCenterOffset: - coverPage.width / 2 + clock.width / 2 + units.gu(8)
391 }
392 PropertyChanges {
393 target: coverPage
394 infographicsLeftMargin: clock.width + units.gu(8)
395 }
396 },
397 State {
398 name: "portrait"
399 when: isPortrait && coverPage.showInfographic
400 AnchorChanges {
401 target: clock;
402 anchors.top: coverPage.top
403 anchors.horizontalCenter: coverPage.horizontalCenter
404 anchors.verticalCenter: undefined
405 }
406 PropertyChanges {
407 target: clock;
408 anchors.topMargin: units.gu(2) + panelHeight
409 anchors.centerIn: undefined
410 anchors.horizontalCenterOffset: 0
411 }
412 PropertyChanges {
413 target: coverPage
414 infographicsLeftMargin: 0
415 }
416 },
417 State {
418 name: "without-infographics"
419 when: !coverPage.showInfographic
420 AnchorChanges {
421 target: clock
422 anchors.top: undefined
423 anchors.horizontalCenter: coverPage.horizontalCenter
424 anchors.verticalCenter: coverPage.verticalCenter
425 }
426 PropertyChanges {
427 target: clock;
428 anchors.topMargin: 0
429 anchors.centerIn: undefined
430 anchors.horizontalCenterOffset: 0
431 }
432 PropertyChanges {
433 target: coverPage
434 infographicsLeftMargin: 0
435 }
436 }
437 ]
438 }
439
440 StyledItem {
441 id: bottomBar
442 visible: usageMode == "phone" && lockscreen.shown
443 height: units.gu(4)
444
445 anchors.left: parent.left
446 anchors.right: parent.right
447 anchors.top: parent.bottom
448 anchors.topMargin: - height * (1 - coverPage.showProgress)
449 - ( inputMethodRect.height )
450
451 Label {
452 text: i18n.tr("Cancel")
453 anchors.left: parent.left
454 anchors.leftMargin: units.gu(2)
455 anchors.top: parent.top
456 anchors.bottom: parent.bottom
457 verticalAlignment: Text.AlignVCenter
458 font.weight: Font.Light
459 fontSize: "small"
460 color: theme.palette.normal.raisedSecondaryText
461
462 AbstractButton {
463 anchors.fill: parent
464 anchors.leftMargin: -units.gu(2)
465 anchors.rightMargin: -units.gu(2)
466 onClicked: coverPage.show()
467 }
468 }
469
470 Label {
471 objectName: "emergencyCallLabel"
472 text: callManager.hasCalls ? i18n.tr("Return to Call") : i18n.tr("Emergency")
473 anchors.right: parent.right
474 anchors.rightMargin: units.gu(2)
475 anchors.top: parent.top
476 anchors.bottom: parent.bottom
477 verticalAlignment: Text.AlignVCenter
478 font.weight: Font.Light
479 fontSize: "small"
480 color: theme.palette.normal.raisedSecondaryText
481 // TODO: uncomment once bug 1616538 is fixed
482 // visible: telepathyHelper.ready && telepathyHelper.emergencyCallsAvailable
483 enabled: visible
484
485 AbstractButton {
486 anchors.fill: parent
487 anchors.leftMargin: -units.gu(2)
488 anchors.rightMargin: -units.gu(2)
489 onClicked: root.emergencyCall()
490 }
491 }
492 }
493
494 states: [
495 State {
496 name: "phone"
497 when: root.usageMode == "phone" || (root.usageMode == "tablet" && isPortrait)
498 AnchorChanges {
499 target: loginList;
500 anchors.horizontalCenter: lockscreen.horizontalCenter;
501 anchors.left: undefined;
502 }
503 PropertyChanges {
504 target: loginList;
505 anchors.leftMargin: 0;
506 }
507 },
508 State {
509 name: "tablet"
510 when: root.usageMode == "tablet" && isLandscape
511 AnchorChanges {
512 target: loginList;
513 anchors.horizontalCenter: undefined;
514 anchors.left: lockscreen.left;
515 }
516 PropertyChanges {
517 target: loginList;
518 anchors.leftMargin: Math.min(lockscreen.width * 0.16, units.gu(8));
519 }
520 },
521 State {
522 name: "desktop"
523 when: root.usageMode == "desktop"
524 AnchorChanges {
525 target: loginList;
526 anchors.horizontalCenter: undefined;
527 anchors.left: lockscreen.left;
528 }
529 PropertyChanges {
530 target: loginList;
531 anchors.leftMargin: Math.min(lockscreen.width * 0.16, units.gu(20));
532 }
533 }
534 ]
535}