neiam
/apollos_crib
It's Apollo's Crib
public · Issues · Pulls 1 · Labels · Forks · Compare · Actions failure · Badges · Packages
Log in to mark this repository.
Draw the weather instead of spelling it
400c82c · Gabriel Morell · 2026-09-08 21:06
Message
{commit_body(@commit)}
Files changed
modified
clients/android/app/src/main/java/io/neiam/apolloscrib/ui/RichCard.kt
+19
−9
| 287 | 287 | } |
| 288 | 288 | |
| 289 | 289 | if (facts.headline != null) { |
| 290 | − Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { | |
| 291 | − Text(facts.headline, style = MaterialTheme.typography.displaySmall) | |
| 292 | − | |
| 293 | − facts.caption?.let { | |
| 294 | − Text( | |
| 295 | − it, | |
| 296 | − style = MaterialTheme.typography.titleSmall, | |
| 297 | − color = palette.dim | |
| 298 | − ) | |
| 290 | + Row(verticalAlignment = Alignment.CenterVertically) { | |
| 291 | + Column( | |
| 292 | + Modifier.weight(1f), | |
| 293 | + verticalArrangement = Arrangement.spacedBy(2.dp) | |
| 294 | + ) { | |
| 295 | + Text(facts.headline, style = MaterialTheme.typography.displaySmall) | |
| 296 | + | |
| 297 | + facts.caption?.let { | |
| 298 | + Text( | |
| 299 | + it, | |
| 300 | + style = MaterialTheme.typography.titleSmall, | |
| 301 | + color = palette.dim | |
| 302 | + ) | |
| 303 | + } | |
| 299 | 304 | } |
| 305 | + | |
| 306 | + // The picture of the answer, opposite the number. Sized to | |
| 307 | + // the headline rather than to the text beside it: from | |
| 308 | + // across a room this is the part that reads. | |
| 309 | + facts.glyph?.let { glyph -> Glyph(glyph, 52.dp, palette.accent) } | |
| 300 | 310 | } |
| 301 | 311 | } else { |
| 302 | 312 | facts.caption?.let { |
modified
clients/android/app/src/main/java/io/neiam/apolloscrib/ui/RichFacts.kt
+51
−1
| 30 | 30 | val iconRes: Int, |
| 31 | 31 | val headline: String? = null, |
| 32 | 32 | val caption: String? = null, |
| 33 | − val facts: List<Fact> = emptyList() | |
| 33 | + val facts: List<Fact> = emptyList(), | |
| 34 | + /** | |
| 35 | + * A picture of the answer, drawn large beside the headline. | |
| 36 | + * | |
| 37 | + * Only where there is one worth drawing. "Clouds" is a word; a cloud is | |
| 38 | + * the same fact read without reading, which is what a card glanced at | |
| 39 | + * from across a room needs. Null everywhere the answer is a number and a | |
| 40 | + * picture would only be decoration. | |
| 41 | + */ | |
| 42 | + val glyph: Int? = null | |
| 34 | 43 | ) { |
| 35 | 44 | data class Fact(val label: String, val value: String) |
| 36 | 45 | } |
| 60 | 69 | iconRes = R.drawable.fa_temperature_half, |
| 61 | 70 | headline = now.temp?.let { "${it.roundToInt()}$degrees" }, |
| 62 | 71 | caption = now.weather, |
| 72 | + glyph = skyGlyph(now.weather), | |
| 63 | 73 | facts = listOfNotNull( |
| 64 | 74 | now.feel?.let { RichFacts.Fact("Feels like", "${it.roundToInt()}$degrees") }, |
| 65 | 75 | now.hum?.let { RichFacts.Fact("Humidity", "${it.roundToInt()}%") }, |
| 182 | 192 | ) |
| 183 | 193 | } |
| 184 | 194 | |
| 195 | +/** | |
| 196 | + * What the sky is doing, as a picture. | |
| 197 | + * | |
| 198 | + * OpenWeather's condition groups, which is what the publisher sends through | |
| 199 | + * untouched. Matched loosely on purpose: agencies and forecasts spell these | |
| 200 | + * inconsistently, and a card showing a cloud for something drizzle-shaped is | |
| 201 | + * right enough, where showing nothing at all is a hole in the card. | |
| 202 | + * | |
| 203 | + * Null for anything unrecognised rather than a guess. A wrong picture is read | |
| 204 | + * before the word beside it and believed instead of it. | |
| 205 | + */ | |
| 206 | +private fun skyGlyph(condition: String?): Int? = when { | |
| 207 | + condition == null -> null | |
| 208 | + condition.contains("thunder", true) || condition.contains("storm", true) -> | |
| 209 | + R.drawable.fa_cloud_bolt | |
| 210 | + condition.contains("drizzle", true) -> R.drawable.fa_cloud_rain | |
| 211 | + condition.contains("rain", true) || condition.contains("shower", true) -> | |
| 212 | + R.drawable.fa_cloud_showers_heavy | |
| 213 | + condition.contains("snow", true) || condition.contains("sleet", true) -> | |
| 214 | + R.drawable.fa_snowflake | |
| 215 | + condition.contains("tornado", true) || condition.contains("squall", true) -> | |
| 216 | + R.drawable.fa_tornado | |
| 217 | + // Mist, fog, haze, smoke, dust, sand, ash: all the ways air stops being | |
| 218 | + // clear, and one glyph for the lot of them. | |
| 219 | + condition.contains("mist", true) || condition.contains("fog", true) || | |
| 220 | + condition.contains("haze", true) || condition.contains("smoke", true) || | |
| 221 | + condition.contains("dust", true) || condition.contains("sand", true) || | |
| 222 | + condition.contains("ash", true) -> R.drawable.fa_smog | |
| 223 | + condition.contains("clear", true) || condition.contains("sun", true) -> | |
| 224 | + R.drawable.fa_sun | |
| 225 | + // "Few clouds" and "scattered clouds" are not the same sky as "overcast", | |
| 226 | + // and the sun showing through says which. | |
| 227 | + condition.contains("few", true) || condition.contains("scattered", true) || | |
| 228 | + condition.contains("part", true) -> R.drawable.fa_cloud_sun | |
| 229 | + condition.contains("cloud", true) || condition.contains("overcast", true) -> | |
| 230 | + R.drawable.fa_cloud | |
| 231 | + condition.contains("wind", true) -> R.drawable.fa_wind | |
| 232 | + else -> null | |
| 233 | +} | |
| 234 | + | |
| 185 | 235 | /** |
| 186 | 236 | * Whatever the compact renderer made of it, laid out with room. |
| 187 | 237 | * |
modified
clients/android/app/src/main/java/io/neiam/apolloscrib/widget/RichBoardWidgetService.kt
+9
−0
| 85 | 85 | if (card.caption == null) android.view.View.GONE else android.view.View.VISIBLE |
| 86 | 86 | ) |
| 87 | 87 | |
| 88 | + // The picture of the answer, where there is one worth drawing. | |
| 89 | + if (card.glyph != null) { | |
| 90 | + setImageViewResource(R.id.rich_glyph, card.glyph) | |
| 91 | + setInt(R.id.rich_glyph, "setColorFilter", palette.accent.toArgb()) | |
| 92 | + setViewVisibility(R.id.rich_glyph, android.view.View.VISIBLE) | |
| 93 | + } else { | |
| 94 | + setViewVisibility(R.id.rich_glyph, android.view.View.GONE) | |
| 95 | + } | |
| 96 | + | |
| 88 | 97 | removeAllViews(R.id.rich_facts) |
| 89 | 98 | |
| 90 | 99 | // Four, because a widget card does not scroll and a fifth row |
added
clients/android/app/src/main/res/drawable/fa_cloud.xml
+12
−0
| 1 | +<!-- Generated by tools/fa_to_vector.py from Font Awesome Free 6.2.0 (cloud). | |
| 2 | + Font Awesome Free icons are CC BY 4.0. Do not edit by hand. --> | |
| 3 | +<vector xmlns:android="http://schemas.android.com/apk/res/android" | |
| 4 | + android:width="24dp" | |
| 5 | + android:height="24dp" | |
| 6 | + android:viewportWidth="640" | |
| 7 | + android:viewportHeight="512" | |
| 8 | + android:tint="?android:attr/colorControlNormal"> | |
| 9 | + <path | |
| 10 | + android:fillColor="@android:color/white" | |
| 11 | + android:pathData="M0 336c0 79.5 64.5 144 144 144H512c70.7 0 128-57.3 128-128c0-61.9-44-113.6-102.4-125.4c4.1-10.7 6.4-22.4 6.4-34.6c0-53-43-96-96-96c-19.7 0-38.1 6-53.3 16.2C367 64.2 315.3 32 256 32C167.6 32 96 103.6 96 192c0 2.7 .1 5.4 .2 8.1C40.2 219.8 0 273.2 0 336z" /> | |
| 12 | +</vector> |
added
clients/android/app/src/main/res/drawable/fa_cloud_bolt.xml
+12
−0
| 1 | +<!-- Generated by tools/fa_to_vector.py from Font Awesome Free 6.2.0 (cloud-bolt). | |
| 2 | + Font Awesome Free icons are CC BY 4.0. Do not edit by hand. --> | |
| 3 | +<vector xmlns:android="http://schemas.android.com/apk/res/android" | |
| 4 | + android:width="24dp" | |
| 5 | + android:height="24dp" | |
| 6 | + android:viewportWidth="512" | |
| 7 | + android:viewportHeight="512" | |
| 8 | + android:tint="?android:attr/colorControlNormal"> | |
| 9 | + <path | |
| 10 | + android:fillColor="@android:color/white" | |
| 11 | + android:pathData="M0 224c0 53 43 96 96 96h47.2L290 202.5c17.6-14.1 42.6-14 60.2 .2s22.8 38.6 12.8 58.8L333.7 320H352h64c53 0 96-43 96-96s-43-96-96-96c-.5 0-1.1 0-1.6 0c1.1-5.2 1.6-10.5 1.6-16c0-44.2-35.8-80-80-80c-24.3 0-46.1 10.9-60.8 28C256.5 24.3 219.1 0 176 0C114.1 0 64 50.1 64 112c0 7.1 .7 14.1 1.9 20.8C27.6 145.4 0 181.5 0 224zm330.1 3.6c-5.8-4.7-14.2-4.7-20.1-.1l-160 128c-5.3 4.2-7.4 11.4-5.1 17.8s8.3 10.7 15.1 10.7h70.1L177.7 488.8c-3.4 6.7-1.6 14.9 4.3 19.6s14.2 4.7 20.1 .1l160-128c5.3-4.2 7.4-11.4 5.1-17.8s-8.3-10.7-15.1-10.7H281.9l52.4-104.8c3.4-6.7 1.6-14.9-4.2-19.6z" /> | |
| 12 | +</vector> |
added
clients/android/app/src/main/res/drawable/fa_cloud_rain.xml
+12
−0
Click to load diff…
added
clients/android/app/src/main/res/drawable/fa_cloud_showers_heavy.xml
+12
−0
Click to load diff…
added
clients/android/app/src/main/res/drawable/fa_smog.xml
+12
−0
Click to load diff…
added
clients/android/app/src/main/res/drawable/fa_snowflake.xml
+12
−0
Click to load diff…
added
clients/android/app/src/main/res/drawable/fa_tornado.xml
+12
−0
Click to load diff…
modified
clients/android/app/src/main/res/layout/widget_rich_card.xml
+33
−12
Click to load diff…
added
clients/android/app/src/test/java/io/neiam/apolloscrib/ui/SkyGlyphTest.kt
+77
−0
Click to load diff…
modified
clients/android/tools/fa_to_vector.py
+4
−0
Click to load diff…
Parents: 3ead170