@@ -0,0 +1,205 @@
1+/*
2+ * Copyright (C) 2015 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+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.2
19+import ".." as LocalComponents
20+import "../../Components" as UnityComponents
21+
22+/**
23+ * See the main passwd-type page for an explanation of why we don't actually
24+ * directly set the password here.
25+ */
26+
27+LocalComponents.Page {
28+ id: passwdSetPage
29+ objectName: "passwdSetPage"
30+ forwardButtonSourceComponent: forwardButton
31+ customTitle: true
32+ title: confirmPhase ? i18n.tr("Confirm password") : i18n.tr("Enter password")
33+ backButtonText: i18n.tr("Cancel")
34+
35+ property alias password: passwordField.text
36+ readonly property int passwordScore: scorePassword(password)
37+ property bool confirmPhase: false
38+
39+ // If we are entering this page, clear any saved password and get focus
40+ onEnabledChanged: if (enabled) password = ""
41+
42+ Component.onCompleted: {
43+ setInfo()
44+ passwordField.forceActiveFocus()
45+ }
46+
47+ function confirm() {
48+ root.password = password
49+ confirmTimer.start()
50+ }
51+
52+ Timer {
53+ id: confirmTimer
54+ interval: UbuntuAnimation.SnapDuration
55+ onTriggered: {
56+ confirmPhase = true
57+ password = ""
58+ passwordField.forceActiveFocus()
59+ }
60+ }
61+
62+ function setError(text) {
63+ if (!!text) {
64+ infoLabel.hasError = true
65+ infoLabel.text = text
66+ }
67+ }
68+
69+ function setInfo(text) {
70+ infoLabel.hasError = false
71+ if (!!text) {
72+ infoLabel.text = text
73+ } else {
74+ infoLabel.text = i18n.tr("Enter at least 4 characters")
75+ }
76+ }
77+
78+ function scorePassword(pass) {
79+ var score = 0;
80+ if (!pass)
81+ return score;
82+
83+ // award every unique letter until 5 repetitions
84+ var letters = Object();
85+ for (var i=0; i<pass.length; i++) {
86+ letters[pass[i]] = (letters[pass[i]] || 0) + 1;
87+ score += 5.0 / letters[pass[i]];
88+ }
89+
90+ // bonus points for mixing it up
91+ var variations = {
92+ digits: /\d/.test(pass),
93+ lower: /[a-z]/.test(pass),
94+ upper: /[A-Z]/.test(pass),
95+ nonWords: /\W/.test(pass),
96+ }
97+
98+ var variationCount = 0;
99+ for (var check in variations) {
100+ variationCount += (variations[check] === true) ? 1 : 0;
101+ }
102+ score += (variationCount - 1) * 10;
103+
104+ return parseInt(score);
105+ }
106+
107+ Item {
108+ id: column
109+ anchors.fill: content
110+
111+ Label {
112+ id: infoLabel
113+ property bool hasError: false
114+ anchors {
115+ left: parent.left
116+ right: parent.right
117+ }
118+ wrapMode: Text.Wrap
119+ color: hasError ? "#e14141": "#525252"
120+ }
121+
122+ TextField {
123+ id: passwordField
124+ anchors {
125+ left: parent.left
126+ right: parent.right
127+ top: infoLabel.bottom
128+ topMargin: units.gu(1)
129+ }
130+ echoMode: TextInput.Password
131+ onTextChanged: {
132+ if (confirmPhase) {
133+ if (password !== root.password) {
134+ setError(i18n.tr("Passwords do not match"))
135+ } else {
136+ setInfo(i18n.tr("Passwords match"))
137+ }
138+ }
139+ }
140+ }
141+
142+ Rectangle {
143+ id: passwordStrengthMeter
144+ anchors {
145+ left: parent.left
146+ right: parent.right
147+ top: passwordField.bottom
148+ topMargin: units.gu(1)
149+ }
150+ width: parent.width
151+ height: units.gu(1)
152+ color: {
153+ if (passwordScore > 80)
154+ return "green";
155+ if (passwordScore > 60)
156+ return "orange";
157+ if (passwordScore >= 30)
158+ return "red";
159+
160+ return "red";
161+ }
162+ visible: password.length > 0
163+ }
164+
165+ Label {
166+ id: passwordStrengthInfo
167+ anchors {
168+ left: parent.left
169+ right: parent.right
170+ top: passwordStrengthMeter.bottom
171+ topMargin: units.gu(1)
172+ }
173+ wrapMode: Text.Wrap
174+ text: {
175+ if (passwordScore > 80)
176+ return i18n.tr("Strong password");
177+ else if (passwordScore > 60)
178+ return i18n.tr("Medium password")
179+ if (passwordScore >= 30)
180+ return i18n.tr("Weak password");
181+
182+ return i18n.tr("Very weak password");
183+ }
184+ color: "#888888"
185+ fontSize: "x-small"
186+ font.weight: Font.Light
187+ visible: password.length > 0
188+ }
189+ }
190+
191+ Component {
192+ id: forwardButton
193+ LocalComponents.StackButton {
194+ enabled: confirmPhase ? password === root.password : password.length >= 4 // TODO set more sensible restrictions for the length?
195+ text: i18n.tr("OK")
196+ onClicked: {
197+ if (confirmPhase) {
198+ pageStack.next()
199+ } else {
200+ passwdSetPage.confirm()
201+ }
202+ }
203+ }
204+ }
205+}