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