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.4
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
36 readonly property int numAboveBelow: 4
37 readonly property int cellHeight: units.gu(5)
38 readonly property int highlightedHeight: highlightItem.height
39 readonly property int moveDuration: LomiriAnimation.FastDuration
40 property string currentSession // Initially set by LightDM
41 readonly property string currentUser: userList.currentItem.username
42
43 readonly property alias currentUserIndex: userList.currentIndex
44
45 signal responded(string response)
46 signal selected(int index)
47 signal sessionChooserButtonClicked()
48
49 function tryToUnlock() {
50 promptList.forceActiveFocus();
51 }
52
53 function showError() {
54 promptList.loginError = true;
55 wrongPasswordAnimation.start();
56 }
57
58 function showFakePassword() {
59 promptList.interactive = false;
60 promptList.showFakePassword();
61 }
62
63 theme: ThemeSettings {
64 name: "Lomiri.Components.Themes.Ambiance"
65 }
66
67 Keys.onUpPressed: {
68 if (currentIndex > 0) {
69 selected(currentIndex - 1);
70 }
71 event.accepted = true;
72 }
73 Keys.onDownPressed: {
74 if (currentIndex + 1 < model.count) {
75 selected(currentIndex + 1);
76 }
77 event.accepted = true;
78 }
79 Keys.onEscapePressed: {
80 selected(currentIndex);
81 event.accepted = true;
82 }
83
84 onCurrentIndexChanged: {
85 userList.currentIndex = currentIndex;
86 promptList.loginError = false;
87 }
88
89 LoginAreaContainer {
90 id: highlightItem
91 objectName: "highlightItem"
92 anchors {
93 left: parent.left
94 leftMargin: units.gu(2)
95 right: parent.right
96 rightMargin: units.gu(2)
97 }
98
99 height: Math.max(units.gu(15), promptList.height + units.gu(8))
100 Behavior on height { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
101 }
102
103 ListView {
104 id: userList
105 objectName: "userList"
106
107 anchors.fill: parent
108 anchors.leftMargin: units.gu(2)
109 anchors.rightMargin: units.gu(2)
110
111 preferredHighlightBegin: highlightItem.y + units.gu(1.5)
112 preferredHighlightEnd: highlightItem.y + units.gu(1.5)
113 highlightRangeMode: ListView.StrictlyEnforceRange
114 highlightMoveDuration: root.moveDuration
115 interactive: count > 1
116
117 readonly property bool movingInternally: moveTimer.running || userList.moving
118
119 onMovingChanged: if (!moving) root.selected(currentIndex)
120
121 onCurrentIndexChanged: {
122 moveTimer.start();
123 }
124
125 delegate: Item {
126 width: userList.width
127 height: root.cellHeight
128
129 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
130 readonly property bool aboveCurrent: (userList.currentIndex > 0 && index < 0) || (userList.currentIndex >= 0 && index < userList.currentIndex)
131 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
132 readonly property string userSession: session
133 readonly property string username: name
134
135 opacity: {
136 // The goal here is to make names less and less opaque as they
137 // leave the highlight area. Can't simply use index, because
138 // that can change quickly if the user clicks at edges of
139 // list. So we use actual pixel distance.
140 var highlightDist = 0;
141 var realY = y - userList.contentY;
142 if (belowHighlight)
143 realY += belowOffset;
144 if (realY + height <= highlightItem.y)
145 highlightDist = realY + height - highlightItem.y;
146 else if (realY >= highlightItem.y + root.highlightedHeight)
147 highlightDist = realY - highlightItem.y - root.highlightedHeight;
148 else
149 return 1;
150 return 1 - Math.min(1, (Math.abs(highlightDist) + root.cellHeight) / ((root.numAboveBelow + 1) * root.cellHeight))
151 }
152
153 Row {
154 spacing: units.gu(1)
155// 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
156
157 anchors {
158 leftMargin: units.gu(2)
159 rightMargin: units.gu(2)
160 horizontalCenter: parent.horizontalCenter
161 bottom: parent.top
162 // Add an offset to bottomMargin for any items below the highlight
163 bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : parent.aboveCurrent ? -units.gu(5) : 0))
164 }
165
166 Rectangle {
167 id: activeIndicator
168 anchors.verticalCenter: parent.verticalCenter
169 color: theme.palette.normal.raised
170 visible: userList.count > 1 && loggedIn
171 height: visible ? units.gu(0.5) : 0
172 width: height
173 }
174
175 Icon {
176 id: userIcon
177 name: "account"
178 height: userList.currentIndex === index ? units.gu(4.5) : units.gu(3)
179 width: height
180 color: theme.palette.normal.raisedSecondaryText
181 anchors.verticalCenter: parent.verticalCenter
182 }
183
184 Column {
185 anchors.verticalCenter: parent.verticalCenter
186 spacing: units.gu(0.25)
187
188 FadingLabel {
189 objectName: "username" + index
190
191 text: userList.currentIndex === index
192 && name === "*other"
193 && LightDMService.greeter.authenticationUser !== ""
194 ? LightDMService.greeter.authenticationUser : realName
195 color: userList.currentIndex !== index ? theme.palette.normal.raised
196 : theme.palette.normal.raisedSecondaryText
197 font.weight: userList.currentIndex === index ? Font.Normal : Font.Light
198 font.pointSize: units.gu(2)
199
200 width: highlightItem.width
201 && contentWidth > highlightItem.width - userIcon.width - units.gu(4)
202 ? highlightItem.width - userIcon.width - units.gu(4)
203 : contentWidth
204
205 Component.onCompleted: _realName = realName
206
207 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
208 }
209
210 Row {
211 spacing: units.gu(1)
212
213 FadingLabel {
214 text: root.alphanumeric ? "Login with password" : "Login with pin"
215 color: theme.palette.normal.raisedSecondaryText
216 visible: userList.currentIndex === index && false
217 font.weight: Font.Light
218 font.pointSize: units.gu(1.25)
219 }
220 }
221 }
222 }
223
224 MouseArea {
225 anchors {
226 left: parent.left
227 right: parent.right
228 top: parent.top
229 // Add an offset to topMargin for any items below the highlight
230 topMargin: parent.belowHighlight ? parent.belowOffset : parent.aboveCurrent ? -units.gu(5) : 0
231 }
232 height: parent.height
233 enabled: userList.currentIndex !== index && parent.opacity > 0
234 onClicked: root.selected(index)
235
236 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
237 }
238 }
239
240 // This is needed because ListView.moving is not true if the ListView
241 // moves because of an internal event (e.g. currentIndex has changed)
242 Timer {
243 id: moveTimer
244 running: false
245 repeat: false
246 interval: root.moveDuration
247 }
248 }
249
250 PromptList {
251 id: promptList
252 objectName: "promptList"
253 anchors {
254 bottom: highlightItem.bottom
255 horizontalCenter: highlightItem.horizontalCenter
256 margins: units.gu(2)
257 }
258 width: highlightItem.width - anchors.margins * 2
259
260 focus: true
261
262 onClicked: {
263 interactive = false;
264 if (root.locked) {
265 root.selected(currentIndex);
266 } else {
267 root.responded("");
268 }
269 }
270 onResponded: {
271 interactive = false;
272 root.responded(text);
273 }
274 onCanceled: {
275 interactive = false;
276 root.selected(currentIndex);
277 }
278
279 Connections {
280 target: LightDMService.prompts
281 onModelReset: promptList.interactive = true
282 }
283 }
284
285 WrongPasswordAnimation {
286 id: wrongPasswordAnimation
287 objectName: "wrongPasswordAnimation"
288 target: promptList
289 }
290}