ReviewOS

also looking at this

stacks/wildloop

feat: native trail maps and complete native mobile screens

#1017
Draft chris wants to merge codex/trail-experience into main
60 files +2505 -823

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.

.maestro/native-trail/directions-map.yamladded+16-0
Changes to .maestro/native-trail/directions-map.yaml
@@ -0,0 +1,16 @@
1appId: ${APP_ID}
2name: Open trailhead directions from the full native map
3---
4- launchApp
5- openLink: wildloop://trail/${TRAIL_ID}/map
6- extendedWaitUntil:
7 visible:
8 id: map-directions
9 timeout: 30000
10- tapOn:
11 id: map-directions
12- extendedWaitUntil:
13 notVisible:
14 id: trail-full-map
15 timeout: 15000
16- takeScreenshot: android-native-full-map-directions-handoff
.maestro/native-trail/maps.yamlmodified+27-0
Changes to .maestro/native-trail/maps.yaml
@@ -41,6 +41,33 @@ name: Native Android trail maps and sticky save
4141 visible: ${INLINE_PROFILE_END_TEXT}
4242 timeout: 15000
4343- takeScreenshot: android-native-inline-elevation-selection
44- scrollUntilVisible:
45 element:
46 id: trail-zoom-in
47 direction: DOWN
48- tapOn:
49 id: trail-zoom-in
50- tapOn:
51 id: trail-zoom-out
52- tapOn:
53 id: trail-fit-route
54- tapOn:
55 id: trail-map-3d
56- assertVisible: '^2D$'
57- tapOn: '^North$'
58- scrollUntilVisible:
59 element:
60 id: trail-map-layer
61 direction: DOWN
62- tapOn:
63 id: trail-map-layer
64- tapOn: '^Satellite$'
65- assertVisible: 'Map layer, Satellite'
66- takeScreenshot: android-native-inline-map-satellite
67- tapOn:
68 id: trail-map-layer
69- tapOn: '^Outdoors$'
70- assertVisible: 'Map layer, Outdoors'
4471- scrollUntilVisible:
4572 element:
4673 id: trail-map-fullscreen
.maestro/native-trail/utilities.yamlmodified+31-1
Changes to .maestro/native-trail/utilities.yaml
@@ -18,7 +18,7 @@ name: Native location, route customization and GPX sharing
1818- tapOn:
1919 id: '.*permission_allow_foreground_only_button'
2020- extendedWaitUntil:
21 visible: 'Your location is shown'
21 visible: 'My location, Your location is shown'
2222 timeout: 30000
2323- takeScreenshot: android-native-map-location
2424- tapOn:
@@ -41,3 +41,33 @@ name: Native location, route customization and GPX sharing
4141- back
4242- assertVisible:
4343 id: trail-full-map
44- openLink: wildloop://trail/${TRAIL_ID}
45- extendedWaitUntil:
46 visible:
47 id: trail-sticky-actions
48 timeout: 30000
49- scrollUntilVisible:
50 element:
51 id: trail-map-locate
52 direction: DOWN
53- tapOn:
54 id: trail-map-locate
55- extendedWaitUntil:
56 visible: 'My location, Your location is shown'
57 timeout: 30000
58- assertNotVisible:
59 id: '.*permission_allow_foreground_only_button'
60- takeScreenshot: android-native-inline-map-location
61- scrollUntilVisible:
62 element:
63 id: trail-map-export
64 direction: DOWN
65- tapOn:
66 id: trail-map-export
67- extendedWaitUntil:
68 visible: ${GPX_NAME}
69 timeout: 15000
70- takeScreenshot: android-native-inline-gpx-share-sheet
71- back
72- assertVisible:
73 id: trail-sticky-actions
resources/native/TrailDetail.stxmodified+1-1
Changes to resources/native/TrailDetail.stx
@@ -321,7 +321,7 @@ onMount(() => onNativeAccountChange(craft, () => { viewer.set(0); saved.set(fals
321321 <View class="mt-2 flex-row flex-wrap gap-2">
322322 <Button testID="trail-map-3d" :class="ui.secondary" @click="pitch.set(pitch() > 0 ? 0 : 45)" :text="pitch() > 0 ? '2D' : '3D'" />
323323 <Button :class="ui.secondary" @click="heading.set(0)">North</Button>
324 <Button testID="trail-map-locate" :class="ui.secondary" @click="locate()">My location</Button>
324 <Button testID="trail-map-locate" :class="ui.secondary" accessibilityLabel="My location" :accessibilityValue="showLocation() ? 'Your location is shown' : ''" @click="locate()">My location</Button>
325325 <Button testID="trail-map-fullscreen" :class="ui.secondary" @click="craft.navigation.open('/trail/' + wanted + '/map')">Expand map</Button>
326326 </View>
327327 <Button testID="trail-map-export" :disabled="!routeAvailable()" :class="ui.secondary" class="mt-2" @click="exportRoute()">Export GPX</Button>
resources/native/TrailMap.stxmodified+1-1
Changes to resources/native/TrailMap.stx
@@ -138,7 +138,7 @@ onMount(() => { void load(); return () => { alive = false } })
138138 <Button testID="map-fit" class="flex-1" :class="ui.secondary" @click="fit()">Fit route</Button>
139139 <Button testID="map-zoom-in" class="w-11" :class="ui.secondary" @click="region.set(nativeZoomRegion(currentRegion(),0.5))" accessibilityLabel="Zoom in">+</Button>
140140 <Button testID="map-zoom-out" class="w-11" :class="ui.secondary" @click="region.set(nativeZoomRegion(currentRegion(),2))" accessibilityLabel="Zoom out">−</Button>
141 <Button testID="map-locate" class="flex-1" :class="ui.secondary" :accessibilityValue="showLocation() ? 'Your location is shown' : ''" @click="locate()">My location</Button>
141 <Button testID="map-locate" class="flex-1" :class="ui.secondary" accessibilityLabel="My location" :accessibilityValue="showLocation() ? 'Your location is shown' : ''" @click="locate()">My location</Button>
142142 </View>
143143 <View class="flex-row gap-2">
144144 <Button testID="map-export" class="flex-1" :class="ui.secondary" @click="exportRoute()">Export GPX</Button>
scripts/run-native-trail-android-e2e.tsmodified+3-3
Changes to scripts/run-native-trail-android-e2e.ts
@@ -114,7 +114,7 @@ export async function runNativeTrailAndroidE2e(): Promise<void> {
114114 GPX_NAME: `${trail.name.replace(/[^a-z0-9]+/gi, '-').replace(/^-|-$/g, '').slice(0, 80)}.gpx`,
115115 }
116116 let reviewId: number | undefined
117 for (const phase of ['maps', 'utilities', 'sharing', 'directions', 'review-create', 'review-edit', 'review-delete'] as const) {
117 for (const phase of ['maps', 'utilities', 'sharing', 'directions', 'directions-map', 'review-create', 'review-edit', 'review-delete'] as const) {
118118 const report = join(root, `${phase}.xml`)
119119 await command([
120120 'maestro', `--device=${device}`, '--no-ansi', 'test',
@@ -140,8 +140,8 @@ export async function runNativeTrailAndroidE2e(): Promise<void> {
140140 }
141141 if (!exported) throw new Error('The native GPX export did not contain the recorded route coordinates.')
142142 }
143 if (phase === 'directions') {
144 const activities = await adb(['shell', 'dumpsys', 'activity', 'activities'], 'directions-activities')
143 if (phase === 'directions' || phase === 'directions-map') {
144 const activities = await adb(['shell', 'dumpsys', 'activity', 'activities'], `${phase}-activities`)
145145 const resumed = activities.split('\n').filter(line => /(?:mResumedActivity|topResumedActivity):/.test(line))
146146 if (!resumed.length || resumed.some(line => line.includes(`${mobile.android.packageName}/`)))
147147 throw new Error('Directions did not hand off to an external Android activity.')