Skip to content

Commit 8c87046

Browse files
committed
Put the train map back as it was
The Trains tab, the map layers and the train widget go back to before the widget redraw; the trip and search widgets stay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011C2fAvFFcMRkaF7qSvRA1v
1 parent a700be8 commit 8c87046

5 files changed

Lines changed: 106 additions & 389 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,7 @@ journeys with 1, 2, 3 or more rides, walking between nearby stops for changes.
145145
- **Your trip**: once you start a trip, where you're up to (walk to the
146146
stop, the bus is 2 stops away, 4 stops to go), the rides ahead, when you'll
147147
get there, and End.
148-
- **Train map**: the Trains tab's map at the widget's size, with every train
149-
on it.
148+
- **Train map**: the train diagram with every train on it, live.
150149
- **Search**: a search bar that opens straight into search.
151150
- **Quick settings tile**: "27H · 6 min" in the notification shade.
152151
- **App shortcuts**: long-press the icon for Directions, Live, Trains, Search

‎app/src/main/java/nz/aryan/akllive/system/MoreWidgets.kt‎

Lines changed: 12 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package nz.aryan.akllive.system
22

3-
import android.appwidget.AppWidgetManager
43
import android.content.Context
54
import android.content.Intent
65
import android.graphics.Bitmap
@@ -22,7 +21,6 @@ import androidx.glance.LocalSize
2221
import androidx.glance.action.ActionParameters
2322
import androidx.glance.action.clickable
2423
import androidx.glance.appwidget.GlanceAppWidget
25-
import androidx.glance.appwidget.GlanceAppWidgetManager
2624
import androidx.glance.appwidget.GlanceAppWidgetReceiver
2725
import androidx.glance.appwidget.SizeMode
2826
import androidx.glance.appwidget.action.ActionCallback
@@ -66,7 +64,6 @@ import nz.aryan.akllive.data.MapData
6664
import nz.aryan.akllive.data.Nz
6765
import nz.aryan.akllive.data.Train
6866
import nz.aryan.akllive.data.TrainRepo
69-
import nz.aryan.akllive.ui.TrainArt
7067

7168
private fun open(ctx: Context, link: String) =
7269
actionStartActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link), ctx, MainActivity::class.java))
@@ -106,55 +103,35 @@ class SearchWidgetReceiver : GlanceAppWidgetReceiver() {
106103

107104
// ======================= the train map =======================
108105

109-
/** The train map, as the app draws it, with every train on it, live. */
106+
/** The train network diagram with every train on it, live. */
110107
class TrainWidget : GlanceAppWidget() {
111108
override val sizeMode = SizeMode.Exact
112109

113110
override suspend fun provideGlance(context: Context, id: GlanceId) {
114111
val trains = TrainsNow.get(context)
115-
val (w, h) = widgetSize(context, id)
116-
// the app's own Diagram view; the plain diagram only if the map can't be drawn
117-
val map = try { TrainArt.render(context, trains ?: emptyList(), w, h, top = HEADER) } catch (e: Exception) { null }
118-
provideContent { GlanceTheme { Body(context, map ?: drawTrains(trains ?: emptyList()), map != null, trains) } }
119-
}
120-
121-
/** The widget's size on the home screen, in dp (portrait: its narrowest width and tallest height). */
122-
private fun widgetSize(ctx: Context, id: GlanceId): Pair<Int, Int> = try {
123-
val wid = GlanceAppWidgetManager(ctx).getAppWidgetId(id)
124-
val o = AppWidgetManager.getInstance(ctx).getAppWidgetOptions(wid)
125-
val w = o.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)
126-
val h = o.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)
127-
if (w > 80 && h > 80) w to h else 320 to 260
128-
} catch (e: Exception) {
129-
320 to 260
112+
val map = drawTrains(trains ?: emptyList())
113+
provideContent { GlanceTheme { Body(context, map, trains) } }
130114
}
131115

