Skip to content

Commit d746afc

Browse files
feat: Update keyboard behaviour and fix layout bug (#413)
- update up/ down and tak keys anvigation on screen - fix bug of screen layout when bottom content is > 1/3 of the screen [Tutorial for writing good descriptions]: https://cbea.ms/git-commit/ [//]: # (Be mindful that the PR title also needs to follow conventional commit standards) # DCMAW-18937: Update keyboard behaviour and fix layout bug - update up/ down and tak keys anvigation on screen - fix bug of screen layout when bottom content is > 1/3 of the screen [//]: # (e.g. "- Create 'androidLibrary' Gradle module.") ## Evidence of the change **Bottom Content > 1/3** [Screen_recording_20260408_161917.webm](https://github.com/user-attachments/assets/6dbf80c6-66c5-4630-9d48-8e84fd0be2f5) **Up/ Down Arrow** [Screen_recording_20260408_162002.webm](https://github.com/user-attachments/assets/80cf4df0-af63-4089-8844-afe879f5ba24) **Tab** [Screen_recording_20260408_162020.webm](https://github.com/user-attachments/assets/6418e2b9-bc00-465b-b1a4-72cfbd14c40a) [//]: # (Screenshots / uploaded videos go here) ## Checklist ### Before creating the pull request - [x] Commit messages that conform to conventional commit messages. - [x] Ran the app locally ensuring it builds. - [x] Tests pass locally. - [x] Pull request has a clear title with a short description about the feature or update. - [x] Created a `draft` pull request if it's not ready for review. ### Before the CODEOWNERS review the pull request - [x] Complete all Acceptance Criteria within Jira ticket. - [x] Self-review code. - [x] Successfully run changes on a testing device. - [x] Complete automated Testing: * [x] Unit Tests. * [x] Integration Tests. * [x] Instrumentation / Emulator Tests. - [x] Review [Accessibility considerations]. - [ ] Handle PR comments. ### Before merging the pull request - [ ] [Sonar cloud report] passes inspections for your PR. - [ ] Resolve all comments. [Sonar cloud report]: https://sonarcloud.io/project/overview?id=di-mobile-android-ui [Accessibility considerations]: https://developer.android.com/guide/topics/ui/accessibility/testing Resolves: DCMAW-18937
1 parent 1699267 commit d746afc

4 files changed

Lines changed: 242 additions & 8 deletions

File tree

patterns/src/main/java/uk/gov/android/ui/patterns/errorscreen/v2/ErrorScreen.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package uk.gov.android.ui.patterns.errorscreen.v2
22

3+
import android.annotation.SuppressLint
34
import androidx.compose.foundation.background
45
import androidx.compose.foundation.layout.Arrangement
56
import androidx.compose.foundation.layout.Column
@@ -10,14 +11,16 @@ import androidx.compose.foundation.layout.height
1011
import androidx.compose.foundation.layout.padding
1112
import androidx.compose.foundation.lazy.LazyColumn
1213
import androidx.compose.foundation.lazy.LazyListScope
14+
import androidx.compose.foundation.lazy.LazyListState
15+
import androidx.compose.foundation.lazy.rememberLazyListState
1316
import androidx.compose.material3.MaterialTheme.colorScheme
1417
import androidx.compose.runtime.Composable
1518
import androidx.compose.ui.Alignment
1619
import androidx.compose.ui.Modifier
1720
import androidx.compose.ui.graphics.vector.ImageVector
1821
import androidx.compose.ui.layout.SubcomposeLayout
22+
import androidx.compose.ui.platform.LocalConfiguration
1923
import androidx.compose.ui.platform.LocalDensity
20-
import androidx.compose.ui.platform.LocalWindowInfo
2124
import androidx.compose.ui.platform.testTag
2225
import androidx.compose.ui.res.stringResource
2326
import androidx.compose.ui.res.vectorResource
@@ -36,6 +39,7 @@ import uk.gov.android.ui.patterns.errorscreen.v2.ErrorScreenDefaults.HorizontalP
3639
import uk.gov.android.ui.patterns.errorscreen.v2.ErrorScreenDefaults.VerticalPadding
3740
import uk.gov.android.ui.patterns.errorscreen.v2.ErrorScreenTitleTestTag.ERROR_BODY_LAZY_COLUMN_TEST_TAG
3841
import uk.gov.android.ui.patterns.errorscreen.v2.ErrorScreenTitleTestTag.ERROR_SCREEN_TITLE_TEST_TAG
42+
import uk.gov.android.ui.patterns.leftalignedscreen.bringIntoView
3943
import uk.gov.android.ui.patterns.utils.clearListSemanticsForTalkBack
4044
import uk.gov.android.ui.theme.m3.GdsTheme
4145
import uk.gov.android.ui.theme.meta.ExcludeFromJacocoGeneratedReport
@@ -67,6 +71,7 @@ private const val DENSITY_PREVIEW_INDEX = 5
6771
* @param tertiaryButton tertiary action button. Use of [GdsButton] composable is recommended (optional).
6872
*/
6973

74+
@SuppressLint("ConfigurationScreenWidthHeight")
7075
@Suppress("LongMethod")
7176
@Composable
7277
fun ErrorScreen(
@@ -78,7 +83,7 @@ fun ErrorScreen(
7883
secondaryButton: (@Composable () -> Unit)? = null,
7984
tertiaryButton: (@Composable () -> Unit)? = null,
8085
) {
81-
val screenHeight = LocalWindowInfo.current.containerSize.height.dp
86+
val screenHeight = LocalConfiguration.current.screenHeightDp.dp
8287
val thresholdHeight = screenHeight * ONE_THIRD
8388
val density = LocalDensity.current
8489

@@ -170,15 +175,18 @@ private fun MainContent(
170175
body: (LazyListScope.(horizontalItemPadding: Dp) -> Unit)? = null,
171176
bottomContent: (@Composable () -> Unit)? = null,
172177
) {
178+
val scrollState: LazyListState = rememberLazyListState()
173179
LazyColumn(
174180
verticalArrangement = Arrangement.spacedBy(
175181
VerticalPadding,
176182
Alignment.CenterVertically,
177183
),
178184
modifier = modifier
179185
.fillMaxSize()
186+
.bringIntoView(scrollState)
180187
.testTag(ERROR_BODY_LAZY_COLUMN_TEST_TAG)
181188
.clearListSemanticsForTalkBack(),
189+
state = scrollState,
182190
) {
183191
item {
184192
Column(

uitestwrapper/src/main/java/uk/gov/android/ui/testwrapper/patterns/Patterns.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import uk.gov.android.ui.patterns.loadingscreen.LoadingScreen
1717
import uk.gov.android.ui.testwrapper.DetailItem
1818
import uk.gov.android.ui.testwrapper.patterns.centrealignedscreen.CentreAlignedScreenDemo
1919
import uk.gov.android.ui.testwrapper.patterns.centrealignedscreen.CentreAlignedScrollableScreenDemo
20+
import uk.gov.android.ui.testwrapper.patterns.error.v2.ErrorBottomContentLargeScreenDemo
21+
import uk.gov.android.ui.testwrapper.patterns.error.v2.ErrorScreenDemo
22+
import uk.gov.android.ui.testwrapper.patterns.error.v2.ErrorScrollableScreenDemo
2023
import uk.gov.android.ui.testwrapper.patterns.leftalignedscreen.LeftAlignedScreenDemo
2124
import uk.gov.android.ui.testwrapper.patterns.leftalignedscreen.LeftAlignedScreenNoTitleDemo
2225
import uk.gov.android.ui.theme.smallPadding
@@ -59,6 +62,9 @@ fun PatternDetail(
5962
LEFT_ALIGNED_SCREEN_NO_TITLE -> LeftAlignedScreenNoTitleDemo()
6063
CENTRED_ALIGNED_SCREEN -> CentreAlignedScreenDemo()
6164
CENTRED_ALIGNED_SCROLLABLE_SCREEN -> CentreAlignedScrollableScreenDemo()
65+
ERROR_SCREEN -> ErrorScreenDemo()
66+
ERROR_SCROLLABLE_SCREEN -> ErrorScrollableScreenDemo()
67+
ERROR_BOTTOM_CONTENT_LARGE_SCREEN -> ErrorBottomContentLargeScreenDemo()
6268
}
6369
}
6470

@@ -67,3 +73,6 @@ const val LEFT_ALIGNED_SCREEN = "leftAlignedScreen"
6773
const val LEFT_ALIGNED_SCREEN_NO_TITLE = "leftAlignedScreenNoTitle"
6874
const val CENTRED_ALIGNED_SCREEN = "centreAlignedScreen"
6975
const val CENTRED_ALIGNED_SCROLLABLE_SCREEN = "centreAlignedScrollableScreen"
76+
const val ERROR_SCREEN = "errorScreen"
77+
const val ERROR_SCROLLABLE_SCREEN = "errorScrollableScreen"
78+
const val ERROR_BOTTOM_CONTENT_LARGE_SCREEN = "errorBottomContentLargeScreen"

uitestwrapper/src/main/java/uk/gov/android/ui/testwrapper/patterns/PatternsDestination.kt

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ sealed class PatternsDestination(
6666
fun entries() =
6767
listOf(
6868
Placeholder(text = "Dialog"),
69-
Placeholder(text = "Error Screen"),
7069
DetailedItem(
7170
text = "Left Aligned Screen",
7271
items =
@@ -79,6 +78,12 @@ sealed class PatternsDestination(
7978
label = LEFT_ALIGNED_SCREEN_NO_TITLE,
8079
name = "Left Aligned Screen No Title",
8180
),
81+
),
82+
),
83+
DetailedItem(
84+
text = "Centre Aligned Screen",
85+
items =
86+
listOf(
8287
DetailItem(
8388
label = CENTRED_ALIGNED_SCREEN,
8489
name = "Centred Aligned Screen",
@@ -90,16 +95,20 @@ sealed class PatternsDestination(
9095
),
9196
),
9297
DetailedItem(
93-
text = "Centre Aligned Screen",
98+
text = "Error Screen",
9499
items =
95100
listOf(
96101
DetailItem(
97-
label = CENTRED_ALIGNED_SCREEN,
98-
name = "Centred Aligned Screen",
102+
label = ERROR_SCREEN,
103+
name = "Error Screen",
99104
),
100105
DetailItem(
101-
label = CENTRED_ALIGNED_SCROLLABLE_SCREEN,
102-
name = "Centred Aligned Scrollable Screen",
106+
label = ERROR_SCROLLABLE_SCREEN,
107+
name = "Error Scrollable Screen",
108+
),
109+
DetailItem(
110+
label = ERROR_BOTTOM_CONTENT_LARGE_SCREEN,
111+
name = "Error Bottom Content Larger Than 1/3 Screen",
103112
),
104113
),
105114
),
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package uk.gov.android.ui.testwrapper.patterns.error.v2
2+
3+
import android.annotation.SuppressLint
4+
import androidx.compose.foundation.layout.fillMaxWidth
5+
import androidx.compose.foundation.layout.padding
6+
import androidx.compose.material3.MaterialTheme
7+
import androidx.compose.material3.Text
8+
import androidx.compose.runtime.Composable
9+
import androidx.compose.ui.Modifier
10+
import androidx.compose.ui.graphics.vector.ImageVector
11+
import androidx.compose.ui.res.stringResource
12+
import androidx.compose.ui.res.vectorResource
13+
import androidx.compose.ui.text.style.TextAlign
14+
import androidx.compose.ui.unit.Dp
15+
import kotlinx.collections.immutable.persistentListOf
16+
import uk.gov.android.ui.componentsv2.button.ButtonTypeV2
17+
import uk.gov.android.ui.componentsv2.button.GdsButton
18+
import uk.gov.android.ui.componentsv2.heading.GdsHeading
19+
import uk.gov.android.ui.componentsv2.heading.GdsHeadingAlignment
20+
import uk.gov.android.ui.componentsv2.images.GdsIcon
21+
import uk.gov.android.ui.patterns.errorscreen.v2.ErrorScreen
22+
import uk.gov.android.ui.componentsv2.R as componentsR
23+
import uk.gov.android.ui.patterns.R as patternsR
24+
25+
@SuppressLint("ComposeModifierMissing")
26+
@Composable
27+
@Suppress("MagicNumber")
28+
fun ErrorScreenDemo() {
29+
ErrorScreen(
30+
icon = { padding ->
31+
GdsIcon(
32+
image = ImageVector.vectorResource(patternsR.drawable.ic_warning_error),
33+
contentDescription = stringResource(patternsR.string.error_icon_description),
34+
modifier = Modifier.errorScreenDemo(padding),
35+
)
36+
},
37+
title = { padding ->
38+
GdsHeading(
39+
text = "Error Screen",
40+
modifier = Modifier.padding(padding),
41+
textAlign = GdsHeadingAlignment.CenterAligned,
42+
)
43+
},
44+
body = { padding ->
45+
items(bodyContent.slice(IntRange(0, 3)).size) { index ->
46+
Text(
47+
text = bodyContent[index],
48+
textAlign = TextAlign.Center,
49+
modifier = Modifier.errorScreenDemo(padding),
50+
color = MaterialTheme.colorScheme.onBackground,
51+
)
52+
}
53+
item {
54+
GdsHeading(
55+
text = "Error Screen",
56+
modifier = Modifier.errorScreenDemo(padding),
57+
textAlign = GdsHeadingAlignment.CenterAligned,
58+
)
59+
}
60+
item {
61+
GdsButton(
62+
text = "Content Button",
63+
buttonType = ButtonTypeV2.Primary(),
64+
onClick = {},
65+
modifier = Modifier.fillMaxWidth(),
66+
)
67+
}
68+
},
69+
primaryButton = {
70+
val text = stringResource(componentsR.string.primary_button)
71+
GdsButton(
72+
text = text,
73+
buttonType = ButtonTypeV2.Primary(),
74+
onClick = {},
75+
modifier = Modifier.fillMaxWidth(),
76+
)
77+
},
78+
)
79+
}
80+
81+
@SuppressLint("ComposeModifierMissing")
82+
@Composable
83+
fun ErrorScrollableScreenDemo() {
84+
ErrorScreen(
85+
icon = { padding ->
86+
GdsIcon(
87+
image = ImageVector.vectorResource(patternsR.drawable.ic_warning_error),
88+
contentDescription = stringResource(patternsR.string.error_icon_description),
89+
modifier = Modifier.errorScreenDemo(padding),
90+
)
91+
},
92+
title = { padding ->
93+
GdsHeading(
94+
text = "Error Screen",
95+
modifier = Modifier.errorScreenDemo(padding),
96+
textAlign = GdsHeadingAlignment.CenterAligned,
97+
)
98+
},
99+
body = { padding ->
100+
items(bodyContent.size) { index ->
101+
Text(
102+
text = bodyContent[index],
103+
textAlign = TextAlign.Center,
104+
modifier = Modifier.errorScreenDemo(padding),
105+
color = MaterialTheme.colorScheme.onBackground,
106+
)
107+
}
108+
},
109+
primaryButton = {
110+
val text = stringResource(componentsR.string.primary_button)
111+
GdsButton(
112+
text = text,
113+
buttonType = ButtonTypeV2.Primary(),
114+
onClick = {},
115+
modifier = Modifier.fillMaxWidth(),
116+
)
117+
},
118+
)
119+
}
120+
121+
@SuppressLint("ComposeModifierMissing")
122+
@Composable
123+
fun ErrorBottomContentLargeScreenDemo() {
124+
ErrorScreen(
125+
icon = { padding ->
126+
GdsIcon(
127+
image = ImageVector.vectorResource(patternsR.drawable.ic_warning_error),
128+
contentDescription = stringResource(patternsR.string.error_icon_description),
129+
modifier = Modifier.errorScreenDemo(padding),
130+
)
131+
},
132+
title = { padding ->
133+
GdsHeading(
134+
text = "Error Screen",
135+
modifier = Modifier.errorScreenDemo(padding),
136+
textAlign = GdsHeadingAlignment.CenterAligned,
137+
)
138+
},
139+
body = { padding ->
140+
items(bodyContent.size) { index ->
141+
Text(
142+
text = bodyContent[index],
143+
textAlign = TextAlign.Center,
144+
modifier = Modifier.errorScreenDemo(padding),
145+
color = MaterialTheme.colorScheme.onBackground,
146+
)
147+
}
148+
},
149+
primaryButton = {
150+
val text = stringResource(componentsR.string.primary_button)
151+
GdsButton(
152+
text = text,
153+
buttonType = ButtonTypeV2.Primary(),
154+
onClick = {},
155+
modifier = Modifier.fillMaxWidth(),
156+
)
157+
},
158+
secondaryButton = {
159+
val text = stringResource(componentsR.string.secondary_button)
160+
GdsButton(
161+
text = text,
162+
buttonType = ButtonTypeV2.Secondary(),
163+
onClick = {},
164+
modifier = Modifier.fillMaxWidth(),
165+
)
166+
},
167+
tertiaryButton = {
168+
val text = stringResource(componentsR.string.tertiary_button)
169+
GdsButton(
170+
text = text,
171+
buttonType = ButtonTypeV2.Tertiary(),
172+
onClick = {},
173+
modifier = Modifier.fillMaxWidth(),
174+
)
175+
},
176+
)
177+
}
178+
179+
private fun Modifier.errorScreenDemo(paddingValues: Dp) = this
180+
.padding(paddingValues)
181+
.fillMaxWidth()
182+
183+
private val bodyContent = persistentListOf(
184+
"Item 1",
185+
"This is a slightly longer description to test wrapping behaviour",
186+
"Short",
187+
"OK",
188+
"A medium length string here",
189+
"This is an even longer string that should definitely wrap across multiple lines on most screens",
190+
"Hello",
191+
"Something went wrong, please try again later",
192+
"Hi",
193+
"We could not verify your identity at this time",
194+
"Error",
195+
"Please check your internet connection and try again. If the problem persists, contact support",
196+
"Try again",
197+
"Unexpected failure",
198+
"A",
199+
"Your session has expired. Please sign in again to continue",
200+
"Not found",
201+
"This service is temporarily unavailable due to scheduled maintenance. We apologise for any inconvenience",
202+
"Retry",
203+
"An unknown error occurred while processing your request",
204+
"No connection",
205+
"We were unable to complete your request because the server did not respond in time. " +
206+
"Please check your network settings and try again",
207+
"Access denied",
208+
)

0 commit comments

Comments
 (0)