14
14
* limitations under the License.
15
15
*/
16
16
17
+
17
18
package io.element.android.features.lockscreen.impl.unlock
18
19
19
20
import androidx.compose.foundation.background
@@ -29,6 +30,7 @@ import androidx.compose.foundation.layout.fillMaxHeight
29
30
import androidx.compose.foundation.layout.fillMaxSize
30
31
import androidx.compose.foundation.layout.fillMaxWidth
31
32
import androidx.compose.foundation.layout.height
33
+ import androidx.compose.foundation.layout.imePadding
32
34
import androidx.compose.foundation.layout.padding
33
35
import androidx.compose.foundation.layout.size
34
36
import androidx.compose.foundation.layout.systemBarsPadding
@@ -37,19 +39,25 @@ import androidx.compose.material.icons.Icons
37
39
import androidx.compose.material.icons.filled.Lock
38
40
import androidx.compose.material3.MaterialTheme
39
41
import androidx.compose.runtime.Composable
42
+ import androidx.compose.runtime.LaunchedEffect
43
+ import androidx.compose.runtime.remember
40
44
import androidx.compose.ui.Alignment
41
45
import androidx.compose.ui.Modifier
46
+ import androidx.compose.ui.focus.FocusRequester
47
+ import androidx.compose.ui.focus.focusRequester
42
48
import androidx.compose.ui.res.pluralStringResource
43
49
import androidx.compose.ui.res.stringResource
44
50
import androidx.compose.ui.text.style.TextAlign
45
51
import androidx.compose.ui.tooling.preview.PreviewParameter
46
52
import androidx.compose.ui.unit.dp
47
53
import androidx.lifecycle.Lifecycle
48
54
import io.element.android.features.lockscreen.impl.R
55
+ import io.element.android.features.lockscreen.impl.components.PinEntryTextField
49
56
import io.element.android.features.lockscreen.impl.pin.model.PinDigit
50
57
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
51
58
import io.element.android.features.lockscreen.impl.unlock.keypad.PinKeypad
52
59
import io.element.android.libraries.architecture.Async
60
+ import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom
53
61
import io.element.android.libraries.designsystem.components.ProgressDialog
54
62
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
55
63
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
@@ -66,6 +74,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
66
74
@Composable
67
75
fun PinUnlockView (
68
76
state : PinUnlockState ,
77
+ isInAppUnlock : Boolean ,
69
78
modifier : Modifier = Modifier ,
70
79
) {
71
80
OnLifecycleEvent { _, event ->
@@ -75,56 +84,7 @@ fun PinUnlockView(
75
84
}
76
85
}
77
86
Surface (modifier) {
78
- BoxWithConstraints {
79
- val commonModifier = Modifier
80
- .fillMaxSize()
81
- .systemBarsPadding()
82
- .padding(all = 20 .dp)
83
-
84
- val header = @Composable {
85
- PinUnlockHeader (
86
- state = state,
87
- modifier = Modifier .padding(top = 60 .dp, bottom = 12 .dp)
88
- )
89
- }
90
- val footer = @Composable {
91
- PinUnlockFooter (
92
- modifier = Modifier .padding(top = 24 .dp),
93
- showBiometricUnlock = state.showBiometricUnlock,
94
- onUseBiometric = {
95
- state.eventSink(PinUnlockEvents .OnUseBiometric )
96
- },
97
- onForgotPin = {
98
- state.eventSink(PinUnlockEvents .OnForgetPin )
99
- },
100
- )
101
- }
102
- val content = @Composable { constraints: BoxWithConstraintsScope ->
103
- PinKeypad (
104
- onClick = {
105
- state.eventSink(PinUnlockEvents .OnPinKeypadPressed (it))
106
- },
107
- maxWidth = constraints.maxWidth,
108
- maxHeight = constraints.maxHeight,
109
- horizontalAlignment = Alignment .CenterHorizontally ,
110
- )
111
- }
112
- if (maxHeight < 600 .dp) {
113
- PinUnlockCompactView (
114
- header = header,
115
- footer = footer,
116
- content = content,
117
- modifier = commonModifier,
118
- )
119
- } else {
120
- PinUnlockExpandedView (
121
- header = header,
122
- footer = footer,
123
- content = content,
124
- modifier = commonModifier,
125
- )
126
- }
127
- }
87
+ PinUnlockPage (state = state, isInAppUnlock = isInAppUnlock)
128
88
if (state.showSignOutPrompt) {
129
89
SignOutPrompt (
130
90
isCancellable = state.isSignOutPromptCancellable,
@@ -144,6 +104,86 @@ fun PinUnlockView(
144
104
}
145
105
}
146
106
107
+ @Composable
108
+ private fun PinUnlockPage (
109
+ state : PinUnlockState ,
110
+ isInAppUnlock : Boolean ,
111
+ modifier : Modifier = Modifier
112
+ ) {
113
+ BoxWithConstraints {
114
+ val commonModifier = modifier
115
+ .fillMaxSize()
116
+ .systemBarsPadding()
117
+ .imePadding()
118
+ .padding(all = 20 .dp)
119
+
120
+ val header = @Composable {
121
+ PinUnlockHeader (
122
+ state = state,
123
+ isInAppUnlock = isInAppUnlock,
124
+ modifier = Modifier .padding(top = 60 .dp)
125
+ )
126
+ }
127
+ val footer = @Composable {
128
+ PinUnlockFooter (
129
+ modifier = Modifier .padding(top = 24 .dp),
130
+ showBiometricUnlock = state.showBiometricUnlock,
131
+ onUseBiometric = {
132
+ state.eventSink(PinUnlockEvents .OnUseBiometric )
133
+ },
134
+ onForgotPin = {
135
+ state.eventSink(PinUnlockEvents .OnForgetPin )
136
+ },
137
+ )
138
+ }
139
+ val content = @Composable { constraints: BoxWithConstraintsScope ->
140
+ if (isInAppUnlock) {
141
+ val pinEntry = state.pinEntry.dataOrNull()
142
+ if (pinEntry != null ) {
143
+ val focusRequester = remember { FocusRequester () }
144
+ LaunchedEffect (Unit ) {
145
+ focusRequester.requestFocus()
146
+ }
147
+ PinEntryTextField (
148
+ pinEntry = pinEntry,
149
+ isSecured = true ,
150
+ onValueChange = {
151
+ state.eventSink(PinUnlockEvents .OnPinEntryChanged (it))
152
+ },
153
+ modifier = Modifier
154
+ .focusRequester(focusRequester)
155
+ .fillMaxWidth()
156
+ )
157
+ }
158
+ } else {
159
+ PinKeypad (
160
+ onClick = {
161
+ state.eventSink(PinUnlockEvents .OnPinKeypadPressed (it))
162
+ },
163
+ maxWidth = constraints.maxWidth,
164
+ maxHeight = constraints.maxHeight,
165
+ horizontalAlignment = Alignment .CenterHorizontally ,
166
+ )
167
+ }
168
+ }
169
+ if (maxHeight < 600 .dp) {
170
+ PinUnlockCompactView (
171
+ header = header,
172
+ footer = footer,
173
+ content = content,
174
+ modifier = commonModifier,
175
+ )
176
+ } else {
177
+ PinUnlockExpandedView (
178
+ header = header,
179
+ footer = footer,
180
+ content = content,
181
+ modifier = commonModifier,
182
+ )
183
+ }
184
+ }
185
+ }
186
+
147
187
@Composable
148
188
private fun SignOutPrompt (
149
189
isCancellable : Boolean ,
@@ -248,16 +288,21 @@ private fun PinDot(
248
288
@Composable
249
289
private fun PinUnlockHeader (
250
290
state : PinUnlockState ,
291
+ isInAppUnlock : Boolean ,
251
292
modifier : Modifier = Modifier ,
252
293
) {
253
294
Column (modifier, horizontalAlignment = Alignment .CenterHorizontally ) {
254
- Icon (
255
- modifier = Modifier
256
- .size(32 .dp),
257
- tint = ElementTheme .colors.iconPrimary,
258
- imageVector = Icons .Filled .Lock ,
259
- contentDescription = " " ,
260
- )
295
+ if (isInAppUnlock) {
296
+ RoundedIconAtom (imageVector = Icons .Filled .Lock )
297
+ } else {
298
+ Icon (
299
+ modifier = Modifier
300
+ .size(32 .dp),
301
+ tint = ElementTheme .colors.iconPrimary,
302
+ imageVector = Icons .Filled .Lock ,
303
+ contentDescription = " " ,
304
+ )
305
+ }
261
306
Spacer (modifier = Modifier .height(16 .dp))
262
307
Text (
263
308
text = stringResource(id = CommonStrings .common_enter_your_pin),
@@ -290,8 +335,8 @@ private fun PinUnlockHeader(
290
335
style = ElementTheme .typography.fontBodyMdRegular,
291
336
color = subtitleColor,
292
337
)
293
- Spacer ( Modifier .height( 24 .dp))
294
- if (state.pinEntry is Async . Success ) {
338
+ if ( ! isInAppUnlock && state.pinEntry is Async . Success ) {
339
+ Spacer ( Modifier .height( 24 .dp))
295
340
PinDotsRow (state.pinEntry.data)
296
341
}
297
342
}
@@ -314,10 +359,22 @@ private fun PinUnlockFooter(
314
359
315
360
@Composable
316
361
@PreviewsDayNight
317
- internal fun PinUnlockViewPreview (@PreviewParameter(PinUnlockStateProvider ::class ) state : PinUnlockState ) {
362
+ internal fun PinUnlockInAppViewPreview (@PreviewParameter(PinUnlockStateProvider ::class ) state : PinUnlockState ) {
363
+ ElementPreview {
364
+ PinUnlockView (
365
+ state = state,
366
+ isInAppUnlock = true ,
367
+ )
368
+ }
369
+ }
370
+
371
+ @Composable
372
+ @PreviewsDayNight
373
+ internal fun PinUnlockDefaultViewPreview (@PreviewParameter(PinUnlockStateProvider ::class ) state : PinUnlockState ) {
318
374
ElementPreview {
319
375
PinUnlockView (
320
376
state = state,
377
+ isInAppUnlock = false ,
321
378
)
322
379
}
323
380
}
0 commit comments