Conversation
09fc25b to
2bc70b0
Compare
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
|
Here are my notes regarding the migration to maplibre-compose
|
This comment was marked as outdated.
This comment was marked as outdated.
Key the inspection effect on the inspected object, zoom to clusters through the map state so the camera state needs no late init, and rely on the overlay effect alone to close an overlay form.
There was a problem hiding this comment.
Ah, finally a complete review!
I am not entirely sure about (all those) states introduced. I have the vague feeling that they kind of make it harder to see/realize that certain state doesn't actually need to exist and instead make it less malleable to clean up. Sorry for being so vague... err... it's maybe related to that I haven't used the *State pattern much so far and also that my brain is working at 80% or so right now. But I do think that at least in a few instances, see below, I have a point.
Finally, not part of your changes, but mine in maplibre-compose and before, also driven by necessity before everything was compose. I have introduced quite a few ViewModels. Question is, since I read somewhere that ideally, ViewModels should be used on a per-screen basis, whether to merge the MapViewModel, BottomSheetViewModel etc into the MainViewModel? What's your thought on this?
| GeographicLayout( | ||
| Modifier | ||
| .windowInsetsPadding(WindowInsets.safeDrawing) | ||
| .padding(if (sheet.isOpen) sheetPadding else PaddingValues(0.dp)) | ||
| ) { | ||
| if (!showIntroTutorial) { | ||
| location.location?.position?.let { position -> | ||
| PointerPinButton(targetPosition = position, onClick = ::followLocation) { | ||
| Image(painterResource(Res.drawable.location_dot_small), null) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Man, this GeographicLayout stuff is pretty cool. You might have noticed that we place quite a few things on a certain map position. Such as:
- on moving a node, the start of the arrow
- the scissors when splitting a way (which snaps to the way)
- in the address overlay, when adding a new address, the pin, as it snaps to building outlines; and for a few overlays, too
Right now, this is done in the quest / overlay form itself, so not at all in the overlay = {...} part of the MapLibre Map. Instead, we use the LocalGetOffsetCallback composition local ((LatLon) -> Offset?) to talk with the MapLibre map from deep within the composition hierarchy in order to get the current screen offset from any given geographic position.
A side effect of the "drawing on map from a different part of the composition hierarchy than one might expect" is that on the slide-in appear-animation of forms, the content that ought to be "on" the map, slides in from the bottom too, which doesn't look so good. Otherwise, it works fine, although since the maplibre-compose migration, it feels a little bit janky. (But that on 1. an emulator and 2. maybe it is due to other unrelated performance issues.)
The current solution was necessary with the Android map. Now that it is compose, we have at least the opportunity to think about whether it would be more straightforward to do it another way, e.g. with the GeographicLayout. Maybe... allow forms to specify a composable function to inject into the overlay part of the MapLibre map (if that's even possible)? What are your thoughts on this?
There was a problem hiding this comment.
done in 3c4db1d, though I'm unsure about how I wired up this OnMap thing, it feels like a bit of a hack
I think doing it more neatly requires some deeper surgery in form state
There was a problem hiding this comment.
took another pass at it in 1842041. Now each form has an overlay slot, more like the maplibre-compose demo app.
claude review flagged this, lmk if you want the entrance flag saved like before:
The entrance flag used to be rememberSaveable inside the form. As a field on the form object it no longer survives process death, since the object is recreated. The sub-form choice does survive because its state comes from rememberSerializable at the screen. If that matters, the flag can be constructed the same way.
There was a problem hiding this comment.
Hmm, I read through it, and I find it looks very very complicated. This affects authors of new (overlay) forms, too. I want to keep this as simple as possible.
If there is no more straightforward way to do it, then I think I'd rather use the previous method. As written, the only (visible) downside of the previous method is that the content shown on the overlay is also animated in the enter and exit transition.
I initially was thinking that maybe a composition local might be used for this. Something like
val LocalMapOverlayContentCallback =
compositionLocalOf<(@Composable (MapOverlayScope.() -> Unit)?> { null }, provided by the main screen map and ready to be filled by whoever "answers the call".
But whether a composable function can be a composition local... unlikely!
…ose-completion # Conflicts: # androidApp/src/main/kotlin/de/westnordost/streetcomplete/screens/main/MainActivity.kt # app/src/androidMain/kotlin/de/westnordost/streetcomplete/AndroidModule.kt # app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/quest/AutoSyncer.kt # app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/MainViewModelImpl.kt # app/src/iosMain/kotlin/de/westnordost/streetcomplete/IosModule.kt
…n' into codex/maplibre-compose-completion
It's the usual way in Compose (and React, and similar frameworks) to extract and encapsulate tightly coupled bundles of logic. But exactly what is or should be bundled like that, where the boundaries are drawn, is pretty subjective. for example in this app there's a lot of complexity in managing the camera, as it tracks the user, or focuses on quests, transitions between different modes. So extracting all that into a single state machine MainMapCameraState helps (me) understand and manipulate the camera more easily. The other bundles, I'm less sure about; we can reshape them to boundaries that make more sense to you, or inline them if they're not serving their purpose well. |
…n' into codex/maplibre-compose-completion
Generally I think a single ViewModel per screen is good, but I wouldn't just merge these three into one as-is. Some of this complexity is because the data layer exposes callbacks, and the "ViewModel" layer adapts them to StateFlows for Compose. We could simplify by making the data layer itself Flow based, then the ViewModels have less work to do, and can focus on simply adapting those services to UI, merged into one. I'd do that in separate PRs, perhaps one per data source. For now, I think these |
I see, thank you! This also helps me to see where you are coming from. In the next review I will focus on the changes you made / on the states. (By the way, feel free to ask me questions, too! Usually I remember pretty well why this or that has been implemented (before) in this or that way and whether it is important to keep it that way or if this could be done differently or simpler etc.) |
Yeah, that would be a whole new endeavour I'd like to not touch right now 😅. |
it wasn't possible before because the attribution button was popup-based, i.e. it would display in front of everything else
…tting, eliminate MainLocationState Regarding the latter: Done because it was a leaky abstraction - locationProvider was still used in MainScreen. I found it was a detriment to readability and only saved up to 20 lines of code
|
(I started reviewing - a bit more hands-on. Will continue tomorrow at the Notes:
) |
Finishes the MapLibre Compose migration on top of the
maplibre-composebranch. The main screen is Compose on all platforms, and the legacy Android map is gone.Stacked on #6352, resolves #6072.
Deviations from @westnordost's implementation notes
WithHaloPainter. MapLibre Compose's imperative image registration api requires the app to handle rasterization (will fix in next release).Validation