132116
@Composable
133-
private fun Body(ctx: Context, map: Bitmap, fullBleed: Boolean, trains: List<Train>?) {
134-
Box(GlanceModifier.fillMaxSize().cornerRadius(24.dp).background(ColorProvider(Color(0xFF172234)))
135-
.clickable(open(ctx, "akllive://trains"))) {
136-
if (fullBleed) Image(ImageProvider(map), "Train map", GlanceModifier.fillMaxSize(), contentScale = ContentScale.Crop)
137-
else Column(GlanceModifier.fillMaxSize().padding(start = 10.dp, end = 10.dp, bottom = 10.dp, top = HEADER.dp)) {
138-
Image(ImageProvider(map), "Train map", GlanceModifier.fillMaxSize(), contentScale = ContentScale.Fit)
139-
}
140-
Row(GlanceModifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 10.dp), verticalAlignment = Alignment.CenterVertically) {
117+
private fun Body(ctx: Context, map: Bitmap, trains: List<Train>?) {
118+
Column(GlanceModifier.fillMaxSize().cornerRadius(24.dp).background(ColorProvider(Color(0xFF0B1220))).padding(10.dp)
119+
.clickable(open(ctx, "akllive://trains"))) {
120+
Row(GlanceModifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
141121
Text("Ngā Tereina", GlanceModifier.defaultWeight(),
142-
style = TextStyle(color = ColorProvider(Color.White), fontWeight = FontWeight.Bold, fontSize = 15.sp))
122+
style = TextStyle(color = ColorProvider(Color.White), fontWeight = FontWeight.Bold, fontSize = 14.sp))
143123
Text(when {
144-
trains == null -> "Tap ↻ for trains"
124+
trains == null -> "Open the app first"
145125
else -> "${trains.size} running · ${Nz.time(TrainsNow.at)}"
146-
}, style = TextStyle(color = ColorProvider(Color(0xFFE6ECF5)), fontSize = 11.sp))
126+
}, style = TextStyle(color = ColorProvider(Color(0xFFB9C2CF)), fontSize = 11.sp))
147127
Spacer(GlanceModifier.width(8.dp))
148128
Text("↻", GlanceModifier.clickable(actionRunCallback<RefreshMore>()),
149129
style = TextStyle(color = ColorProvider(Color.White), fontWeight = FontWeight.Bold, fontSize = 18.sp))
150130
}
131+
Spacer(GlanceModifier.height(4.dp))
132+
Image(ImageProvider(map), "Train map", GlanceModifier.fillMaxSize(), contentScale = ContentScale.Fit)
151133
}
152134
}
153-
154-
private companion object {
155-
/** room for the header over the top of the map, dp */
156-
const val HEADER = 40
157-
}
158135
}
159136

