Skip to content

Commit d6d8e92

Browse files
committed
feat(add-new-user)
1 parent 7a610f5 commit d6d8e92

File tree

2 files changed

+175
-106
lines changed

2 files changed

+175
-106
lines changed

feature-add/src/main/java/com/hoc/flowmvi/ui/add/AddNewUserScreen.kt

Lines changed: 171 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,6 @@ internal fun AddNewUserRoute(
156156
)
157157
}
158158

159-
@OptIn(ExperimentalMaterial3Api::class)
160159
@Composable
161160
private fun AddNewUserContent(
162161
viewState: ViewState,
@@ -166,17 +165,8 @@ private fun AddNewUserContent(
166165
onSubmit: () -> Unit,
167166
modifier: Modifier = Modifier,
168167
) {
169-
val emailError =
170-
if (viewState.emailChanged && UserValidationError.INVALID_EMAIL_ADDRESS in viewState.errors) "Invalid email"
171-
else null
172-
173-
val firstNameError =
174-
if (viewState.firstNameChanged && UserValidationError.TOO_SHORT_FIRST_NAME in viewState.errors) "Too short first name"
175-
else null
176-
177-
val lastNameError =
178-
if (viewState.lastNameChanged && UserValidationError.TOO_SHORT_LAST_NAME in viewState.errors) "Too short last name"
179-
else null
168+
val resources = LocalContext.current.resources
169+
val errors = viewState.errors
180170

181171
Box(
182172
modifier = modifier
@@ -191,125 +181,200 @@ private fun AddNewUserContent(
191181
) {
192182
Spacer(modifier = Modifier.height(16.dp))
193183

194-
TextField(
195-
modifier = Modifier.fillMaxWidth(),
196-
value = viewState.email ?: "",
197-
onValueChange = onEmailChanged,
198-
label = { Text(text = "Email") },
199-
leadingIcon = {
200-
Icon(
201-
imageVector = Icons.Filled.Email,
202-
contentDescription = "Email"
203-
)
204-
},
205-
maxLines = 1,
206-
singleLine = true,
207-
keyboardOptions = KeyboardOptions(
208-
keyboardType = KeyboardType.Email,
209-
imeAction = ImeAction.Next
210-
),
211-
isError = emailError !== null,
212-
supportingText = {
213-
emailError?.let {
214-
Text(text = it)
184+
EmailTextField(
185+
email = viewState.email ?: "",
186+
onEmailChanged = onEmailChanged,
187+
emailError = remember(viewState.emailChanged, errors, resources) {
188+
if (viewState.emailChanged && UserValidationError.INVALID_EMAIL_ADDRESS in errors) {
189+
resources.getString(R.string.invalid_email)
190+
} else {
191+
null
215192
}
216193
}
217194
)
218195

219196
Spacer(modifier = Modifier.height(16.dp))
220197

221-
TextField(
222-
modifier = Modifier.fillMaxWidth(),
223-
value = viewState.firstName ?: "",
224-
onValueChange = onFirstNameChanged,
225-
label = { Text(text = "First name") },
226-
leadingIcon = {
227-
Icon(
228-
imageVector = Icons.Filled.Person,
229-
contentDescription = "First name"
230-
)
231-
},
232-
maxLines = 1,
233-
singleLine = true,
234-
keyboardOptions = KeyboardOptions(
235-
keyboardType = KeyboardType.Text,
236-
imeAction = ImeAction.Next
237-
),
238-
isError = firstNameError !== null,
239-
supportingText = {
240-
firstNameError?.let {
241-
Text(text = it)
198+
FirstNameTextField(
199+
firstName = viewState.firstName ?: "",
200+
onFirstNameChanged = onFirstNameChanged,
201+
firstNameError = remember(viewState.firstNameChanged, errors, resources) {
202+
if (viewState.firstNameChanged && UserValidationError.TOO_SHORT_FIRST_NAME in errors) {
203+
resources.getString(R.string.too_short_first_name)
204+
} else {
205+
null
242206
}
243207
}
244208
)
245209

246210
Spacer(modifier = Modifier.height(16.dp))
247211

248-
TextField(
249-
modifier = Modifier.fillMaxWidth(),
250-
value = viewState.lastName ?: "",
251-
onValueChange = onLastNameChanged,
252-
label = { Text(text = "Last name") },
253-
leadingIcon = {
254-
Icon(
255-
imageVector = Icons.Filled.Person,
256-
contentDescription = "Last name"
257-
)
258-
},
259-
maxLines = 1,
260-
singleLine = true,
261-
keyboardOptions = KeyboardOptions(
262-
keyboardType = KeyboardType.Text,
263-
imeAction = ImeAction.Done
264-
),
265-
isError = lastNameError !== null,
266-
supportingText = {
267-
lastNameError?.let {
268-
Text(text = it)
212+
LastNameTextField(
213+
lastName = viewState.lastName ?: "",
214+
onLastNameChanged = onLastNameChanged,
215+
lastNameError = remember(viewState.lastNameChanged, errors, resources) {
216+
if (viewState.lastNameChanged && UserValidationError.TOO_SHORT_LAST_NAME in errors) {
217+
resources.getString(R.string.too_short_last_name)
218+
} else {
219+
null
269220
}
270221
}
271222
)
272223

273224
Spacer(modifier = Modifier.height(24.dp))
274225

275-
Crossfade(
226+
AddButton(
227+
isLoading = viewState.isLoading,
228+
onSubmit = onSubmit,
229+
)
230+
231+
Spacer(modifier = Modifier.height(16.dp))
232+
}
233+
}
234+
}
235+
236+
@Composable
237+
private fun AddButton(
238+
isLoading: Boolean,
239+
onSubmit: () -> Unit,
240+
modifier: Modifier = Modifier,
241+
) {
242+
Crossfade(
243+
modifier = modifier
244+
.fillMaxWidth()
245+
.heightIn(min = 64.dp),
246+
targetState = isLoading,
247+
animationSpec = tween(durationMillis = 200),
248+
label = "LoadingIndicator/ElevatedButton",
249+
) { state ->
250+
if (state) {
251+
LoadingIndicator(
252+
modifier = Modifier
253+
.fillMaxWidth(),
254+
)
255+
} else {
256+
ElevatedButton(
276257
modifier = Modifier
277258
.fillMaxWidth()
278-
.heightIn(min = 64.dp),
279-
targetState = viewState.isLoading,
280-
animationSpec = tween(durationMillis = 200),
281-
label = "LoadingIndicator/ElevatedButton",
282-
) { isLoading ->
283-
if (isLoading) {
284-
LoadingIndicator(
285-
modifier = Modifier
286-
.fillMaxWidth(),
287-
)
288-
} else {
289-
ElevatedButton(
290-
modifier = Modifier
291-
.fillMaxWidth()
292-
.wrapContentSize(Alignment.Center),
293-
colors = ButtonDefaults.elevatedButtonColors(
294-
containerColor = MaterialTheme.colorScheme.primary,
295-
contentColor = MaterialTheme.colorScheme.onPrimary
296-
),
297-
onClick = onSubmit,
298-
contentPadding = PaddingValues(
299-
horizontal = 32.dp,
300-
vertical = 16.dp,
301-
),
302-
) {
303-
Text(text = "Add")
304-
}
305-
}
259+
.wrapContentSize(Alignment.Center),
260+
colors = ButtonDefaults.elevatedButtonColors(
261+
containerColor = MaterialTheme.colorScheme.primary,
262+
contentColor = MaterialTheme.colorScheme.onPrimary
263+
),
264+
onClick = onSubmit,
265+
contentPadding = PaddingValues(
266+
horizontal = 32.dp,
267+
vertical = 16.dp,
268+
),
269+
) {
270+
Text(text = "Add")
306271
}
307-
308-
Spacer(modifier = Modifier.height(16.dp))
309272
}
310273
}
311274
}
312275

276+
@Composable
277+
@OptIn(ExperimentalMaterial3Api::class)
278+
private fun LastNameTextField(
279+
lastName: String,
280+
onLastNameChanged: (String) -> Unit,
281+
lastNameError: String?,
282+
modifier: Modifier = Modifier,
283+
) {
284+
TextField(
285+
modifier = modifier.fillMaxWidth(),
286+
value = lastName,
287+
onValueChange = onLastNameChanged,
288+
label = { Text(text = "Last name") },
289+
leadingIcon = {
290+
Icon(
291+
imageVector = Icons.Filled.Person,
292+
contentDescription = "Last name"
293+
)
294+
},
295+
maxLines = 1,
296+
singleLine = true,
297+
keyboardOptions = KeyboardOptions(
298+
keyboardType = KeyboardType.Text,
299+
imeAction = ImeAction.Done
300+
),
301+
isError = lastNameError !== null,
302+
supportingText = {
303+
lastNameError?.let {
304+
Text(text = it)
305+
}
306+
}
307+
)
308+
}
309+
310+
@Composable
311+
@OptIn(ExperimentalMaterial3Api::class)
312+
private fun FirstNameTextField(
313+
firstName: String,
314+
onFirstNameChanged: (String) -> Unit,
315+
firstNameError: String?,
316+
modifier: Modifier = Modifier,
317+
) {
318+
TextField(
319+
modifier = modifier.fillMaxWidth(),
320+
value = firstName,
321+
onValueChange = onFirstNameChanged,
322+
label = { Text(text = "First name") },
323+
leadingIcon = {
324+
Icon(
325+
imageVector = Icons.Filled.Person,
326+
contentDescription = "First name"
327+
)
328+
},
329+
maxLines = 1,
330+
singleLine = true,
331+
keyboardOptions = KeyboardOptions(
332+
keyboardType = KeyboardType.Text,
333+
imeAction = ImeAction.Next
334+
),
335+
isError = firstNameError !== null,
336+
supportingText = {
337+
firstNameError?.let {
338+
Text(text = it)
339+
}
340+
}
341+
)
342+
}
343+
344+
@Composable
345+
@OptIn(ExperimentalMaterial3Api::class)
346+
private fun EmailTextField(
347+
email: String,
348+
onEmailChanged: (String) -> Unit,
349+
emailError: String?,
350+
modifier: Modifier = Modifier,
351+
) {
352+
TextField(
353+
modifier = modifier.fillMaxWidth(),
354+
value = email,
355+
onValueChange = onEmailChanged,
356+
label = { Text(text = "Email") },
357+
leadingIcon = {
358+
Icon(
359+
imageVector = Icons.Filled.Email,
360+
contentDescription = "Email"
361+
)
362+
},
363+
maxLines = 1,
364+
singleLine = true,
365+
keyboardOptions = KeyboardOptions(
366+
keyboardType = KeyboardType.Email,
367+
imeAction = ImeAction.Next
368+
),
369+
isError = emailError !== null,
370+
supportingText = {
371+
emailError?.let {
372+
Text(text = it)
373+
}
374+
}
375+
)
376+
}
377+
313378
@Preview(
314379
showBackground = true,
315380
showSystemUi = true,

feature-add/src/main/res/values/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,8 @@
99
<string name="user_not_found_error_message">User not found</string>
1010
<string name="validation_failed_error_message">Validation failed</string>
1111
<string name="add_user_success">Added user successfully</string>
12+
13+
<string name="invalid_email">Invalid email</string>
14+
<string name="too_short_first_name">Too short first name</string>
15+
<string name="too_short_last_name">Too short last name</string>
1216
</resources>

0 commit comments

Comments
 (0)