Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ Request body is a JSON object. Only `message` is required.
| `widthPercent` | number | 60 (TV setting) | Card width as a percentage of screen width, 10 to 100. Height follows the content. |
| `background` | string (hex) | `#FFFFFF` (TV setting) | Card background. |
| `textColor` | string (hex) | `#111111` (TV setting) | Title and message color. |
| `accent` | string (hex) | none (TV setting) | Color of a vertical stripe on the card's left edge. Omit for no stripe. |
| `accent` | string (hex) | none (TV setting) | Color of a vertical stripe on the card's left edge. Omit the field to use the TV's default; send `"none"`, `""` or `null` to force no stripe even when the TV has a default. |
| `dim` | number | 0 (TV setting) | Opacity, 0 to 1, of a full-screen black layer behind the card. `0.9` hides what is playing almost completely. |
| `sound` | string | `default` (TV setting) | `none`, `default` for the bundled chime, or an `http(s)` URL of an audio file to stream. |
| `speak` | boolean | `false` | Read the title and message aloud with the TV's text-to-speech engine after the sound finishes. |
Expand Down
1 change: 1 addition & 0 deletions client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ are in the root README under "Hosted client".
3. **Send.** Posts to every selected device in parallel and lists success or the actual error
per device. **Clear card by ID** sends `DELETE /notify/{id}` for the ID in the composer,
which is how you remove a persistent card.
Turning the accent switch off sends `"accent": "none"`, which overrides any default accent set on the TV.
4. **History tab.** Every send in this browser session, with per-device results. Re-send it
as-is, or load it back into the composer.
5. **curl tab.** The composed notification as a working `curl` command against the chosen
Expand Down
2 changes: 1 addition & 1 deletion client/src/lib/payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function toWire(payload: NotificationPayload): WirePayload {
wire.widthPercent = payload.widthPercent
wire.background = payload.background
wire.textColor = payload.textColor
text('accent')
wire.accent = payload.accent.trim() || 'none'
wire.dim = Number(payload.dim.toFixed(2))
wire.sound = payload.sound.trim() || 'none'
wire.speak = payload.speak
Expand Down
4 changes: 2 additions & 2 deletions client/src/lib/tvApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ async function readError(response: Response): Promise<string> {
function describeNetworkError(error: unknown): string {
if (error instanceof DOMException && error.name === 'TimeoutError') return 'Timed out waiting for the TV'
if (error instanceof TypeError && servedOverHttps()) {
return 'Blocked: this page is HTTPS and the TV is HTTP. Allow insecure content for this site in your browser.'
return 'Blocked by the browser (this page is HTTPS, the TV is HTTP) or nothing answered on that port. Check the port is 8090, then allow insecure content for this site.'
}
if (error instanceof TypeError) return 'Unreachable (network error or blocked by the browser)'
if (error instanceof TypeError) return 'Unreachable: check the IP and that the port is 8090, not 5555'
return error instanceof Error ? error.message : String(error)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class NotificationPayloadParser(
widthPercent = int(json, "widthPercent", 10, 100) ?: defaults.widthPercent,
background = color(json, "background") ?: defaults.background,
textColor = color(json, "textColor") ?: defaults.textColor,
accent = color(json, "accent") ?: defaults.accent,
accent = accent(json, defaults.accent),
dim = fraction(json, "dim") ?: defaults.dim,
sound = sound(json) ?: defaults.sound,
speak = boolean(json, "speak") ?: false,
Expand Down Expand Up @@ -92,6 +92,17 @@ class NotificationPayloadParser(
return HexColor.parse(value) ?: throw FieldError("field '$key' must be a hex color like #RRGGBB")
}

private fun accent(json: JSONObject, default: Int?): Int? {
if (!json.has("accent")) return default
if (json.isNull("accent")) return null
val value = json.get("accent") as? String
?: throw FieldError("field 'accent' must be a hex color like #RRGGBB, \"none\" or null")
val trimmed = value.trim()
if (trimmed.isEmpty() || trimmed.equals("none", ignoreCase = true)) return null
return HexColor.parse(trimmed)
?: throw FieldError("field 'accent' must be a hex color like #RRGGBB, \"none\" or null")
}

private fun position(json: JSONObject): Position? {
val value = string(json, "position") ?: return null
return Position.fromWire(value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ fun StepperRow(
) {
ListItem(
selected = false,
onClick = onIncrease,
onClick = {},
modifier = modifier.onKeyEvent { event ->
if (event.type != KeyEventType.KeyDown) return@onKeyEvent false
when (event.key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,22 @@ class NotificationPayloadParserTest {
assertEquals(200, n.title!!.length)
}

@Test
fun explicitEmptyAccentOverridesTheDefault() {
val withAccent = NotificationDefaults(accent = 0xFFFF1744.toInt())
assertEquals(0xFFFF1744.toInt(), success("""{"message":"m"}""", withAccent).accent)
assertNull(success("""{"message":"m","accent":""}""", withAccent).accent)
assertNull(success("""{"message":"m","accent":"none"}""", withAccent).accent)
assertNull(success("""{"message":"m","accent":null}""", withAccent).accent)
assertEquals(0xFF00C853.toInt(), success("""{"message":"m","accent":"#00C853"}""", withAccent).accent)
}

@Test
fun invalidAccentNamesTheField() {
assertTrue(failure("""{"message":"m","accent":"stripe"}""").startsWith("field 'accent' must be"))
assertTrue(failure("""{"message":"m","accent":12}""").startsWith("field 'accent' must be"))
}

@Test
fun blankOptionalStringsCountAsAbsent() {
val n = success("""{"message":"m","image":"","accent":"","title":""}""")
Expand Down
Loading