Repository: streetcomplete/StreetComplete (language: kotlin)

Issue:
Cycling on sidewalk (in both directions) answer is missing
Thanks for adding back the differentiation of cycling on the sidewalk being allowed vs. a shared use sidewalk. (not sure when that happend, but it was missing a while) 

However there's an option missing:

Cycling on the sidewalk is allowed in both directions - on none side. Meaning: Riding on the sidewalk is not a one way.

The equivalent of "shared use path" however has such an option:

![Screenshot_2024-09-10-07-22-48-133-edit_de westnordost streetcomplete](https://github.com/user-attachments/assets/dfc086ac-6c16-40e1-acc1-3984345d0e29)

Btw: Wouldn't shared use sidewalk not be less confusing here?

**Use case**
We got sidewalks which are marked from both directions for usage with bikes:

![Screenshot_2024-09-10-07-29-08-179-edit_com google android apps maps](https://github.com/user-attachments/assets/323fc4bf-1838-432d-a30d-fabd3ab2f333)


**Proposed Solution**
Just another button. Not sure about the tagging thought. 


Discussion hints:
"Btw: Wouldn't shared use sidewalk not be less confusing here?"

No. Sidewalk is a term specifically referring to a path next to a road. There are plenty of unsegregated cycle tracks that do not follow the route of a road and are thus not "sidewalk" in any sense of the meaning of the word.
> "Btw: Wouldn't shared use sidewalk not be less confusing here?"
> 
> No. Sidewalk is a term specifically referring to a path next to a road. There are plenty of unsegregated cycle tracks that do not follow the route of a road and are thus not "sidewalk" in any sense of the meaning of the word.

I think you're missing the point here. The quest is regarding infrastructure along a road. So sidewalk is always the appropriate term here. 
> there's an option missing:
> 
> Cycling on the sidewalk is allowed in both directions - on [one] side

I had this same problem yesterday. Not sure which I ended up picking anymore, also because the effect seems virtually identical: you can cycle on the same path as pedestrians can walk on, either because a sign says it's ok or because a sign says it's a shared path. I'm also not sure how to differentiate between a path where it says "cycling ok" but means it only for one direction, and one where the same sign means both directions.

> We got sidewalks which are marked from both directions for usage with bikes:

The picture doesn't show anything that indicates both directions though? Since there is no oneway variant of it, it stands to reason that it means that, but honestly I wouldn't know

> 
> > We got sidewalks which are marked from both directions for usage with bikes:
> 
> The picture doesn't show anything that indicates both directions though? Since there is no oneway variant of it, it stands to reason that it means that, but honestly I wouldn't know

Well, that's on the left side of the road. If the sign is facing you, it means you're allowed to ride in this direction. 

I the other direction there's the same sign, just facing the other way. 



🎶 Now this looks like a job for me...🎶

I also thought about that when implementing that change, but thought it would clutter up the UI too much for the limited times I thought it would apply. If @westnordost is okay with it, I can implement that addition.

We should keep in mind that we still need to think about if theese options should be only enabled in certain countries, following my post on the forum that got out of hand a bit. :D 
Cool, yeah, I think it should be added (at the very bottom). I can imagine that it is a bit common in rural areas where there is just one path on one side of a rural road for both cyclists and pedestrians which however is not broad enough to legally force cyclists to use it.
For tagging:
My intuitive way to tag it would be 
`sidewalk:<side>:bicycle=yes`
`sidewalk:<side>:oneway:bicycle=no`
I guess it would be weird to have `cycleway:<side>:oneway=no` when there is `cycleway:<side>=no`, so your suggestion sounds good.

Pull request: Add "sign allows cycling on sidewalk, both directions" option to cycleway quest/overlay
Adds the option "None, but sign allows cycling on sidewalk, both directions" to the available road-side cycleway options.

![photo_2024-10-13_19-59-21](https://github.com/user-attachments/assets/ccbd4558-b324-45ce-a31f-30c3aadfaef6)

Fixes #5888 

Test command: ./gradlew :app:testDebugUnitTest --tests "de.westnordost.streetcomplete.osm.cycleway.CyclewayCreatorKtTest" --tests "de.westnordost.streetcomplete.osm.cycleway.CyclewayParserKtTest"

Fix patch:
diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/Cycleway.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/Cycleway.kt
index 44b6cd2c1af..a56eaba6727 100644
--- a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/Cycleway.kt
+++ b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/Cycleway.kt
@@ -64,7 +64,7 @@ data class CyclewayAndDirection(val cycleway: Cycleway, val direction: Direction
 fun CyclewayAndDirection.isSelectable(countryInfo: CountryInfo): Boolean =
     cycleway.isSelectable(countryInfo) &&
     // only allow dual track, dual lanes and "dual" sidewalk (not dual pictograms or something)
-    (direction != BOTH || cycleway in listOf(TRACK, UNSPECIFIED_LANE, EXCLUSIVE_LANE, SIDEWALK_EXPLICIT))
+    (direction != BOTH || cycleway in listOf(TRACK, UNSPECIFIED_LANE, EXCLUSIVE_LANE, SIDEWALK_EXPLICIT, SIDEWALK_OK))
 
 @Serializable
 enum class Direction {
@@ -195,7 +195,8 @@ fun getSelectableCycleways(
     val dualCycleways = listOf(
         CyclewayAndDirection(if (countryInfo.hasAdvisoryCycleLane) EXCLUSIVE_LANE else UNSPECIFIED_LANE, BOTH),
         CyclewayAndDirection(TRACK, BOTH),
-        CyclewayAndDirection(SIDEWALK_EXPLICIT, BOTH)
+        CyclewayAndDirection(SIDEWALK_EXPLICIT, BOTH),
+        CyclewayAndDirection(SIDEWALK_OK, BOTH)
     )
 
     // no need to distinguish between advisory and exclusive lane where the concept of exclusive
diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreator.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreator.kt
index fda4de15278..31fb4e680a4 100644
--- a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreator.kt
+++ b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreator.kt
@@ -181,7 +181,11 @@ private fun CyclewayAndDirection.applyTo(tags: Tags, isRight: Boolean, isLeftHan
         val isDefaultDirection = defaultDirection == direction
         val isInContraflowOfOneway = isInContraflowOfOneway(tags, direction)
         if (!isDefaultDirection || isInContraflowOfOneway || tags.containsKey("$cyclewayKey:oneway")) {
-            tags["$cyclewayKey:oneway"] = direction.onewayValue
+            if (cycleway == SIDEWALK_OK) {
+                tags["sidewalk:$side:oneway:bicycle"] = direction.onewayValue
+            } else {
+                tags["$cyclewayKey:oneway"] = direction.onewayValue
+            }
         }
     }
 
@@ -195,11 +199,14 @@ private fun CyclewayAndDirection.applyTo(tags: Tags, isRight: Boolean, isLeftHan
     if (!touchedSegregatedValue) {
         tags.remove("$cyclewayKey:segregated")
     }
-    // no matter what option was chosen, if it's not SIDEWAY_OK, remove that cycling is signed and ok on sidewalk if it's there
+    // no matter what option was chosen, if it's not SIDEWALK_OK, remove that cycling is signed and ok on sidewalk if it's there
     if (cycleway != SIDEWALK_OK && tags["sidewalk:$side:bicycle"] == "yes" && tags["sidewalk:$side:bicycle:signed"] == "yes") {
         tags.remove("sidewalk:$side:bicycle")
         tags.remove("sidewalk:$side:bicycle:signed")
     }
+    if (direction != BOTH && tags["sidewalk:$side:oneway:bicycle"] == "no") {
+        tags.remove("sidewalk:$side:oneway:bicycle")
+    }
     // if anything that is not SIDEWALK_EXPLICIT is chosen, remove sidewalk:<side>:bicycle=designated if it's there
     if (cycleway != SIDEWALK_EXPLICIT && tags["sidewalk:$side:bicycle"] == "designated") {
         tags.remove("sidewalk:$side:bicycle")
diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayItem.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayItem.kt
index 6ede17cf6d2..16670a08887 100644
--- a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayItem.kt
+++ b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayItem.kt
@@ -110,6 +110,7 @@ private fun Cycleway.getDualTrafficIconResId(countryInfo: CountryInfo): Int = wh
             R.drawable.ic_cycleway_track_dual
         }
     SIDEWALK_EXPLICIT ->                   R.drawable.ic_cycleway_sidewalk_explicit_dual
+    SIDEWALK_OK ->                         R.drawable.ic_cycleway_sidewalk_ok_both
     else ->                                0
 }
 
@@ -183,6 +184,12 @@ private fun CyclewayAndDirection.getTitleResId(isContraflowInOneway: Boolean): I
     BUSWAY ->            R.string.quest_cycleway_value_bus_lane
     SEPARATE ->          R.string.quest_cycleway_value_separate
     SHOULDER ->          R.string.quest_cycleway_value_shoulder
-    SIDEWALK_OK ->       R.string.quest_cycleway_value_sidewalk_ok
+    SIDEWALK_OK ->       {
+        if (direction == BOTH) {
+            R.string.quest_cycleway_value_sidewalk_ok_dual
+        } else {
+            R.string.quest_cycleway_value_sidewalk_ok
+        }
+    }
     else -> 0
 }
diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParser.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParser.kt
index 67701662d99..32f1655f903 100644
--- a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParser.kt
+++ b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParser.kt
@@ -154,11 +154,20 @@ private fun parseDirectionForSide(
 ): Direction {
     val sideVal = if (isRight) ":right" else ":left"
     val cyclewayKey = "cycleway$sideVal"
+    val sidewalkKey = "sidewalk$sideVal"
     val explicitDirection = when (tags["$cyclewayKey:oneway"]) {
         "yes" -> Direction.FORWARD
         "-1" ->  Direction.BACKWARD
         "no" ->  Direction.BOTH
-        else ->  null
+        else ->  when (tags["$sidewalkKey:oneway:bicycle"]) {
+            "yes" -> Direction.FORWARD
+            "-1" ->  Direction.BACKWARD
+            "no" ->  when (tags["$sidewalkKey:bicycle:signed"]) {
+                "yes" -> Direction.BOTH
+                else -> null
+            }
+            else -> null
+        }
     }
     return explicitDirection ?: Direction.getDefault(isRight, isLeftHandTraffic)
 }
@@ -188,6 +197,7 @@ private fun expandRelevantSidesTags(tags: Map<String, String>): Map<String, Stri
     result.expandSidesTags("cycleway", "segregated", true)
     result.expandSidesTags("sidewalk", "bicycle", true)
     result.expandSidesTags("sidewalk", "bicycle:signed", true)
+    result.expandSidesTags("sidewalk", "oneway:bicycle", true)
     return result
 }
 
diff --git a/app/src/main/res/drawable/ic_cycleway_sidewalk_ok_both.xml b/app/src/main/res/drawable/ic_cycleway_sidewalk_ok_both.xml
new file mode 100644
index 00000000000..ef7c48f5a01
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cycleway_sidewalk_ok_both.xml
@@ -0,0 +1,80 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="256dp"
+    android:height="256dp"
+    android:viewportWidth="256"
+    android:viewportHeight="256">
+  <path
+      android:pathData="M128,0.01l72,0l0,256l-72,0z"
+      android:fillColor="#aaa"/>
+  <path
+      android:pathData="m192,231.99l8,0m-8,-24l8,0m-8,-24l8,0m0,-24l-8,0m0,-24l8,0m-8,-24l8,0m-8,-24l8,0m-8,-24l8,0m-8,-24l8,0m-8,-24l8,0m-72,216l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-48l16,0m-16,-24l16,0m-16,-48l16,0m-16,24l16,0m-16,72l16,0m16,120l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-48l16,0m-16,-24l16,0m-16,-48l16,0m-16,24l16,0m-16,72l16,0m-32,132l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-48l16,0m-16,-24l16,0m-16,-48l16,0m-16,24l16,0m-16,72l16,0m32,156l0,-256m-16,0l0,256m-16,0l0,-256m-16,0l0,256m32,-228.58l16,0m-16,24l16,0m-16,192l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-72l16,0"
+      android:strokeWidth="4"
+      android:fillColor="#00000000"
+      android:strokeColor="#999"/>
+  <path
+      android:pathData="M120,0.01l8,0l0,256l-8,0z"
+      android:fillColor="#999"/>
+  <path
+      android:pathData="M200,0.01l8,0l0,256l-8,0z"
+      android:fillColor="#999"/>
+  <path
+      android:pathData="M112,0.01l8,0l0,256l-8,0z"
+      android:fillColor="#666"/>
+  <path
+      android:pathData="m168.11,152.45c-2.87,0 -5.19,2.32 -5.19,5.19 0,2.87 2.33,5.19 5.19,5.19 2.87,0 5.19,-2.32 5.19,-5.19 0,-2.87 -2.32,-5.19 -5.19,-5.19zM162.68,164.72c-5.25,1.96 -9.3,5.3 -9.35,10.86 0,1.42 1.15,2.57 2.57,2.57 1.42,0 2.57,-1.15 2.57,-2.57 0.04,-1.42 0.64,-2.62 1.52,-3.59 0.03,1.26 0.23,2.68 0.66,4.26 -0.27,0.45 -0.38,1.12 -0.27,2.1 -0.91,5.23 -6.67,8.63 -10.39,10.32 -1.3,0.58 -1.89,2.1 -1.31,3.4 0.58,1.3 2.1,1.89 3.4,1.31 4.93,-2.57 11.2,-6.71 13,-12.15 4.1,1.91 7.93,6.2 8.62,10.16 0.2,1.41 1.51,2.38 2.91,2.18 1.41,-0.2 2.39,-1.5 2.18,-2.91 -1.07,-6.35 -5.45,-11.05 -10.93,-13.81 -1.58,-0.68 -1.71,-4.38 -1.62,-5.71 4.8,2.73 9.72,3.94 14.59,1.55 1.27,-0.64 1.79,-2.18 1.15,-3.45 -0.64,-1.27 -2.18,-1.79 -3.45,-1.15 -5.72,2.83 -9.29,-1.5 -12.3,-3.26 -1.08,-0.75 -2.51,-0.76 -3.58,-0.1z"
+      android:strokeWidth="3.78"
+      android:fillColor="#ffffff"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="m153.73,200.93l22.41,0a4.8,4.8 0,0 1,4.8 4.79l0,20.41a4.8,4.8 0,0 1,-4.8 4.79l-22.41,0a4.8,4.8 0,0 1,-4.8 -4.79l0,-20.41a4.8,4.8 0,0 1,4.8 -4.79z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#ffffff"
+      android:strokeColor="#000000"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="m162.06,215.14a4.64,4.66 90,0 1,-4.66 4.64,4.64 4.66,90 0,1 -4.66,-4.64 4.64,4.66 90,0 1,4.66 -4.64,4.64 4.66,90 0,1 4.66,4.64zM177.24,215.14a4.64,4.66 90,0 1,-4.66 4.64,4.64 4.66,90 0,1 -4.66,-4.64 4.64,4.66 90,0 1,4.66 -4.64,4.64 4.66,90 0,1 4.66,4.64zM159.99,207.99 L165.88,215.11l6.73,0l-4.63,-7.12L160,207.99m5.89,7.12 l2.11,-8.8m0,0 l2.05,-0.01m-12.57,8.8 l3.51,-10.39l3.36,0"
+      android:strokeLineJoin="round"
+      android:strokeWidth="1.5"
+      android:fillColor="#00000000"
+      android:strokeColor="#000000"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="m164.94,224.93c0,1.5 -0.99,3 -3,3 -2.01,0 -3.03,-1.47 -3,-3 0.03,-1.52 0.95,-3 3,-3 2.05,0 3,1.5 3,3zM163.44,224.93c0,-0.73 -0.5,-1.5 -1.5,-1.5 -1.01,0 -1.5,0.74 -1.5,1.5 0,0.76 0.5,1.5 1.5,1.5 1,0 1.5,-0.77 1.5,-1.5z"
+      android:strokeWidth="0.5"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="m171.43,227.94l-1.64,0l-1.85,-2.5 -0.5,0.5l0,2l-1.5,0l0,-6l1.5,0l0,2.5l2.5,-2.5l1.65,0l-2.65,2.6z"
+      android:strokeWidth="0.5"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="m161.83,97.48c2.87,0 5.19,-2.32 5.19,-5.19 0,-2.87 -2.33,-5.19 -5.19,-5.19 -2.87,0 -5.19,2.32 -5.19,5.19 0,2.87 2.32,5.19 5.19,5.19zM167.26,85.21c5.25,-1.96 9.3,-5.3 9.35,-10.86 0,-1.42 -1.15,-2.57 -2.57,-2.57 -1.42,0 -2.57,1.15 -2.57,2.57 -0.04,1.42 -0.64,2.62 -1.52,3.59 -0.03,-1.26 -0.23,-2.68 -0.66,-4.26 0.27,-0.45 0.38,-1.12 0.27,-2.1 0.91,-5.23 6.67,-8.63 10.39,-10.32 1.3,-0.58 1.89,-2.1 1.31,-3.4 -0.58,-1.3 -2.1,-1.89 -3.4,-1.31 -4.93,2.57 -11.2,6.71 -13,12.15 -4.1,-1.91 -7.93,-6.2 -8.62,-10.16 -0.2,-1.41 -1.51,-2.38 -2.91,-2.18 -1.41,0.2 -2.39,1.5 -2.18,2.91 1.07,6.35 5.45,11.05 10.93,13.81 1.58,0.68 1.71,4.38 1.62,5.71 -4.8,-2.73 -9.72,-3.94 -14.59,-1.55 -1.27,0.64 -1.79,2.18 -1.15,3.45 0.64,1.27 2.18,1.79 3.45,1.15 5.72,-2.83 9.29,1.5 12.3,3.26 1.08,0.75 2.51,0.76 3.58,0.1z"
+      android:strokeWidth="3.78"
+      android:fillColor="#ffffff"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="m176.2,49l-22.41,0a4.8,4.8 0,0 1,-4.8 -4.79l0,-20.41a4.8,4.8 0,0 1,4.8 -4.79l22.41,0a4.8,4.8 0,0 1,4.8 4.79l0,20.41a4.8,4.8 0,0 1,-4.8 4.79z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#ffffff"
+      android:strokeColor="#000000"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="m167.87,34.79a4.64,4.66 90,0 1,4.66 -4.64,4.64 4.66,90 0,1 4.66,4.64 4.64,4.66 90,0 1,-4.66 4.64,4.64 4.66,90 0,1 -4.66,-4.64zM152.69,34.79a4.64,4.66 90,0 1,4.66 -4.64,4.64 4.66,90 0,1 4.66,4.64 4.64,4.66 90,0 1,-4.66 4.64,4.64 4.66,90 0,1 -4.66,-4.64zM169.94,41.94 L164.05,34.82l-6.73,0l4.63,7.12l7.99,0m-5.89,-7.12 l-2.11,8.8m0,0 l-2.05,0.01m12.57,-8.8 l-3.51,10.39l-3.36,0"
+      android:strokeLineJoin="round"
+      android:strokeWidth="1.5"
+      android:fillColor="#00000000"
+      android:strokeColor="#000000"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="m165,25c0,-1.5 0.99,-3 3,-3 2.01,0 3.03,1.47 3,3 -0.03,1.52 -0.95,3 -3,3 -2.05,0 -3,-1.5 -3,-3zM166.5,25c-0,0.73 0.5,1.5 1.5,1.5 1.01,0 1.5,-0.74 1.5,-1.5 0,-0.76 -0.5,-1.5 -1.5,-1.5 -1,0 -1.5,0.77 -1.5,1.5z"
+      android:strokeWidth="0.5"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="m158.51,21.99l1.64,0l1.85,2.5 0.5,-0.5l0,-2l1.5,0l0,6l-1.5,0l0,-2.5L160,27.99l-1.65,0l2.65,-2.6z"
+      android:strokeWidth="0.5"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M0,0h112v256h-112z"
+      android:fillColor="#808080"/>
+</vector>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fe27cde6680..6d2b5001463 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1022,6 +1022,7 @@ A level counts as a roof level when its windows are in the roof. Subsequently, r
     <string name="quest_cycleway_value_none_and_oneway">none, no cycling allowed in this direction</string>
     <string name="quest_cycleway_value_shoulder">none, but shoulder is usable for cyclists</string>
     <string name="quest_cycleway_value_sidewalk_ok">none, but sign allows cycling on sidewalk</string>
+    <string name="quest_cycleway_value_sidewalk_ok_dual">none, but sign allows cycling on sidewalk, both directions</string>
     <string name="quest_cycleway_answer_contraflow_cycleway">Also bicycle path on the other side…</string>
     <string name="quest_cycleway_answer_no_bicycle_infrastructure">No bicycle lane or path at all</string>
     <string name="quest_cycleway_answer_no_bicycle_infrastructure_explanation">As usual, tap on one side of the illustration, select a proper answer and do the same for the other side.\n\nNote all the available options: some cases are not obvious, like a oneway road that allows cyclists in both directions!</string>
diff --git a/res/graphics/cycleway/sidewalk_ok_dual.svg b/res/graphics/cycleway/sidewalk_ok_dual.svg
new file mode 100644
index 00000000000..1fabc37ee2f
--- /dev/null
+++ b/res/graphics/cycleway/sidewalk_ok_dual.svg
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#aaa" d="M128 .006h72v255.997h-72z"/><path fill="none" stroke="#999" stroke-width="3.998711" d="M191.999 231.992h8m-8-24h8m-8-24h8m0-24h-8m0-24h8m-8-24h8m-8-24h8m-8-24h8m-8-23.999h8m-8-24h8m-72 215.999h16m-16-24h16m-16-24h16m-16-24h16m-16-24h16m-16-48h16m-16-24h16m-16-47.999h16m-16 24h16m-16 72h16m16 119.999h16m-16-24h16m-16-24h16m-16-24h16m-16-24h16m-16-48h16m-16-24h16m-16-47.999h16m-16 24h16m-16 72h16m-32 131.999h16m-16-24h16m-16-24h16m-16-24h16m-16-24h16m-16-24h16m-16-48h16m-16-23.999h16m-16-48h16m-16 24h16m-16 72h16m32 155.999V-.005m-16 0v255.997m-16 0V-.005m-16 0v255.997m32-228.577h16m-16 24h16m-16 191.998h16m-16-24h16m-16-24h16m-16-24h16m-16-23.999h16m-16-24h16m-16-24h16m-16-24h16m-16-71.999h16"/><path fill="#999" d="M120 .006h8v255.997h-8zM200 .006H208v255.997H200z"/><path fill="#666" d="M111.998.006h8.001v255.997h-8.001z"/><path fill="#fff" d="M44.478 269.6a1.374 1.374 0 1 0 0 2.748 1.374 1.374 0 0 0 0-2.748zm-1.437 3.247c-1.39.52-2.462 1.403-2.474 2.872a.68.68 0 1 0 1.363 0c.01-.375.17-.694.403-.949.007.335.06.71.176 1.127-.071.118-.1.296-.073.555-.24 1.383-1.765 2.283-2.748 2.73a.681.681 0 0 0 .553 1.245c1.305-.68 2.964-1.775 3.441-3.214 1.084.505 2.098 1.64 2.28 2.687a.681.681 0 0 0 1.349-.192c-.282-1.68-1.443-2.925-2.89-3.654-.42-.18-.453-1.159-.43-1.511 1.27.72 2.573 1.043 3.861.41a.681.681 0 0 0-.609-1.218c-1.514.749-2.458-.398-3.255-.862a.87.87 0 0 0-.947-.026z" style="paint-order:stroke fill markers" transform="translate(0 -866.52) scale(3.7795)"/><path fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.9993555" d="M153.735 200.924h22.41a4.795 4.795 0 0 1 4.795 4.795v20.41a4.795 4.795 0 0 1-4.795 4.795h-22.41a4.795 4.795 0 0 1-4.795-4.795v-20.41a4.795 4.795 0 0 1 4.795-4.795z"/><path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5004615000000001" d="M162.065 215.135a4.66 4.64 0 0 1-4.66 4.64 4.66 4.64 0 0 1-4.66-4.64 4.66 4.64 0 0 1 4.66-4.64 4.66 4.64 0 0 1 4.66 4.64zm15.18 0a4.66 4.64 0 0 1-4.66 4.64 4.66 4.64 0 0 1-4.66-4.64 4.66 4.64 0 0 1 4.66-4.64 4.66 4.64 0 0 1 4.66 4.64zm-17.25-7.145 5.89 7.12h6.73l-4.625-7.12H160m5.89 7.12 2.105-8.795m0 0 2.05-.011m-12.565 8.8 3.505-10.39h3.365"/><path d="M164.937 224.924c0 1.5-.995 3-3 3s-3.03-1.475-3-3c.03-1.525.95-3 3-3s3 1.5 3 3zm-1.5 0c.005-.73-.495-1.5-1.5-1.5s-1.5.745-1.5 1.5.5 1.5 1.5 1.5 1.495-.77 1.5-1.5zM171.427 227.948h-1.64l-1.85-2.5-.5.5v2h-1.5v-6h1.5v2.5l2.5-2.5h1.65l-2.65 2.6z"/><path fill="#fff" d="M42.818 255.06a1.374 1.374 0 1 0 0-2.748 1.374 1.374 0 0 0 0 2.748zm1.437-3.247c1.39-.52 2.462-1.403 2.474-2.872a.68.68 0 1 0-1.363 0c-.01.375-.17.694-.403.949a4.617 4.617 0 0 0-.176-1.127c.071-.118.1-.296.073-.555.24-1.383 1.765-2.283 2.748-2.73a.681.681 0 0 0-.553-1.245c-1.305.68-2.964 1.775-3.441 3.214-1.084-.505-2.098-1.64-2.28-2.687a.681.681 0 0 0-1.349.192c.282 1.68 1.443 2.925 2.89 3.654.42.18.453 1.159.43 1.511-1.27-.72-2.573-1.043-3.861-.41a.681.681 0 0 0 .609 1.218c1.514-.749 2.458.398 3.255.862.285.2.664.2.947.026z" style="paint-order:stroke fill markers" transform="translate(0 -866.52) scale(3.7795)"/><path fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.9993555" d="M176.204 48.988h-22.41a4.795 4.795 0 0 1-4.795-4.795v-20.41a4.795 4.795 0 0 1 4.795-4.795h22.41A4.795 4.795 0 0 1 181 23.783v20.41a4.795 4.795 0 0 1-4.795 4.795z"/><path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5004615000000001" d="M167.874 34.777a4.66 4.64 0 0 1 4.66-4.64 4.66 4.64 0 0 1 4.66 4.64 4.66 4.64 0 0 1-4.66 4.64 4.66 4.64 0 0 1-4.66-4.64zm-15.18 0a4.66 4.64 0 0 1 4.66-4.64 4.66 4.64 0 0 1 4.66 4.64 4.66 4.64 0 0 1-4.66 4.64 4.66 4.64 0 0 1-4.66-4.64zm17.25 7.146-5.89-7.12h-6.73l4.625 7.12h7.99m-5.89-7.12-2.105 8.795m0 0-2.05.01m12.565-8.8-3.505 10.39h-3.365"/><path d="M164.998 24.988c0-1.5.995-3 3-3s3.03 1.475 3 3c-.03 1.525-.95 3-3 3s-3-1.5-3-3zm1.5 0c-.005.73.495 1.5 1.5 1.5s1.5-.745 1.5-1.5-.5-1.5-1.5-1.5-1.495.77-1.5 1.5zM158.508 22.003h1.64l1.85 2.5.5-.5v-2h1.5v6h-1.5v-2.5l-2.5 2.5h-1.65l2.65-2.6z"/><path fill="gray" d="M0 0h112v256H0z"/></svg>
\ No newline at end of file


Test patch:
diff --git a/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreatorKtTest.kt b/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreatorKtTest.kt
index 76e0f0523f5..afed76e8a26 100644
--- a/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreatorKtTest.kt
+++ b/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreatorKtTest.kt
@@ -815,6 +815,71 @@ class CyclewayCreatorKtTest {
             cycleway(null, INVALID).applyTo(StringMapChangesBuilder(mapOf()), false)
         }
     }
+
+    @Test fun `apply allowed on sidewalk with both directions to one side`() {
+        assertEquals(
+            setOf(
+                StringMapEntryAdd("cycleway:right", "no"),
+                StringMapEntryAdd("sidewalk:right:bicycle", "yes"),
+                StringMapEntryAdd("sidewalk:right:bicycle:signed", "yes"),
+                StringMapEntryAdd("sidewalk:right:oneway:bicycle", "no")
+            ),
+            cycleway(
+                null,
+                SIDEWALK_OK to BOTH
+            ).appliedTo(mapOf())
+        )
+    }
+
+    @Test fun `apply allowed on sidewalk with both directions to left side`() {
+        assertEquals(
+            setOf(
+                StringMapEntryAdd("cycleway:left", "no"),
+                StringMapEntryAdd("sidewalk:left:bicycle", "yes"),
+                StringMapEntryAdd("sidewalk:left:bicycle:signed", "yes"),
+                StringMapEntryAdd("sidewalk:left:oneway:bicycle", "no")
+            ),
+            cycleway(
+                SIDEWALK_OK to BOTH,
+                null
+            ).appliedTo(mapOf())
+        )
+    }
+
+    @Test fun `apply allowed on sidewalk with both directions to both sides`() {
+        assertEquals(
+            setOf(
+                StringMapEntryAdd("cycleway:both", "no"),
+                StringMapEntryAdd("sidewalk:both:bicycle", "yes"),
+                StringMapEntryAdd("sidewalk:both:bicycle:signed", "yes"),
+                StringMapEntryAdd("sidewalk:both:oneway:bicycle", "no")
+            ),
+            cycleway(
+                SIDEWALK_OK to BOTH,
+                SIDEWALK_OK to BOTH
+            ).appliedTo(mapOf())
+        )
+    }
+
+    @Test fun `apply allowed on sidewalk in both directions to existing sidewalk ok`() {
+        assertEquals(
+            setOf(
+                StringMapEntryModify("cycleway:right", "no", "no"),
+                StringMapEntryModify("sidewalk:right:bicycle", "yes", "yes"),
+                StringMapEntryModify("sidewalk:right:bicycle:signed", "yes", "yes"),
+                StringMapEntryAdd("sidewalk:right:oneway:bicycle", "no")
+            ),
+            cycleway(
+                null,
+                SIDEWALK_OK to BOTH
+            ).appliedTo(mapOf(
+                "cycleway:right" to "no",
+                "sidewalk:right:bicycle" to "yes",
+                "sidewalk:right:bicycle:signed" to "yes"
+            ))
+        )
+    }
+
 }
 
 private fun LeftAndRightCycleway.appliedTo(tags: Map<String, String>, isLeftHandTraffic: Boolean = false): Set<StringMapEntryChange> {
diff --git a/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParserKtTest.kt b/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParserKtTest.kt
index 927d63de671..d2386ed4f7c 100644
--- a/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParserKtTest.kt
+++ b/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParserKtTest.kt
@@ -2343,6 +2343,75 @@ class CyclewayParserKtTest {
             )
         )
     }
+
+    @Test fun `right side sidewalk ok in both directions`() {
+        assertEquals(
+            cycleway(null, SIDEWALK_OK to BOTH),
+            parse(
+                "sidewalk:right:bicycle" to "yes",
+                "sidewalk:right:bicycle:signed" to "yes",
+                "sidewalk:right:oneway:bicycle" to "no",
+                "cycleway:right" to "no"
+            )
+        )
+    }
+
+    @Test fun `left side sidewalk ok in both directions`() {
+        assertEquals(
+            cycleway(SIDEWALK_OK to BOTH, null),
+            parse(
+                "sidewalk:left:bicycle" to "yes",
+                "sidewalk:left:bicycle:signed" to "yes",
+                "sidewalk:left:oneway:bicycle" to "no",
+                "cycleway:left" to "no"
+            )
+        )
+    }
+
+    @Test fun `both sides sidewalk ok in both directions`() {
+        assertEquals(
+            cycleway(SIDEWALK_OK to BOTH, SIDEWALK_OK to BOTH),
+            parse(
+                "sidewalk:both:bicycle" to "yes",
+                "sidewalk:both:bicycle:signed" to "yes",
+                "sidewalk:both:oneway:bicycle" to "no",
+                "cycleway:both" to "no"
+            )
+        )
+    }
+
+    @Test fun `right side sidewalk ok in both directions but missing signed yes leads to none`() {
+        assertEquals(
+            cycleway(null, NONE),
+            parse(
+                "sidewalk:right:bicycle" to "yes",
+                "sidewalk:right:oneway:bicycle" to "no",
+                "cycleway:right" to "no"
+            )
+        )
+    }
+
+    @Test fun `left side sidewalk ok in both directions but missing signed yes leads to none`() {
+        assertEquals(
+            cycleway(NONE, null),
+            parse(
+                "sidewalk:left:bicycle" to "yes",
+                "sidewalk:left:oneway:bicycle" to "no",
+                "cycleway:left" to "no"
+            )
+        )
+    }
+
+    @Test fun `both sides sidewalk ok in both directions but missing signed yes leads to none`() {
+        assertEquals(
+            cycleway(NONE, NONE),
+            parse(
+                "sidewalk:both:bicycle" to "yes",
+                "sidewalk:both:oneway:bicycle" to "no",
+                "cycleway:both" to "no"
+            )
+        )
+    }
 }
 
 private fun cycleway(left: Pair<Cycleway, Direction>?, right: Pair<Cycleway, Direction>?) =