1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package com.arblant.railwayipscanner
import android.content.Context
import android.graphics.drawable.GradientDrawable
import android.text.SpannableString
import android.text.Spanned
import android.text.style.ForegroundColorSpan
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
/**
* Shared row/chip presentation for the results ledger, the workbench
* and the IP picker — one component grammar everywhere (spec).
*/
object UiKit {
fun dp(context: Context, value: Float): Int =
(value * context.resources.displayMetrics.density + 0.5f).toInt()
/** Latency tiers (spec): fast <120, mid 120–300, slow 300–600, poor >600. */
fun tierColorRes(delayMs: Long): Int = when {
delayMs < 120 -> R.color.latency_fast
delayMs <= 300 -> R.color.latency_mid
delayMs <= 600 -> R.color.latency_slow
else -> R.color.latency_poor
}
fun tierColor(context: Context, delayMs: Long): Int =
ContextCompat.getColor(context, tierColorRes(delayMs))
private fun chipBackground(context: Context, color: Int): GradientDrawable {
val bg = GradientDrawable()
bg.shape = GradientDrawable.RECTANGLE
bg.cornerRadius = dp(context, 7f).toFloat()
bg.setColor(ColorUtils.setAlphaComponent(color, 36)) // 14% fill
bg.setStroke(dp(context, 1f).coerceAtLeast(1), ColorUtils.setAlphaComponent(color, 89)) // 35% edge
return bg
}
/** Live latency chip: tier-tinted fill/edge/text, fixed rail width. */
fun styleLatencyChip(view: TextView, delayMs: Long) {
val color = tierColor(view.context, delayMs)
view.background = chipBackground(view.context, color)
view.setTextColor(color)
view.text = "$delayMs ms"
}
/** Measuring state: three quiet dots in tertiary ink. */
fun styleMeasuringChip(view: TextView) {
val ctx = view.context
val ink = ContextCompat.getColor(ctx, R.color.ink_3)
val bg = GradientDrawable()
bg.shape = GradientDrawable.RECTANGLE
bg.cornerRadius = dp(ctx, 7f).toFloat()
bg.setColor(ContextCompat.getColor(ctx, R.color.panel_raised))
bg.setStroke(dp(ctx, 1f).coerceAtLeast(1), ContextCompat.getColor(ctx, R.color.border))
view.background = bg
view.setTextColor(ink)
view.text = "• • •"
}
/** Dead state after a failed re-test: poor tier, plain word. */
fun styleDeadChip(view: TextView) {
val color = ContextCompat.getColor(view.context, R.color.latency_poor)
view.background = chipBackground(view.context, color)
view.setTextColor(color)
view.text = "Dead"
}
/** Stale state: the assigned IP vanished from the latest scan. */
fun styleStaleChip(view: TextView) {
val color = ContextCompat.getColor(view.context, R.color.latency_slow)
view.background = chipBackground(view.context, color)
view.setTextColor(color)
view.text = "stale"
}
/**
* Typeset IP: bright octets, dimmed dots (spec craft detail #1).
* Tabular mono comes from the view's fontFamily/fontFeatureSettings.
*/
fun dimmedIp(context: Context, ip: String): SpannableString {
val span = SpannableString(ip)
val dotColor = ContextCompat.getColor(context, R.color.ink_3)
for (i in ip.indices) {
if (ip[i] == '.') {
span.setSpan(
ForegroundColorSpan(dotColor), i, i + 1,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
return span
}
}