ReviewOS

also looking at this

craft-native/craft

fix(mobile): preserve native editing, navigation, and durable media requests

#369
Merged chris wants to merge codex/native-vertical-scroll-tab-swipes into main
36 files +864 -185

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

packages/android/fixtures/native-navigation/NativeFlatListTest.ktmodified+33-25
Changes to packages/android/fixtures/native-navigation/NativeFlatListTest.kt
@@ -217,11 +217,15 @@ class NativeFlatListTest {
217217 list.apply(nodes, true, 1, true, 0.1, renderer(context, renders), { _ -> }, { _ -> }, null)
218218 }
219219 awaitCount(list, 9)
220 val horizontal = list.layoutManager as LinearLayoutManager
221 assertEquals(LinearLayoutManager.HORIZONTAL, horizontal.orientation)
222 val invertedIdentities = list.visibleItemIdentities
223 assertEquals("row-7", invertedIdentities.first())
224 assertEquals("mounted identities must stay in adapter order", invertedIdentities, invertedIdentities.sortedBy { it.substringAfter('-').toInt() })
220 instrumentation.runOnMainSync {
221 attach(list)
222 val horizontal = list.layoutManager as LinearLayoutManager
223 assertEquals(LinearLayoutManager.HORIZONTAL, horizontal.orientation)
224 val invertedIdentities = list.visibleItemIdentities
225 assertEquals("row-7", invertedIdentities.first())
226 val adapterOrder = nodes.reversed().map { it.getString("id") }
227 assertEquals("mounted identities must stay in adapter order", adapterOrder.filter { it in invertedIdentities }, invertedIdentities)
228 }
225229
226230 instrumentation.runOnMainSync {
227231 list.apply(nodes, false, 3, false, 0.1, renderer(context, renders), { _ -> }, { _ -> }, null)
@@ -284,29 +288,33 @@ class NativeFlatListTest {
284288
285289 @Test
286290 fun nonScrollingListMeasuresItsFullContentHeight() {
287 val context = instrumentation.targetContext
288 val list = CraftNativeFlatList(context)
291 val (scenario, list) = launchHost()
292 val context = list.context
289293 val rows = (0 until 3).map(::row)
290294 val contentSizeChanges = AtomicInteger()
291 instrumentation.runOnMainSync {
292 list.setScrollEnabled(false)
293 list.onContentSizeChanged = { contentSizeChanges.incrementAndGet() }
294 list.apply(rows, false, 1, false, 0.1, renderer(context, AtomicInteger()), { _ -> }, { _ -> }, null)
295 }
296 awaitCount(list, rows.size)
297 instrumentation.runOnMainSync {
298 list.measure(
299 View.MeasureSpec.makeMeasureSpec(320, View.MeasureSpec.EXACTLY),
300 View.MeasureSpec.makeMeasureSpec(1_000, View.MeasureSpec.AT_MOST),
301 )
302 list.layout(0, 0, list.measuredWidth, list.measuredHeight)
303 }
304 assertTrue("non-scrolling FlatList should include every row", list.measuredHeight >= rows.size * 44)
305 repeat(100) {
306 if (contentSizeChanges.get() > 0) return@repeat
307 SystemClock.sleep(10)
295 try {
296 instrumentation.runOnMainSync {
297 list.setScrollEnabled(false)
298 list.onContentSizeChanged = { contentSizeChanges.incrementAndGet() }
299 list.apply(rows, false, 1, false, 0.1, renderer(context, AtomicInteger()), { _ -> }, { _ -> }, null)
300 }
301 awaitCount(list, rows.size)
302 instrumentation.runOnMainSync {
303 list.measure(
304 View.MeasureSpec.makeMeasureSpec(320, View.MeasureSpec.EXACTLY),
305 View.MeasureSpec.makeMeasureSpec(1_000, View.MeasureSpec.AT_MOST),
306 )
307 list.layout(0, 0, list.measuredWidth, list.measuredHeight)
308 assertTrue("non-scrolling FlatList should include every row", list.measuredHeight >= rows.size * 44)
309 }
310 repeat(100) {
311 if (contentSizeChanges.get() > 0) return@repeat
312 SystemClock.sleep(10)
313 }
314 assertTrue("non-scrolling FlatList should report content-size changes", contentSizeChanges.get() > 0)
315 } finally {
316 scenario.close()
308317 }
309 assertTrue("non-scrolling FlatList should report content-size changes", contentSizeChanges.get() > 0)
310318 }
311319
312320 @Test
packages/android/fixtures/native-navigation/NativeMapTest.ktmodified+4-2
Changes to packages/android/fixtures/native-navigation/NativeMapTest.kt
@@ -176,8 +176,10 @@ class NativeMapTest {
176176 ?.getNumberProperty("width") != null
177177 }
178178 val density = main { host.resources.displayMetrics.density.toDouble() }
179 val width = features(map).first { it.getStringProperty("craftOverlayId") == "route" }
180 .getNumberProperty("width").toDouble()
179 val width = main {
180 features(map).first { it.getStringProperty("craftOverlayId") == "route" }
181 .getNumberProperty("width").toDouble()
182 }
181183 assertEquals("MapLibre must scale logical stroke points by display density", 5.0 * density, width, 0.001)
182184 }
183185
packages/android/fixtures/native-navigation/NativeNavigationTest.ktmodified+10-4
Changes to packages/android/fixtures/native-navigation/NativeNavigationTest.kt
@@ -1858,10 +1858,16 @@ class NativeNavigationTest {
18581858 people.scrollTo(0, (peopleContent?.height ?: 0) - people.height)
18591859 }
18601860 SystemClock.sleep(150)
1861 assertTrue(
1862 "ScrollView sticky header did not remain pinned",
1863 screenTop(peopleHeader) >= screenTop(people) + (8 * density).toInt() - 1,
1864 )
1861 instrumentation.runOnMainSync {
1862 // Content padding scrolls away; a sticky header pins to the
1863 // viewport edge, matching the iOS native scroll host.
1864 val headerTop = screenTop(peopleHeader)
1865 val viewportTop = screenTop(people)
1866 assertTrue(
1867 "ScrollView sticky header did not remain pinned: header=$headerTop viewport=$viewportTop",
1868 kotlin.math.abs(headerTop - viewportTop) <= 1,
1869 )
1870 }
18651871 val peopleLayouts = awaitView(activity, "people-layout-status") as TextView
18661872 val initialRowLayouts = peopleLayouts.text.toString().substringAfterLast(": ").toInt()
18671873 assertTrue("ScrollView rows did not report layout", initialRowLayouts > 0)