Lomiri
LoginList.qml
1/*
2 * Copyright (C) 2013-2016 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.12
18import QtGraphicalEffects 1.0
19import Lomiri.Components 1.3
20import "../Components"
21import "." 0.1
22
23StyledItem {
24 id: root
25 focus: true
26
27 property alias model: userList.model
28 property alias alphanumeric: promptList.alphanumeric
29 property alias hasKeyboard: promptList.hasKeyboard
30 property int currentIndex
31 property bool locked
32 property bool waiting
33 property alias boxVerticalOffset: highlightItem.y
34 property string _realName
35 property bool isLandscape
36 property string usageMode
37
38 readonly property int numAboveBelow: 4
39 readonly property int cellHeight: units.gu(5)
40 readonly property int highlightedHeight: highlightItem.height
41 readonly property int moveDuration: LomiriAnimation.FastDuration
42 property string currentSession // Initially set by LightDM
43 readonly property string currentUser: userList.currentItem.username
44
45 readonly property alias currentUserIndex: userList.currentIndex
46
47 signal responded(string response)
48 signal selected(int index)
49 signal sessionChooserButtonClicked()
50
51 function tryToUnlock() {
52 promptList.forceActiveFocus();
53 }
54
55 function showError() {
56 promptList.loginError = true;
57 wrongPasswordAnimation.start();
58 }
59
60 function showFakePassword() {
61 promptList.interactive = false;
62 promptList.showFakePassword();
63 }
64
65 theme: ThemeSettings {
66 name: "Lomiri.Components.Themes.Ambiance"
67 }
68
69 Keys.onUpPressed: {
70 if (currentIndex > 0) {
71 selected(currentIndex - 1);
72 }
73 event.accepted = true;
74 }
75 Keys.onDownPressed: {
76 if (currentIndex + 1 < model.count) {
77 selected(currentIndex + 1);
78 }
79 event.accepted = true;
80 }
81 Keys.onEscapePressed: {
82 selected(currentIndex);
83 event.accepted = true;
84 }
85
86 onCurrentIndexChanged: {
87 userList.currentIndex = currentIndex;
88 promptList.loginError = false;
89 }
90
91 LoginAreaContainer {
92 id: highlightItem
93 objectName: "highlightItem"
94 anchors {
95 left: parent.left
96 leftMargin: units.gu(2)
97 right: parent.right
98 rightMargin: units.gu(2)
99 }
100
101 height: Math.max(units.gu(15), promptList.height + units.gu(8))
102 Behavior on height { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
103 }
104
105 ListView {
106 id: userList
107 objectName: "userList"
108
109 anchors.fill: parent
110 anchors.leftMargin: units.gu(2)
111 anchors.rightMargin: units.gu(2)
112
113 preferredHighlightBegin: highlightItem.y + units.gu(1.5)
114 preferredHighlightEnd: highlightItem.y + units.gu(1.5)
115 highlightRangeMode: ListView.StrictlyEnforceRange
116 highlightMoveDuration: root.moveDuration
117 interactive: count > 1
118
119 readonly property bool movingInternally: moveTimer.running || userList.moving
120
121 onMovingChanged: if (!moving) root.selected(currentIndex)
122
123 onCurrentIndexChanged: {
124 moveTimer.start();
125 }
126
127 delegate: Item {
128 width: userList.width
129 height: root.cellHeight
130
131 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
132 readonly property bool aboveCurrent: (userList.currentIndex > 0 && index < 0) || (userList.currentIndex >= 0 && index < userList.currentIndex)
133 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
134 readonly property string userSession: session
135 readonly property string username: name
136
137 opacity: {
138 // The goal here is to make names less and less opaque as they
139 // leave the highlight area. Can't simply use index, because
140 // that can change quickly if the user clicks at edges of
141 // list. So we use actual pixel distance.
142 var highlightDist = 0;
143 var realY = y - userList.contentY;
144 if (belowHighlight)
145 realY += belowOffset;
146 if (realY + height <= highlightItem.y)
147 highlightDist = realY + height - highlightItem.y;
148 else if (realY >= highlightItem.y + root.highlightedHeight)
149 highlightDist = realY - highlightItem.y - root.highlightedHeight;
150 else
151 return 1;
152 return 1 - Math.min(1, (Math.abs(highlightDist) + root.cellHeight) / ((root.numAboveBelow + 1) * root.cellHeight))
153 }
154
155 Row {
156 spacing: units.gu(1)
157// visible: userList.count != 1 // HACK Hide username label until someone sorts out the anchoring with the keyboard-dismiss animation, Work around https://github.com/ubports/unity8/issues/185
158
159 anchors {
160 leftMargin: units.gu(2)
161 rightMargin: units.gu(2)
162 horizontalCenter: parent.horizontalCenter
163 bottom: parent.top
164 // Add an offset to bottomMargin for any items below the highlight
165 bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : parent.aboveCurrent ? -units.gu(5) : 0))
166 }
167
168 Rectangle {
169 id: activeIndicator
170 anchors.verticalCenter: parent.verticalCenter
171 color: theme.palette.normal.raised
172 visible: userList.count > 1 && loggedIn
173 height: visible ? units.gu(0.5) : 0
174 width: height
175 }
176
177 Icon {
178 id: userIcon
179 name: "account"
180 height: userList.currentIndex === index ? units.gu(4.5) : units.gu(3)
181 width: height
182 color: theme.palette.normal.raisedSecondaryText
183 anchors.verticalCenter: parent.verticalCenter
184 }
185
186 Column {
187 anchors.verticalCenter: parent.verticalCenter
188 spacing: units.gu(0.25)
189
190 FadingLabel {
191 objectName: "username" + index
192
193 text: userList.currentIndex === index
194 && name === "*other"
195 && LightDMService.greeter.authenticationUser !== ""
196 ? LightDMService.greeter.authenticationUser : realName
197 color: userList.currentIndex !== index ? theme.palette.normal.raised
198 : theme.palette.normal.raisedSecondaryText
199 font.weight: userList.currentIndex === index ? Font.Normal : Font.Light
200 font.pointSize: units.gu(2)
201
202 width: highlightItem.width
203 && contentWidth > highlightItem.width - userIcon.width - units.gu(4)
204 ? highlightItem.width - userIcon.width - units.gu(4)
205 : contentWidth
206
207 Component.onCompleted: _realName = realName
208
209 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
210 }
211
212 Row {
213 spacing: units.gu(1)
214
215 FadingLabel {
216 text: root.alphanumeric ? "Login with password" : "Login with pin"
217 color: theme.palette.normal.raisedSecondaryText
218 visible: userList.currentIndex === index && false
219 font.weight: Font.Light
220 font.pointSize: units.gu(1.25)
221 }
222 }
223 }
224 }
225
226 MouseArea {
227 anchors {
228 left: parent.left
229 right: parent.right
230 top: parent.top
231 // Add an offset to topMargin for any items below the highlight
232 topMargin: parent.belowHighlight ? parent.belowOffset : parent.aboveCurrent ? -units.gu(5) : 0
233 }
234 height: parent.height
235 enabled: userList.currentIndex !== index && parent.opacity > 0
236 onClicked: root.selected(index)
237
238 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
239 }
240 }
241
242 // This is needed because ListView.moving is not true if the ListView
243 // moves because of an internal event (e.g. currentIndex has changed)
244 Timer {
245 id: moveTimer
246 running: false
247 repeat: false
248 interval: root.moveDuration
249 }
250 }
251
252 PromptList {
253 id: promptList
254 objectName: "promptList"
255 anchors {
256 bottom: highlightItem.bottom
257 horizontalCenter: highlightItem.horizontalCenter
258 margins: units.gu(2)
259 }
260 defaultPromptWidth: highlightItem.width - anchors.margins * 2
261 maxHeight: root.height * 0.66
262 isLandscape: root.isLandscape
263 usageMode: root.usageMode
264 width: root.width
265 focus: true
266
267 onClicked: {
268 interactive = false;
269 if (root.locked) {
270 root.selected(currentIndex);
271 } else {
272 root.responded("");
273 }
274 }
275 onResponded: {
276 interactive = false;
277 root.responded(text);
278 }
279 onCanceled: {
280 interactive = false;
281 root.selected(currentIndex);
282 }
283
284 Connections {
285 target: LightDMService.prompts
286 onModelReset: promptList.interactive = true
287 }
288 }
289
290 WrongPasswordAnimation {
291 id: wrongPasswordAnimation
292 objectName: "wrongPasswordAnimation"
293 target: promptList
294 }
295}