Lomiri
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 
18 import QtQuick 2.12
19 import QtQuick.Window 2.2
20 import QtGraphicalEffects 1.12
21 import Lomiri.Components 1.3
22 import Lomiri.Telephony 0.1 as Telephony
23 import "../Components"
24 
25 FocusScope {
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 }