Coverage Summary for Class: GoalAmountInput (com.georgeci.moneysurfer.feature.goal)
| Class |
Class, %
|
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| GoalAmountInput |
100%
(1/1)
|
100%
(2/2)
|
88.9%
(16/18)
|
100%
(12/12)
|
97.7%
(85/87)
|
package com.georgeci.moneysurfer.feature.goal
import com.georgeci.moneysurfer.domain.model.GoalStatus
import com.georgeci.moneysurfer.domain.primitives.Money
import com.georgeci.moneysurfer.uikit.components.goal.SurferGoalStatus
import kotlinx.datetime.LocalDate
import moneysurfer.feature.goal.generated.resources.Res
import moneysurfer.feature.goal.generated.resources.goal_status_active
import moneysurfer.feature.goal.generated.resources.goal_status_archived
import moneysurfer.feature.goal.generated.resources.goal_status_completed
import moneysurfer.feature.goal.generated.resources.goal_status_paused
import org.jetbrains.compose.resources.StringResource
/** The design system carries its own status enum so `uikit` stays free of `domain`. */
internal fun GoalStatus.toUi(): SurferGoalStatus = when (this) {
GoalStatus.ACTIVE -> SurferGoalStatus.Active
GoalStatus.COMPLETED -> SurferGoalStatus.Completed
GoalStatus.PAUSED -> SurferGoalStatus.Paused
GoalStatus.ARCHIVED -> SurferGoalStatus.Archived
}
internal fun GoalStatus.labelRes(): StringResource = when (this) {
GoalStatus.ACTIVE -> Res.string.goal_status_active
GoalStatus.COMPLETED -> Res.string.goal_status_completed
GoalStatus.PAUSED -> Res.string.goal_status_paused
GoalStatus.ARCHIVED -> Res.string.goal_status_archived
}
/** `05 Aug 2026` — the same shape the transaction screens use. */
internal fun formatGoalDate(date: LocalDate): String {
val month = date.month.name.lowercase().replaceFirstChar { it.uppercase() }.take(MONTH_ABBREVIATION)
val day = date.day.toString().padStart(2, '0')
return "$day $month ${date.year}"
}
private const val MONTH_ABBREVIATION = 3
/**
* Text-field input for goal amounts: digits and at most one decimal separator. Goal amounts are
* never negative — the withdraw screen flips the sign itself — so a minus is simply dropped.
*/
internal object GoalAmountInput {
fun sanitize(raw: String): String {
val digits = StringBuilder()
var separatorUsed = false
for (ch in raw) {
when {
ch.isDigit() -> digits.append(ch)
(ch == '.' || ch == ',') && !separatorUsed -> {
separatorUsed = true
digits.append('.')
}
}
}
return digits.toString()
}
/** `null` while the field is empty or still half-typed (`"."`). */
fun parse(value: String): Money? = value.toDoubleOrNull()
?.takeIf { it > 0.0 }
?.let(Money::fromDouble)
}