Lomiri
WideView.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.4
19 import Lomiri.Components 1.3
20 import "." 0.1
21 
22 FocusScope {
23  id: root
24  objectName: "WideView"
25 
26  focus: true
27 
28  property alias background: coverPage.background
29  property alias backgroundSourceSize: coverPage.backgroundSourceSize
30  property alias panelHeight: coverPage.panelHeight
31  property alias hasCustomBackground: coverPage.hasCustomBackground
32  property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
33  property alias infographicModel: coverPage.infographicModel
34  property alias launcherOffset: coverPage.launcherOffset
35  property alias currentIndex: loginList.currentIndex
36  property int delayMinutes // TODO
37  property alias alphanumeric: loginList.alphanumeric
38  property alias hasKeyboard: loginList.hasKeyboard
39  property alias locked: loginList.locked
40  property alias waiting: loginList.waiting
41  property var userModel // Set from outside
42 
43  readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
44  readonly property bool fullyShown: coverPage.showProgress === 1
45  readonly property bool required: coverPage.required
46  readonly property alias sessionToStart: loginList.currentSession
47 
48  property rect inputMethodRect
49 
50  signal selected(int index)
51  signal responded(string response)
52  signal tease()
53  signal emergencyCall() // unused
54 
55  function notifyAuthenticationFailed() {
56  loginList.showError();
57  }
58 
59  function forceShow() {
60  // Nothing to do, we are always fully shown
61  }
62 
63  function tryToUnlock(toTheRight) {
64  if (root.locked) {
65  coverPage.show();
66  loginList.tryToUnlock();
67  return false;
68  } else {
69  var coverChanged = coverPage.shown;
70  if (toTheRight) {
71  coverPage.hideRight();
72  } else {
73  coverPage.hide();
74  }
75  return coverChanged;
76  }
77  }
78 
79  function hide() {
80  coverPage.hide();
81  }
82 
83  function showFakePassword() {
84  loginList.showFakePassword();
85  }
86 
87  Rectangle {
88  anchors.fill: parent
89  color: "black"
90  opacity: coverPage.showProgress * 0.8
91  }
92 
93  CoverPage {
94  id: coverPage
95  objectName: "coverPage"
96  height: parent.height
97  width: parent.width
98  draggable: !root.locked && !root.waiting
99  state: "LoginList"
100  blurAreaHeight: loginList.highlightedHeight + units.gu(4.5)
101  blurAreaWidth: loginList.width + units.gu(3)
102  blurAreaX: loginList.x - units.gu(1.5)
103  blurAreaY: loginList.boxVerticalOffset + loginList.y - units.gu(3)
104 
105  // Darken background to match CoverPage
106  Rectangle {
107  objectName: "lockscreenShade"
108  anchors.fill: parent
109  color: "black"
110  opacity: root.hasCustomBackground ? 0.1 : 0
111  }
112 
113  infographics {
114  anchors.topMargin: parent.height * 0.125
115  anchors.bottomMargin: parent.height * 0.125
116  anchors.leftMargin: loginList.x + loginList.width
117  }
118 
119  onTease: root.tease()
120 
121  onShowProgressChanged: {
122  if (showProgress === 0 && !root.locked) {
123  root.responded("");
124  }
125  }
126 
127  LoginList {
128  id: loginList
129  objectName: "loginList"
130 
131  width: units.gu(40)
132  anchors {
133  left: parent.left
134  leftMargin: Math.min(parent.width * 0.16, units.gu(20))
135  top: parent.top
136  bottom: parent.bottom
137  }
138 
139  boxVerticalOffset: (height - highlightedHeight -
140  inputMethodRect.height) / 2
141  Behavior on boxVerticalOffset { LomiriNumberAnimation {} }
142 
143  model: root.userModel
144  onResponded: root.responded(response)
145  onSelected: root.selected(index)
146  onSessionChooserButtonClicked: parent.state = "SessionsList"
147  onCurrentIndexChanged: setCurrentSession()
148 
149  Keys.forwardTo: [sessionChooserLoader.item]
150 
151  Component.onCompleted: setCurrentSession()
152 
153  function setCurrentSession() {
154  currentSession = LightDMService.users.data(currentIndex, LightDMService.userRoles.SessionRole);
155  }
156  }
157 
158  Loader {
159  id: sessionChooserLoader
160 
161  height: loginList.height
162  width: loginList.width
163 
164  anchors {
165  left: parent.left
166  leftMargin: Math.min(parent.width * 0.16, units.gu(20))
167  top: parent.top
168  }
169 
170  active: false
171 
172  onLoaded: sessionChooserLoader.item.forceActiveFocus();
173  onActiveChanged: {
174  if (!active) return;
175  item.updateHighlight(loginList.currentSession);
176  }
177 
178  Connections {
179  target: sessionChooserLoader.item
180  onSessionSelected: loginList.currentSession = sessionKey
181  onShowLoginList: {
182  coverPage.state = "LoginList"
183  loginList.tryToUnlock();
184  }
185  ignoreUnknownSignals: true
186  }
187  }
188 
189  // Use an AbstractButton due to icon limitations with Button
190  AbstractButton {
191  id: sessionChooser
192  objectName: "sessionChooserButton"
193 
194  readonly property url icon: LightDMService.sessions.iconUrl(loginList.currentSession)
195 
196  visible: LightDMService.sessions.count > 1 &&
197  !LightDMService.users.data(loginList.currentUserIndex, LightDMService.userRoles.LoggedInRole)
198 
199  height: units.gu(3.5)
200  width: units.gu(3.5)
201 
202  activeFocusOnTab: true
203  anchors {
204  right: parent.right
205  rightMargin: units.gu(2)
206 
207  bottom: parent.bottom
208  bottomMargin: units.gu(1.5)
209  }
210 
211  Rectangle {
212  id: badgeHighlight
213 
214  anchors.fill: parent
215  visible: parent.activeFocus
216  color: "transparent"
217  border.color: theme.palette.normal.focus
218  border.width: units.dp(1)
219  radius: 3
220  }
221 
222  Icon {
223  id: badge
224  anchors.fill: parent
225  anchors.margins: units.dp(3)
226  keyColor: "#ffffff" // icon providers give us white icons
227  color: theme.palette.normal.raisedSecondaryText
228  source: sessionChooser.icon
229  }
230 
231  Keys.onReturnPressed: {
232  parent.state = "SessionsList";
233  }
234 
235  onClicked: {
236  parent.state = "SessionsList";
237  }
238 
239  // Refresh the icon path if looking at different places at runtime
240  // this is mainly for testing
241  Connections {
242  target: LightDMService.sessions
243  onIconSearchDirectoriesChanged: {
244  badge.source = LightDMService.sessions.iconUrl(root.currentSession)
245  }
246  }
247  }
248 
249  states: [
250  State {
251  name: "SessionsList"
252  PropertyChanges { target: loginList; opacity: 0 }
253  PropertyChanges { target: sessionChooserLoader;
254  active: true;
255  opacity: 1
256  source: "SessionsList.qml"
257  }
258  },
259 
260  State {
261  name: "LoginList"
262  PropertyChanges { target: loginList; opacity: 1 }
263  PropertyChanges { target: sessionChooserLoader;
264  active: false;
265  opacity: 0
266  source: "";
267  }
268  }
269  ]
270 
271  transitions: [
272  Transition {
273  from: "*"
274  to: "*"
275  LomiriNumberAnimation {
276  property: "opacity";
277  }
278  }
279  ]
280  }
281 }