160137
class TrainWidgetReceiver : GlanceAppWidgetReceiver() {

‎app/src/main/java/nz/aryan/akllive/ui/LiveMap.kt‎

Lines changed: 57 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,6 @@ object MapStyles {
157157
val QUIET_DARK = Quiet("#172234", "#0B1625", "#172A24", "#233049", "#1E2A40", "#2E3C57", "#1C283B", "#6C7B94", "#46709A")
158158

159159
/** A quiet map for the train diagram: land, water, parks, faint roads and suburb names, so the lines stand out. */
160-
fun diagramJson(dark: Boolean): String = diagram(dark)
161-
162160
private fun diagram(dark: Boolean): String {
163161
val c = if (dark) QUIET_DARK else QUIET_LIGHT
164162
fun arr(vararg v: Any) = JSONArray().also { a -> v.forEach { a.put(it) } }
@@ -525,76 +523,61 @@ private fun byZoom(p: String) = Expression.interpolate(Expression.linear(), Expr
525523
*/
526524
private fun drawLayers(s: Style, lines: List<MapLine>, links: List<MapLine>, stops: List<MapStop>, basemap: Basemap, dark: Boolean) {
527525
fun put(layer: Layer) = if (s.getLayer("crowd-dots") != null) s.addLayerBelow(layer, "crowd-dots") else s.addLayer(layer)
526+
val zoom = Expression.zoom()
528527
if (s.getSource("akl-lines") == null) {
528+
val dg = basemap == Basemap.Diagram
529+
val caseCol = when {
530+
dg -> if (dark) MapStyles.QUIET_DARK.bg else MapStyles.QUIET_LIGHT.bg
531+
basemap == Basemap.Streets && !dark -> "#FFFFFF"
532+
else -> "#0B1628"
533+
}
529534
for (id in listOf("akl-lines", "akl-links", "akl-stops")) s.addSource(GeoJsonSource(id))
530-
aklLayers(basemap, dark).forEach { put(it) }
535+
val round = arrayOf(PropertyFactory.lineCap(Property.LINE_CAP_ROUND), PropertyFactory.lineJoin(Property.LINE_JOIN_ROUND))
536+
put(LineLayer("akl-lines-case", "akl-lines").withProperties(*round,
537+
PropertyFactory.lineColor(Expression.switchCase(Expression.toBool(Expression.get("z")), Expression.color(android.graphics.Color.parseColor(caseCol)),
538+
Expression.color(android.graphics.Color.parseColor("#0B1628")))),
539+
PropertyFactory.lineOpacity(Expression.switchCase(Expression.toBool(Expression.get("z")), Expression.literal(if (dg) 1f else 0.8f), Expression.literal(0.55f))),
540+
PropertyFactory.lineWidth(byZoom("cw")),
541+
PropertyFactory.lineOffset(byZoom("o"))))
542+
put(LineLayer("akl-lines", "akl-lines").withProperties(*round,
543+
PropertyFactory.lineColor(Expression.toColor(Expression.get("c"))),
544+
PropertyFactory.lineWidth(byZoom("w")),
545+
PropertyFactory.lineOffset(byZoom("o"))))
546+
put(LineLayer("akl-links-case", "akl-links").withProperties(*round,
547+
PropertyFactory.lineColor(Expression.toColor(Expression.get("c"))),
548+
PropertyFactory.lineWidth(Expression.interpolate(Expression.linear(), zoom,
549+
Expression.stop(9, 6f), Expression.stop(12, 11.4f), Expression.stop(16, 19f)))))
550+
put(LineLayer("akl-links", "akl-links").withProperties(*round,
551+
PropertyFactory.lineColor(Color.White.toArgb()),
552+
PropertyFactory.lineWidth(Expression.interpolate(Expression.linear(), zoom,
553+
Expression.stop(9, 2.8f), Expression.stop(12, 6.2f), Expression.stop(16, 13.8f)))))
554+
put(CircleLayer("akl-stops", "akl-stops").withProperties(
555+
PropertyFactory.circleColor(Color.White.toArgb()),
556+
PropertyFactory.circleStrokeColor(Expression.toColor(Expression.get("c"))),
557+
PropertyFactory.circleRadius(byZoom("rad")),
558+
PropertyFactory.circleStrokeWidth(Expression.interpolate(Expression.linear(), zoom,
559+
Expression.stop(9, num("sw9")), Expression.stop(13, num("sw13"))))))
560+
// names: the most important stations win the space, the rest appear as you zoom in
561+
val darkLabels = basemap != Basemap.Satellite && dark
562+
put(SymbolLayer("akl-stop-labels", "akl-stops").withProperties(
563+
PropertyFactory.textField(Expression.step(zoom, Expression.toString(Expression.get("l0")),
564+
Expression.stop(10.4, Expression.toString(Expression.get("l1"))), Expression.stop(11, Expression.toString(Expression.get("label"))))),
565+
PropertyFactory.textFont(arrayOf("Noto Sans Bold")),
566+
PropertyFactory.textSize(Expression.interpolate(Expression.linear(), zoom,
567+
Expression.stop(10, num("ts10")), Expression.stop(14, num("ts14")))),
568+
PropertyFactory.textVariableAnchor(arrayOf(Property.TEXT_ANCHOR_LEFT, Property.TEXT_ANCHOR_RIGHT, Property.TEXT_ANCHOR_TOP,
569+
Property.TEXT_ANCHOR_BOTTOM, Property.TEXT_ANCHOR_TOP_LEFT, Property.TEXT_ANCHOR_BOTTOM_RIGHT)),
570+
PropertyFactory.textRadialOffset(0.95f),
571+
PropertyFactory.textJustify(Property.TEXT_JUSTIFY_AUTO),
572+
PropertyFactory.symbolSortKey(num("rank")),
573+
PropertyFactory.textPadding(3f),
574+
PropertyFactory.textMaxWidth(9f),
575+
PropertyFactory.textColor(android.graphics.Color.parseColor(if (darkLabels) "#E6ECF5" else "#1A2744")),
576+
PropertyFactory.textHaloColor(if (darkLabels) android.graphics.Color.parseColor(if (dg) MapStyles.QUIET_DARK.bg else "#0B1628")
577+
else android.graphics.Color.argb(235, 255, 255, 255)),
578+
PropertyFactory.textHaloWidth(2.2f)))
531579
}
532-
val (l, k, st) = aklData(lines, links, stops)
533-
s.getSourceAs<GeoJsonSource>("akl-lines")?.setGeoJson(l)
534-
s.getSourceAs<GeoJsonSource>("akl-links")?.setGeoJson(k)
535-
s.getSourceAs<GeoJsonSource>("akl-stops")?.setGeoJson(st)
536-
}
537-
538-
/** The route lines, platform bars, stop dots and names as map layers, bottom to top, fed by akl-lines, akl-links and akl-stops. */
539-
internal fun aklLayers(basemap: Basemap, dark: Boolean): List<Layer> {
540-
val out = ArrayList<Layer>()
541-
val zoom = Expression.zoom()
542-
val dg = basemap == Basemap.Diagram
543-
val caseCol = when {
544-
dg -> if (dark) MapStyles.QUIET_DARK.bg else MapStyles.QUIET_LIGHT.bg
545-
basemap == Basemap.Streets && !dark -> "#FFFFFF"
546-
else -> "#0B1628"
547-
}
548-
val round = arrayOf(PropertyFactory.lineCap(Property.LINE_CAP_ROUND), PropertyFactory.lineJoin(Property.LINE_JOIN_ROUND))
549-
out += LineLayer("akl-lines-case", "akl-lines").withProperties(*round,
550-
PropertyFactory.lineColor(Expression.switchCase(Expression.toBool(Expression.get("z")), Expression.color(android.graphics.Color.parseColor(caseCol)),
551-
Expression.color(android.graphics.Color.parseColor("#0B1628")))),
552-
PropertyFactory.lineOpacity(Expression.switchCase(Expression.toBool(Expression.get("z")), Expression.literal(if (dg) 1f else 0.8f), Expression.literal(0.55f))),
553-
PropertyFactory.lineWidth(byZoom("cw")),
554-
PropertyFactory.lineOffset(byZoom("o")))
555-
out += LineLayer("akl-lines", "akl-lines").withProperties(*round,
556-
PropertyFactory.lineColor(Expression.toColor(Expression.get("c"))),
557-
PropertyFactory.lineWidth(byZoom("w")),
558-
PropertyFactory.lineOffset(byZoom("o")))
559-
out += LineLayer("akl-links-case", "akl-links").withProperties(*round,
560-
PropertyFactory.lineColor(Expression.toColor(Expression.get("c"))),
561-
PropertyFactory.lineWidth(Expression.interpolate(Expression.linear(), zoom,
562-
Expression.stop(9, 6f), Expression.stop(12, 11.4f), Expression.stop(16, 19f))))
563-
out += LineLayer("akl-links", "akl-links").withProperties(*round,
564-
PropertyFactory.lineColor(Color.White.toArgb()),
565-
PropertyFactory.lineWidth(Expression.interpolate(Expression.linear(), zoom,
566-
Expression.stop(9, 2.8f), Expression.stop(12, 6.2f), Expression.stop(16, 13.8f))))
567-
out += CircleLayer("akl-stops", "akl-stops").withProperties(
568-
PropertyFactory.circleColor(Color.White.toArgb()),
569-
PropertyFactory.circleStrokeColor(Expression.toColor(Expression.get("c"))),
570-
PropertyFactory.circleRadius(byZoom("rad")),
571-
PropertyFactory.circleStrokeWidth(Expression.interpolate(Expression.linear(), zoom,
572-
Expression.stop(9, num("sw9")), Expression.stop(13, num("sw13")))))
573-
// names: the most important stations win the space, the rest appear as you zoom in
574-
val darkLabels = basemap != Basemap.Satellite && dark
575-
out += SymbolLayer("akl-stop-labels", "akl-stops").withProperties(
576-
PropertyFactory.textField(Expression.step(zoom, Expression.toString(Expression.get("l0")),
577-
Expression.stop(10.4, Expression.toString(Expression.get("l1"))), Expression.stop(11, Expression.toString(Expression.get("label"))))),
578-
PropertyFactory.textFont(arrayOf("Noto Sans Bold")),
579-
PropertyFactory.textSize(Expression.interpolate(Expression.linear(), zoom,
580-
Expression.stop(10, num("ts10")), Expression.stop(14, num("ts14")))),
581-
PropertyFactory.textVariableAnchor(arrayOf(Property.TEXT_ANCHOR_LEFT, Property.TEXT_ANCHOR_RIGHT, Property.TEXT_ANCHOR_TOP,
582-
Property.TEXT_ANCHOR_BOTTOM, Property.TEXT_ANCHOR_TOP_LEFT, Property.TEXT_ANCHOR_BOTTOM_RIGHT)),
583-
PropertyFactory.textRadialOffset(0.95f),
584-
PropertyFactory.textJustify(Property.TEXT_JUSTIFY_AUTO),
585-
PropertyFactory.symbolSortKey(num("rank")),
586-
PropertyFactory.textPadding(3f),
587-
PropertyFactory.textMaxWidth(9f),
588-
PropertyFactory.textColor(android.graphics.Color.parseColor(if (darkLabels) "#E6ECF5" else "#1A2744")),
589-
PropertyFactory.textHaloColor(if (darkLabels) android.graphics.Color.parseColor(if (dg) MapStyles.QUIET_DARK.bg else "#0B1628")
590-
else android.graphics.Color.argb(235, 255, 255, 255)),
591-
PropertyFactory.textHaloWidth(2.2f))
592-
return out
593-
}
594-
595-
/** The features for akl-lines, akl-links and akl-stops. */
596-
internal fun aklData(lines: List<MapLine>, links: List<MapLine>, stops: List<MapStop>): Triple<FeatureCollection, FeatureCollection, FeatureCollection> {
597-
val lineFc = FeatureCollection.fromFeatures(lines.map { l ->
580+
s.getSourceAs<GeoJsonSource>("akl-lines")?.setGeoJson(FeatureCollection.fromFeatures(lines.map { l ->
598581
Feature.fromGeometry(MultiLineString.fromLngLats(l.parts.map { part -> part.map { Point.fromLngLat(it.second, it.first) } })).also { f ->
599582
val k9 = if (l.z) 0.6f else 1f
600583
val k16 = if (l.z) 1.9f else 1f
@@ -607,12 +590,12 @@ internal fun aklData(lines: List<MapLine>, links: List<MapLine>, stops: List<Map
607590
f.addNumberProperty("o$k", l.offset * v)
608591
}
609592
}
610-
})
611-
val linkFc = FeatureCollection.fromFeatures(links.map { l ->
593+
}))
594+
s.getSourceAs<GeoJsonSource>("akl-links")?.setGeoJson(FeatureCollection.fromFeatures(links.map { l ->
612595
Feature.fromGeometry(MultiLineString.fromLngLats(l.parts.map { part -> part.map { Point.fromLngLat(it.second, it.first) } }))
613596
.also { it.addStringProperty("c", hex(l.color)) }
614-
})
615-
val stopFc = FeatureCollection.fromFeatures(stops.map { st ->
597+
}))
598+
s.getSourceAs<GeoJsonSource>("akl-stops")?.setGeoJson(FeatureCollection.fromFeatures(stops.map { st ->
616599
Feature.fromGeometry(Point.fromLngLat(st.lon, st.lat)).also { f ->
617600
val label = st.label ?: ""
618601
f.addStringProperty("c", hex(st.color))
@@ -628,8 +611,7 @@ internal fun aklData(lines: List<MapLine>, links: List<MapLine>, stops: List<Map
628611
f.addNumberProperty("ts10", if (st.rank <= 0) 12f else 11f)
629612
f.addNumberProperty("ts14", if (st.rank <= 0) 14f else 12.5f)
630613
}
631-
})
632-
return Triple(lineFc, linkFc, stopFc)
614+
}))
633615
}
634616

635617
/** The crowd's source and layers: a dot per vehicle, a heading arrow, and the route once zoomed in. */

0 commit comments

Comments
 (0)