Skip to content

Commit 88a6879

Browse files
authored
Add landscape support for the PIN layout. (#74)
1 parent df88ff8 commit 88a6879

File tree

5 files changed

+206
-82
lines changed

5 files changed

+206
-82
lines changed

app/src/main/java/com/xinto/mauth/ui/component/pinboard/PinBoard.kt

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import androidx.compose.ui.Alignment
1919
import androidx.compose.ui.Modifier
2020
import androidx.compose.ui.res.painterResource
2121
import androidx.compose.ui.tooling.preview.Preview
22+
import androidx.compose.ui.unit.Dp
2223
import androidx.compose.ui.unit.dp
2324
import com.xinto.mauth.R
2425
import com.xinto.mauth.ui.theme.MauthTheme
@@ -27,20 +28,23 @@ import com.xinto.mauth.ui.theme.MauthTheme
2728
@Composable
2829
fun PinBoard(
2930
modifier: Modifier = Modifier,
31+
horizontalButtonSpace: Dp = 16.dp,
32+
minButtonSize: Dp = PinButtonDefaults.PinButtonNormalMinSize,
3033
state: PinBoardState = rememberPinBoardState()
3134
) {
3235
FlowRow(
3336
modifier = modifier,
3437
verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically),
35-
horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally),
38+
horizontalArrangement = Arrangement.spacedBy(horizontalButtonSpace, Alignment.CenterHorizontally),
3639
maxItemsInEachRow = 3
3740
) {
3841
state.buttons.forEach { button ->
3942
when (button) {
4043
is PinBoardState.PinBoardButton.Number -> {
4144
PinButton(
4245
modifier = Modifier.weight(1f),
43-
onClick = { state.onNumberClick(button.number) }
46+
onClick = { state.onNumberClick(button.number) },
47+
minButtonSize = minButtonSize
4448
) {
4549
Text(button.toString())
4650
}
@@ -57,9 +61,10 @@ fun PinBoard(
5761
else -> throw NoSuchElementException()
5862
},
5963
onLongClick =
60-
if (button is PinBoardState.PinBoardButton.Backspace)
61-
state.onBackspaceLongClick
62-
else null
64+
if (button is PinBoardState.PinBoardButton.Backspace)
65+
state.onBackspaceLongClick
66+
else null,
67+
minButtonSize = minButtonSize
6368
) {
6469
Icon(
6570
modifier = Modifier.fillMaxSize(0.4f).aspectRatio(1f),
@@ -76,7 +81,7 @@ fun PinBoard(
7681
}
7782
}
7883
is PinBoardState.PinBoardButton.Empty -> {
79-
Spacer(Modifier.aspectRatio(1f).weight(1f).size(PinButtonDefaults.PinButtonMinSize))
84+
Spacer(Modifier.aspectRatio(1f).weight(1f).size(minButtonSize))
8085
}
8186
}
8287
}

app/src/main/java/com/xinto/mauth/ui/component/pinboard/PinButton.kt

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import androidx.compose.ui.geometry.Size
3131
import androidx.compose.ui.graphics.Color
3232
import androidx.compose.ui.graphics.graphicsLayer
3333
import androidx.compose.ui.platform.LocalDensity
34+
import androidx.compose.ui.unit.Dp
3435
import androidx.compose.ui.unit.dp
3536
import com.xinto.mauth.ui.component.Animatable
3637
import kotlinx.coroutines.CancellationException
@@ -48,6 +49,7 @@ fun PrimaryPinButton(
4849
enabled: Boolean = true,
4950
colors: PinButtonColors = PinButtonDefaults.primaryPinButtonColors(),
5051
shapes: PinButtonShapes = PinButtonDefaults.plainPinButtonShapes(),
52+
minButtonSize: Dp = PinButtonDefaults.PinButtonNormalMinSize,
5153
content: @Composable () -> Unit
5254
) = PinButton(
5355
onClick = onClick,
@@ -56,6 +58,7 @@ fun PrimaryPinButton(
5658
enabled = enabled,
5759
colors = colors,
5860
shapes = shapes,
61+
minButtonSize = minButtonSize,
5962
content = content
6063
)
6164

@@ -67,18 +70,19 @@ fun PinButton(
6770
enabled: Boolean = true,
6871
colors: PinButtonColors = PinButtonDefaults.plainPinButtonColors(),
6972
shapes: PinButtonShapes = PinButtonDefaults.plainPinButtonShapes(),
73+
minButtonSize: Dp = PinButtonDefaults.PinButtonNormalMinSize,
7074
content: @Composable () -> Unit
7175
) {
7276
val interactionSource = remember { MutableInteractionSource() }
73-
val shape by shapes.getButtonShape(interactionSource)
77+
val shape by shapes.getButtonShape(interactionSource, minButtonSize)
7478
val backgroundColor by colors.getBackgroundColor(interactionSource)
7579
val contentColor by colors.getForegroundColor(interactionSource)
7680
Box(
7781
modifier = modifier
7882
.aspectRatio(1f)
7983
.sizeIn(
80-
minWidth = PinButtonDefaults.PinButtonMinSize,
81-
minHeight = PinButtonDefaults.PinButtonMinSize,
84+
minWidth = minButtonSize,
85+
minHeight = minButtonSize,
8286
)
8387
.graphicsLayer {
8488
clip = true
@@ -106,7 +110,8 @@ fun PinButton(
106110

107111
object PinButtonDefaults {
108112

109-
val PinButtonMinSize = 72.dp
113+
val PinButtonSmallMinSize = 56.dp
114+
val PinButtonNormalMinSize = 72.dp
110115
const val AnimationDurationPress = 200
111116
const val AnimationDurationRelease = 150
112117

@@ -190,14 +195,14 @@ data class PinButtonShapes(
190195
) {
191196

192197
@Composable
193-
fun getButtonShape(interactionSource: InteractionSource): State<CornerBasedShape> {
198+
fun getButtonShape(interactionSource: InteractionSource, minButtonSize: Dp = PinButtonDefaults.PinButtonNormalMinSize): State<CornerBasedShape> {
194199
val density = LocalDensity.current
195200
val size = with(density) {
196-
val shapeSize = PinButtonDefaults.PinButtonMinSize.toPx()
201+
val shapeSize = minButtonSize.toPx()
197202
Size(shapeSize, shapeSize)
198203
}
199204

200-
val animatable = remember(density, size) {
205+
val animatable = remember(density, size, minButtonSize) {
201206
Animatable(shape, density, size)
202207
}
203208
return animatePressValue(

app/src/main/java/com/xinto/mauth/ui/component/pinboard/PinScaffold.kt

Lines changed: 109 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import android.content.res.Configuration
44
import androidx.compose.foundation.layout.Arrangement
55
import androidx.compose.foundation.layout.Box
66
import androidx.compose.foundation.layout.Column
7+
import androidx.compose.foundation.layout.Row
78
import androidx.compose.foundation.layout.Spacer
89
import androidx.compose.foundation.layout.WindowInsets
910
import androidx.compose.foundation.layout.fillMaxHeight
1011
import androidx.compose.foundation.layout.fillMaxWidth
12+
import androidx.compose.foundation.layout.height
1113
import androidx.compose.foundation.layout.padding
14+
import androidx.compose.foundation.layout.sizeIn
1215
import androidx.compose.material3.FabPosition
1316
import androidx.compose.material3.LocalTextStyle
1417
import androidx.compose.material3.MaterialTheme
@@ -18,9 +21,11 @@ import androidx.compose.material3.Text
1821
import androidx.compose.material3.contentColorFor
1922
import androidx.compose.runtime.Composable
2023
import androidx.compose.runtime.CompositionLocalProvider
24+
import androidx.compose.runtime.remember
2125
import androidx.compose.ui.Alignment
2226
import androidx.compose.ui.Modifier
2327
import androidx.compose.ui.graphics.Color
28+
import androidx.compose.ui.platform.LocalConfiguration
2429
import androidx.compose.ui.text.style.TextAlign
2530
import androidx.compose.ui.tooling.preview.Preview
2631
import androidx.compose.ui.unit.dp
@@ -40,6 +45,7 @@ fun PinScaffold(
4045
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
4146
description: (@Composable () -> Unit)? = null,
4247
error: Boolean = false,
48+
useSmallButtons: Boolean = false,
4349
codeLength: Int,
4450
) {
4551
Scaffold(
@@ -53,44 +59,116 @@ fun PinScaffold(
5359
contentColor = contentColor,
5460
contentWindowInsets = contentWindowInsets,
5561
) {
56-
Column(
57-
modifier = Modifier
58-
.fillMaxWidth()
59-
.fillMaxHeight()
60-
.padding(it)
61-
.padding(40.dp)
62-
.padding(bottom = 16.dp),
63-
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Bottom),
64-
horizontalAlignment = Alignment.CenterHorizontally
65-
) {
66-
if (description != null) {
67-
Spacer(modifier = Modifier.weight(1f))
62+
val orientation = LocalConfiguration.current.orientation
63+
val minButtonSize = remember(useSmallButtons) {
64+
if (useSmallButtons) {
65+
PinButtonDefaults.PinButtonSmallMinSize
66+
} else {
67+
PinButtonDefaults.PinButtonNormalMinSize
68+
}
69+
}
70+
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
71+
Row(
72+
modifier = Modifier
73+
.fillMaxWidth()
74+
.fillMaxHeight()
75+
.padding(it)
76+
.padding(horizontal = 20.dp),
77+
horizontalArrangement = Arrangement.SpaceAround,
78+
verticalAlignment = Alignment.CenterVertically
79+
) {
6880
Box(
69-
modifier = Modifier.fillMaxWidth(),
70-
contentAlignment = Alignment.Center
81+
modifier = Modifier
82+
.fillMaxWidth(0.5f)
83+
.fillMaxHeight()
7184
) {
72-
CompositionLocalProvider(
73-
LocalTextStyle provides MaterialTheme.typography.headlineMedium.copy(
74-
textAlign = TextAlign.Center
75-
)
85+
Column(
86+
modifier = Modifier.fillMaxHeight(),
87+
verticalArrangement = Arrangement.Center,
88+
horizontalAlignment = Alignment.CenterHorizontally
7689
) {
77-
description()
90+
if (description != null) {
91+
Box(
92+
modifier = Modifier.fillMaxWidth(),
93+
contentAlignment = Alignment.Center
94+
) {
95+
CompositionLocalProvider(
96+
LocalTextStyle provides MaterialTheme.typography.headlineMedium.copy(
97+
textAlign = TextAlign.Center
98+
)
99+
) {
100+
description()
101+
}
102+
}
103+
Spacer(modifier = Modifier.height(32.dp))
104+
}
105+
PinDisplay(
106+
modifier = Modifier
107+
.fillMaxWidth(0.5f),
108+
length = codeLength,
109+
error = error,
110+
)
78111
}
79112
}
80-
Spacer(modifier = Modifier.weight(1f))
113+
val pinBoardHorizontalPadding = 16.dp
114+
val horizontalButtonSpace = 16.dp
115+
val totalPadding = (pinBoardHorizontalPadding * 2) + (horizontalButtonSpace * 2)
116+
val maxBoxWidth = remember(minButtonSize) {
117+
val buttonsInRow = 3
118+
(minButtonSize * buttonsInRow) + totalPadding
119+
}
120+
Box(
121+
modifier = Modifier.sizeIn(maxWidth = maxBoxWidth),
122+
contentAlignment = Alignment.Center
123+
) {
124+
PinBoard(
125+
modifier = Modifier.padding(horizontal = pinBoardHorizontalPadding),
126+
horizontalButtonSpace = horizontalButtonSpace,
127+
minButtonSize = minButtonSize,
128+
state = state
129+
)
130+
}
81131
}
82-
PinDisplay(
83-
modifier = Modifier
84-
.fillMaxWidth(),
85-
length = codeLength,
86-
error = error,
87-
)
88-
PinBoard(
132+
} else {
133+
134+
Column(
89135
modifier = Modifier
90-
.padding(horizontal = 8.dp)
91-
.padding(top = 32.dp),
92-
state = state
93-
)
136+
.fillMaxWidth()
137+
.fillMaxHeight()
138+
.padding(it)
139+
.padding(start = 40.dp, top = 40.dp, end = 40.dp, bottom = 56.dp),
140+
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Bottom),
141+
horizontalAlignment = Alignment.CenterHorizontally
142+
) {
143+
if (description != null) {
144+
Spacer(modifier = Modifier.weight(1f))
145+
Box(
146+
modifier = Modifier.fillMaxWidth(),
147+
contentAlignment = Alignment.Center
148+
) {
149+
CompositionLocalProvider(
150+
LocalTextStyle provides MaterialTheme.typography.headlineMedium.copy(
151+
textAlign = TextAlign.Center
152+
)
153+
) {
154+
description()
155+
}
156+
}
157+
Spacer(modifier = Modifier.weight(1f))
158+
}
159+
PinDisplay(
160+
modifier = Modifier
161+
.fillMaxWidth(),
162+
length = codeLength,
163+
error = error,
164+
)
165+
PinBoard(
166+
modifier = Modifier
167+
.padding(horizontal = 8.dp)
168+
.padding(top = 32.dp),
169+
state = state
170+
)
171+
}
94172
}
95173
}
96174
}

app/src/main/java/com/xinto/mauth/ui/screen/pinremove/PinRemoveScreen.kt

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package com.xinto.mauth.ui.screen.pinremove
22

3+
import android.content.res.Configuration
34
import androidx.activity.compose.BackHandler
45
import androidx.compose.material3.Icon
56
import androidx.compose.material3.IconButton
67
import androidx.compose.material3.LargeTopAppBar
78
import androidx.compose.material3.Text
9+
import androidx.compose.material3.TopAppBar
810
import androidx.compose.runtime.Composable
911
import androidx.compose.runtime.getValue
12+
import androidx.compose.ui.platform.LocalConfiguration
1013
import androidx.compose.ui.res.painterResource
1114
import androidx.compose.ui.res.stringResource
1215
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -45,25 +48,25 @@ fun PinRemoveScreen(
4548
onNumberDelete: () -> Unit,
4649
onAllDelete: () -> Unit,
4750
) {
51+
val orientation = LocalConfiguration.current.orientation
4852
PinScaffold(
4953
codeLength = state.code.length,
5054
error = state is PinRemoveScreenState.Error,
5155
topBar = {
52-
LargeTopAppBar(
53-
title = {
54-
Text(stringResource(R.string.pinremove_title))
55-
},
56-
navigationIcon = {
57-
IconButton(onClick = onBack) {
58-
Icon(
59-
painter = painterResource(R.drawable.ic_arrow_back),
60-
contentDescription = null
61-
)
62-
}
63-
}
64-
)
56+
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
57+
TopAppBar(
58+
title = { AppBarTitle() },
59+
navigationIcon = { AppBarNavigationIcon(onBack) }
60+
)
61+
} else {
62+
LargeTopAppBar(
63+
title = { AppBarTitle() },
64+
navigationIcon = { AppBarNavigationIcon(onBack) }
65+
)
66+
}
6567
},
6668
description = null,
69+
useSmallButtons = orientation == Configuration.ORIENTATION_LANDSCAPE,
6770
state = rememberPinBoardState(
6871
showEnter = true,
6972
onNumberClick = onNumberEnter,
@@ -72,4 +75,19 @@ fun PinRemoveScreen(
7275
onBackspaceLongClick = onAllDelete
7376
)
7477
)
78+
}
79+
80+
@Composable
81+
fun AppBarTitle() {
82+
Text(stringResource(R.string.pinremove_title))
83+
}
84+
85+
@Composable
86+
fun AppBarNavigationIcon(onBack: () -> Unit) {
87+
IconButton(onClick = onBack) {
88+
Icon(
89+
painter = painterResource(R.drawable.ic_arrow_back),
90+
contentDescription = null
91+
)
92+
}
7593
}

0 commit comments

Comments
 (0)