Lomiri
CoverPage.qml
1 /*
2  * Copyright (C) 2013-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 QtGraphicalEffects 1.12
20 import Lomiri.Components 1.3
21 import Lomiri.Gestures 0.1
22 import "../Components"
23 
24 import BatteryMonitor 1.0
25 
26 Showable {
27  id: root
28 
29  property real dragHandleLeftMargin
30  property real launcherOffset
31  property alias background: greeterBackground.source
32  property alias backgroundSourceSize: greeterBackground.sourceSize
33  property alias hasCustomBackground: backgroundShade.visible
34  property alias backgroundShadeOpacity: backgroundShade.opacity
35  property real panelHeight
36  property var infographicModel
37  property bool draggable: true
38 
39  property bool showInfographic: false
40  property real infographicsLeftMargin: 0
41  property real infographicsTopMargin: 0
42  property real infographicsRightMargin: 0
43  property real infographicsBottomMargin: 0
44 
45  readonly property real showProgress: MathUtils.clamp((width - Math.abs(x + launcherOffset)) / width, 0, 1)
46 
47  signal tease()
48  signal clicked()
49 
50  function hideRight() {
51  d.forceRightOnNextHideAnimation = true;
52  hide();
53  }
54 
55  function showErrorMessage(msg) {
56  d.errorMessage = msg;
57  showLabelAnimation.start();
58  errorMessageAnimation.start();
59  }
60 
61  QtObject {
62  id: d
63  property bool forceRightOnNextHideAnimation: false
64  property string errorMessage
65  }
66 
67  prepareToHide: function () {
68  hideTranslation.from = root.x + translation.x
69  hideTranslation.to = root.x > 0 || d.forceRightOnNextHideAnimation ? root.width : -root.width;
70  d.forceRightOnNextHideAnimation = false;
71  }
72 
73  // We don't directly bind "x" because that's owned by the DragHandle. So
74  // instead, we can get a little extra horizontal push by using transforms.
75  transform: Translate { id: translation; x: root.draggable ? launcherOffset : 0 }
76 
77  // Eat events elsewhere on the coverpage, except mouse clicks which we pass
78  // up (they are used in the NarrowView to hide the cover page)
79  MouseArea {
80  anchors.fill: parent
81  onClicked: root.clicked()
82 
83  MultiPointTouchArea {
84  anchors.fill: parent
85  mouseEnabled: false
86  }
87  }
88 
89  Rectangle {
90  // In case background fails to load
91  id: backgroundBackup
92  anchors.fill: parent
93  color: "black"
94  }
95 
96  Wallpaper {
97  id: greeterBackground
98  objectName: "greeterBackground"
99  anchors {
100  fill: parent
101  }
102  }
103 
104  // Darkens wallpaper so that we can read text on it and see infographic
105  Rectangle {
106  id: backgroundShade
107  objectName: "backgroundShade"
108  anchors.fill: parent
109  color: "black"
110  visible: false
111  }
112 
113  Item {
114  id: infographicsArea
115 
116  anchors {
117  leftMargin: root.infographicsLeftMargin
118  topMargin: root.infographicsTopMargin ? root.infographicsTopMargin : root.panelHeight
119  rightMargin: root.infographicsRightMargin
120  bottomMargin: root.infographicsBottomMargin
121  top: parent.top
122  bottom: parent.bottom
123  left: parent.left
124  right: parent.right
125  }
126  }
127 
128  Loader {
129  id: infographicsLoader
130  objectName: "infographicsLoader"
131  active: root.showInfographic && infographicsArea.width > units.gu(32)
132  anchors.fill: infographicsArea
133 
134  sourceComponent:Infographics {
135  id: infographics
136  objectName: "infographics"
137  model: root.infographicModel
138  clip: true // clip large data bubbles
139  }
140  }
141 
142  Label {
143  id: chargingHint
144  anchors.horizontalCenter: parent.horizontalCenter
145  anchors.bottom: parent.bottom
146  anchors.bottomMargin: units.gu(5)
147  text: {
148  var hourText = "";
149  var minuteText = "";
150  var seconds = BatteryMonitor.timeToFull;
151  var minutes = Math.floor(seconds / 60 % 60);
152  var hours = Math.floor(seconds / 60 / 60);
153 
154  if (hours > 0) {
155  hourText = i18n.tr("%1 hour", "%1 hours", hours).arg(hours)
156  }
157  if (minutes > 0) {
158  minuteText = i18n.tr("%1 minute", "%1 minutes", minutes).arg(minutes)
159  }
160  if (hours == 0 && minutes == 0) {
161  var state = BatteryMonitor.state();
162  if (state == BatteryMonitor.FULLY_CHARGED) return i18n.tr("Fully charged")
163  }
164  if (hourText != "" && minuteText != "") {
165  // Translators: String like "1 hour, 2 minutes until full"
166  return i18n.tr("%1, %2 until full").arg(hourText).arg(minuteText);
167  } else if (hourText == "" || minuteText == "") {
168  // Translators: String like "32 minutes until full" or "3 hours until full"
169  return i18n.tr("%1 until full").arg((hourText != "" ? hourText : minuteText))
170  }
171  }
172  color: "white"
173  font.weight: Font.Light
174  visible: BatteryMonitor.charging
175  }
176 
177  Label {
178  id: swipeHint
179  objectName: "swipeHint"
180  property real baseOpacity: 0.5
181  opacity: 0.0
182  anchors.horizontalCenter: parent.horizontalCenter
183  anchors.bottom: parent.bottom
184  anchors.bottomMargin: units.gu(5)
185  text: "《 " + (d.errorMessage ? d.errorMessage : i18n.tr("Unlock")) + " 》"
186  color: "white"
187  font.weight: Font.Light
188  visible: !BatteryMonitor.charging
189 
190  readonly property var opacityAnimation: showLabelAnimation // for testing
191 
192  SequentialAnimation on opacity {
193  id: showLabelAnimation
194  running: false
195  loops: 2
196 
197  StandardAnimation {
198  from: 0.0
199  to: swipeHint.baseOpacity
200  duration: LomiriAnimation.SleepyDuration
201  }
202  PauseAnimation { duration: LomiriAnimation.BriskDuration }
203  StandardAnimation {
204  from: swipeHint.baseOpacity
205  to: 0.0
206  duration: LomiriAnimation.SleepyDuration
207  }
208 
209  onRunningChanged: {
210  if (!running)
211  d.errorMessage = "";
212  }
213  }
214  }
215 
216  WrongPasswordAnimation {
217  id: errorMessageAnimation
218  objectName: "errorMessageAnimation"
219  target: swipeHint
220  }
221 
222  DragHandle {
223  id: dragHandle
224  objectName: "coverPageDragHandle"
225  anchors.fill: parent
226  anchors.leftMargin: root.dragHandleLeftMargin
227  enabled: root.draggable
228  direction: Direction.Horizontal
229 
230  onPressedChanged: {
231  if (pressed) {
232  root.tease();
233  showLabelAnimation.start();
234  }
235  }
236  }
237 
238  // right side shadow
239  Image {
240  anchors.left: parent.right
241  anchors.top: parent.top
242  anchors.bottom: parent.bottom
243  fillMode: Image.Tile
244  source: "../graphics/dropshadow_right.png"
245  }
246 
247  // left side shadow
248  Image {
249  anchors.right: parent.left
250  anchors.top: parent.top
251  anchors.bottom: parent.bottom
252  fillMode: Image.Tile
253  source: "../graphics/dropshadow_left.png"
254  }
255 
256  Binding {
257  id: positionLock
258 
259  property bool enabled: false
260  onEnabledChanged: {
261  if (enabled === __enabled) {
262  return;
263  }
264 
265  if (enabled) {
266  if (root.x > 0) {
267  value = Qt.binding(function() { return root.width; })
268  } else {
269  value = Qt.binding(function() { return -root.width; })
270  }
271  }
272 
273  __enabled = enabled;
274  }
275 
276  property bool __enabled: false
277 
278  target: root
279  when: __enabled
280  property: "x"
281  }
282 
283  hideAnimation: SequentialAnimation {
284  id: hideAnimation
285  objectName: "hideAnimation"
286  property var target // unused, here to silence Showable warning
287  StandardAnimation {
288  id: hideTranslation
289  property: "x"
290  target: root
291  }
292  PropertyAction { target: root; property: "visible"; value: false }
293  PropertyAction { target: positionLock; property: "enabled"; value: true }
294  }
295 
296  showAnimation: SequentialAnimation {
297  id: showAnimation
298  objectName: "showAnimation"
299  property var target // unused, here to silence Showable warning
300  PropertyAction { target: root; property: "visible"; value: true }
301  PropertyAction { target: positionLock; property: "enabled"; value: false }
302  StandardAnimation {
303  property: "x"
304  target: root
305  to: 0
306  duration: LomiriAnimation.FastDuration
307  }
308  }
309 }