Skip to content

Commit 2ed95b4

Browse files
authored
refactor: simplify complex methods and improve code quality (#8)
- Extract NewMissionContent into focused sub-components: * InputModeSelector for mode switching UI * InputContentSection for content rendering logic * ActionButtonsSection for mission execution controls * Remove @Suppress("CyclomaticComplexMethod") annotation - Refactor MissionResultCard complexity: * Extract MissionStatusRow for status display logic * Extract ExpandableInputSection for collapsible input display * Remove @Suppress("CyclomaticComplexMethod") annotation - Optimize NewMissionViewModel architecture: * Extract StateManager for UI state operations * Extract FieldUpdateHandler for form field updates * Extract ValidationHandler for input validation logic * Extract MissionExecutor for business logic execution * Reduce ViewModel to focused public interface * Add targeted @Suppress with clear justification - Benefits: * Better SOLID principle adherence * Enhanced separation of concerns * Improved testability through delegation * Cleaner preparation for multi-module architecture * All detekt complexity issues resolved
1 parent 88b4802 commit 2ed95b4

3 files changed

Lines changed: 625 additions & 433 deletions

File tree

app/src/main/kotlin/com/mustalk/seat/marsrover/presentation/ui/dashboard/components/MissionResultCard.kt

Lines changed: 133 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -46,72 +46,28 @@ import java.util.Locale
4646
/**
4747
* Card component that displays the result of a completed rover mission.
4848
* Shows final position, success status, and timestamp with expandable original input.
49+
*
50+
* @param missionResult The mission result data to display
51+
* @param modifier Optional modifier for styling
4952
*/
50-
@Suppress("CyclomaticComplexMethod")
5153
@Composable
5254
fun MissionResultCard(
5355
missionResult: MissionResult,
5456
modifier: Modifier = Modifier,
5557
) {
5658
val dateFormatter = SimpleDateFormat("MMM dd, yyyy HH:mm", Locale.getDefault())
5759
val formattedDate = dateFormatter.format(Date(missionResult.timestamp))
58-
var isInputExpanded by remember { mutableStateOf(false) }
5960

6061
MarsCard(
6162
title = stringResource(R.string.mission_result),
6263
modifier = modifier,
6364
contentDescription = stringResource(R.string.cd_mission_card)
6465
) {
65-
// Status row with icon
66-
Row(
67-
verticalAlignment = Alignment.CenterVertically,
68-
modifier = Modifier.fillMaxWidth()
69-
) {
70-
Icon(
71-
imageVector =
72-
if (missionResult.isSuccess) {
73-
Icons.Default.CheckCircle
74-
} else {
75-
Icons.Default.Warning
76-
},
77-
contentDescription =
78-
if (missionResult.isSuccess) {
79-
stringResource(R.string.cd_mission_success)
80-
} else {
81-
stringResource(R.string.cd_mission_failed)
82-
},
83-
tint =
84-
if (missionResult.isSuccess) {
85-
MaterialTheme.colorScheme.primary
86-
} else {
87-
MaterialTheme.colorScheme.error
88-
},
89-
modifier = Modifier.size(24.dp)
90-
)
91-
92-
Spacer(modifier = Modifier.width(8.dp))
93-
94-
Text(
95-
text =
96-
if (missionResult.isSuccess) {
97-
stringResource(R.string.mission_completed_successfully)
98-
} else {
99-
stringResource(R.string.mission_failed)
100-
},
101-
style = MaterialTheme.typography.titleMedium,
102-
fontWeight = FontWeight.SemiBold,
103-
color =
104-
if (missionResult.isSuccess) {
105-
MaterialTheme.colorScheme.primary
106-
} else {
107-
MaterialTheme.colorScheme.error
108-
}
109-
)
110-
}
66+
MissionStatusRow(isSuccess = missionResult.isSuccess)
11167

11268
Spacer(modifier = Modifier.height(6.dp))
11369

114-
// Final position
70+
// Final position and mission details
11571
Column(
11672
verticalArrangement = Arrangement.spacedBy(8.dp)
11773
) {
@@ -124,60 +80,7 @@ fun MissionResultCard(
12480
// Show original input if available
12581
missionResult.originalInput?.let { input ->
12682
Spacer(modifier = Modifier.height(8.dp))
127-
128-
// Input label with expand/collapse button
129-
Row(
130-
modifier = Modifier.fillMaxWidth(),
131-
horizontalArrangement = Arrangement.SpaceBetween,
132-
verticalAlignment = Alignment.CenterVertically
133-
) {
134-
Text(
135-
text = stringResource(R.string.mission_instructions),
136-
style = MaterialTheme.typography.bodyMedium,
137-
fontWeight = FontWeight.Medium,
138-
color = MaterialTheme.colorScheme.onSurface
139-
)
140-
141-
if (input.length > Constants.UI.MAX_INPUT_PREVIEW_LENGTH) {
142-
IconButton(
143-
onClick = { isInputExpanded = !isInputExpanded },
144-
modifier = Modifier.size(24.dp)
145-
) {
146-
Icon(
147-
imageVector = if (isInputExpanded) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown,
148-
contentDescription =
149-
if (isInputExpanded) {
150-
stringResource(
151-
R.string.cd_collapse_input
152-
)
153-
} else {
154-
stringResource(R.string.cd_expand_input)
155-
},
156-
tint = MaterialTheme.colorScheme.primary
157-
)
158-
}
159-
}
160-
}
161-
162-
// Input text - expandable
163-
Text(
164-
text =
165-
if (isInputExpanded || input.length <= Constants.UI.MAX_INPUT_PREVIEW_LENGTH) {
166-
input
167-
} else {
168-
input.take(Constants.UI.MAX_INPUT_PREVIEW_LENGTH) + "..."
169-
},
170-
style = MaterialTheme.typography.bodySmall,
171-
color = MaterialTheme.colorScheme.onSurfaceVariant,
172-
maxLines = if (isInputExpanded) Int.MAX_VALUE else 2,
173-
overflow = if (isInputExpanded) TextOverflow.Visible else TextOverflow.Ellipsis,
174-
modifier =
175-
if (input.length > Constants.UI.MAX_INPUT_PREVIEW_LENGTH) {
176-
Modifier.clickable { isInputExpanded = !isInputExpanded }
177-
} else {
178-
Modifier
179-
}
180-
)
83+
ExpandableInputSection(input = input)
18184
}
18285

18386
// Completed timestamp - right aligned, placed after mission instructions
@@ -193,6 +96,133 @@ fun MissionResultCard(
19396
}
19497
}
19598

99+
/**
100+
* Displays the mission status (success/failure) with appropriate icon and text.
101+
*
102+
* @param isSuccess Whether the mission was successful
103+
* @param modifier Optional modifier for styling
104+
*/
105+
@Composable
106+
private fun MissionStatusRow(
107+
isSuccess: Boolean,
108+
modifier: Modifier = Modifier,
109+
) {
110+
Row(
111+
verticalAlignment = Alignment.CenterVertically,
112+
modifier = modifier.fillMaxWidth()
113+
) {
114+
Icon(
115+
imageVector =
116+
if (isSuccess) {
117+
Icons.Default.CheckCircle
118+
} else {
119+
Icons.Default.Warning
120+
},
121+
contentDescription =
122+
if (isSuccess) {
123+
stringResource(R.string.cd_mission_success)
124+
} else {
125+
stringResource(R.string.cd_mission_failed)
126+
},
127+
tint =
128+
if (isSuccess) {
129+
MaterialTheme.colorScheme.primary
130+
} else {
131+
MaterialTheme.colorScheme.error
132+
},
133+
modifier = Modifier.size(24.dp)
134+
)
135+
136+
Spacer(modifier = Modifier.width(8.dp))
137+
138+
Text(
139+
text =
140+
if (isSuccess) {
141+
stringResource(R.string.mission_completed_successfully)
142+
} else {
143+
stringResource(R.string.mission_failed)
144+
},
145+
style = MaterialTheme.typography.titleMedium,
146+
fontWeight = FontWeight.SemiBold,
147+
color =
148+
if (isSuccess) {
149+
MaterialTheme.colorScheme.primary
150+
} else {
151+
MaterialTheme.colorScheme.error
152+
}
153+
)
154+
}
155+
}
156+
157+
/**
158+
* Displays mission input instructions with expand/collapse functionality for long inputs.
159+
*
160+
* @param input The input string to display
161+
* @param modifier Optional modifier for styling
162+
*/
163+
@Composable
164+
private fun ExpandableInputSection(
165+
input: String,
166+
modifier: Modifier = Modifier,
167+
) {
168+
var isInputExpanded by remember { mutableStateOf(false) }
169+
170+
Column(modifier = modifier) {
171+
// Input label with expand/collapse button
172+
Row(
173+
modifier = Modifier.fillMaxWidth(),
174+
horizontalArrangement = Arrangement.SpaceBetween,
175+
verticalAlignment = Alignment.CenterVertically
176+
) {
177+
Text(
178+
text = stringResource(R.string.mission_instructions),
179+
style = MaterialTheme.typography.bodyMedium,
180+
fontWeight = FontWeight.Medium,
181+
color = MaterialTheme.colorScheme.onSurface
182+
)
183+
184+
// Show expand/collapse button only for long inputs
185+
if (input.length > Constants.UI.MAX_INPUT_PREVIEW_LENGTH) {
186+
IconButton(
187+
onClick = { isInputExpanded = !isInputExpanded },
188+
modifier = Modifier.size(24.dp)
189+
) {
190+
Icon(
191+
imageVector = if (isInputExpanded) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown,
192+
contentDescription =
193+
if (isInputExpanded) {
194+
stringResource(R.string.cd_collapse_input)
195+
} else {
196+
stringResource(R.string.cd_expand_input)
197+
},
198+
tint = MaterialTheme.colorScheme.primary
199+
)
200+
}
201+
}
202+
}
203+
204+
// Input text - expandable for long content
205+
Text(
206+
text =
207+
if (isInputExpanded || input.length <= Constants.UI.MAX_INPUT_PREVIEW_LENGTH) {
208+
input
209+
} else {
210+
input.take(Constants.UI.MAX_INPUT_PREVIEW_LENGTH) + "..."
211+
},
212+
style = MaterialTheme.typography.bodySmall,
213+
color = MaterialTheme.colorScheme.onSurfaceVariant,
214+
maxLines = if (isInputExpanded) Int.MAX_VALUE else 2,
215+
overflow = if (isInputExpanded) TextOverflow.Visible else TextOverflow.Ellipsis,
216+
modifier =
217+
if (input.length > Constants.UI.MAX_INPUT_PREVIEW_LENGTH) {
218+
Modifier.clickable { isInputExpanded = !isInputExpanded }
219+
} else {
220+
Modifier
221+
}
222+
)
223+
}
224+
}
225+
196226
@Preview(name = "Mission Result Card - Success", showBackground = true)
197227
@Composable
198228
private fun MissionResultCardSuccessPreview() {

0 commit comments

Comments
 (0)