Skip to content

Commit 0eb13b2

Browse files
authored
Migrate MoveNodeFragment speech bubble content to Compose (commonMain) (#6797)
1 parent 9f5fdaa commit 0eb13b2

File tree

9 files changed

+196
-115
lines changed

9 files changed

+196
-115
lines changed

app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/main/MainActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ import de.westnordost.streetcomplete.screens.main.bottom_sheet.CreateNoteFragmen
8484
import de.westnordost.streetcomplete.screens.main.bottom_sheet.IsCloseableBottomSheet
8585
import de.westnordost.streetcomplete.screens.main.bottom_sheet.IsMapOrientationAware
8686
import de.westnordost.streetcomplete.screens.main.bottom_sheet.IsMapPositionAware
87-
import de.westnordost.streetcomplete.screens.main.bottom_sheet.MoveNodeFragment
87+
import de.westnordost.streetcomplete.screens.main.bottom_sheet.move_node.MoveNodeFragment
8888
import de.westnordost.streetcomplete.screens.main.bottom_sheet.SplitWayFragment
8989
import de.westnordost.streetcomplete.screens.main.controls.LocationState
9090
import de.westnordost.streetcomplete.screens.main.edithistory.EditHistoryViewModel

app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/main/bottom_sheet/ArrowDrawable.kt renamed to app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/main/bottom_sheet/move_node/ArrowDrawable.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package de.westnordost.streetcomplete.screens.main.bottom_sheet
1+
package de.westnordost.streetcomplete.screens.main.bottom_sheet.move_node
22

33
import android.content.res.Resources
44
import android.graphics.Canvas

app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/main/bottom_sheet/MoveNodeFragment.kt renamed to app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/main/bottom_sheet/move_node/MoveNodeFragment.kt

Lines changed: 28 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package de.westnordost.streetcomplete.screens.main.bottom_sheet
1+
package de.westnordost.streetcomplete.screens.main.bottom_sheet.move_node
22

33
import android.content.res.Configuration
44
import android.graphics.PointF
@@ -7,6 +7,10 @@ import android.view.View
77
import android.view.animation.AnimationUtils
88
import androidx.annotation.UiThread
99
import androidx.appcompat.app.AlertDialog
10+
import androidx.compose.foundation.layout.padding
11+
import androidx.compose.runtime.mutableFloatStateOf
12+
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.unit.dp
1014
import androidx.core.graphics.toPointF
1115
import androidx.core.os.bundleOf
1216
import androidx.fragment.app.Fragment
@@ -28,13 +32,12 @@ import de.westnordost.streetcomplete.data.osm.mapdata.Node
2832
import de.westnordost.streetcomplete.data.osm.mapdata.key
2933
import de.westnordost.streetcomplete.databinding.FragmentMoveNodeBinding
3034
import de.westnordost.streetcomplete.overlays.IsShowingElement
31-
import de.westnordost.streetcomplete.screens.measure.MeasureDisplayUnit
32-
import de.westnordost.streetcomplete.screens.measure.MeasureDisplayUnitFeetInch
33-
import de.westnordost.streetcomplete.screens.measure.MeasureDisplayUnitMeter
35+
import de.westnordost.streetcomplete.screens.main.bottom_sheet.IsCloseableBottomSheet
36+
import de.westnordost.streetcomplete.screens.main.bottom_sheet.IsMapPositionAware
37+
import de.westnordost.streetcomplete.ui.common.FloatingOkButton
38+
import de.westnordost.streetcomplete.ui.util.content
3439
import de.westnordost.streetcomplete.util.ktx.awaitLayout
3540
import de.westnordost.streetcomplete.util.ktx.getLocationInWindow
36-
import de.westnordost.streetcomplete.util.ktx.popIn
37-
import de.westnordost.streetcomplete.util.ktx.popOut
3841
import de.westnordost.streetcomplete.util.ktx.setMargins
3942
import de.westnordost.streetcomplete.util.ktx.viewLifecycleScope
4043
import de.westnordost.streetcomplete.util.math.distanceTo
@@ -67,6 +70,8 @@ class MoveNodeFragment :
6770

6871
private lateinit var arrowDrawable: ArrowDrawable
6972

73+
private val distance = mutableFloatStateOf(0f)
74+
7075
private val hasChanges get() = getMarkerPosition() != node.position
7176

7277
interface Listener {
@@ -98,8 +103,6 @@ class MoveNodeFragment :
98103
binding.arrowView.setImageDrawable(arrowDrawable)
99104
arrowDrawable.setTint(requireContext().resources.getColor(R.color.accent))
100105

101-
binding.okButton.setOnClickListener { onClickOk() }
102-
binding.cancelButton.setOnClickListener { activity?.onBackPressed() }
103106
binding.pin.pinIconView.setImageResource(editType.icon)
104107

105108
val cornerRadius = resources.getDimension(R.dimen.speech_bubble_rounded_corner_radius)
@@ -114,6 +117,22 @@ class MoveNodeFragment :
114117
)
115118
}
116119

120+
binding.composeView.content {
121+
MoveNodeForm(
122+
distance = distance.floatValue,
123+
displayUnit = displayUnit,
124+
onClickCancel = { activity?.onBackPressed() },
125+
)
126+
}
127+
128+
binding.okButtonComposeView.content {
129+
FloatingOkButton(
130+
visible = distance.floatValue.toDouble() in MIN_MOVE_DISTANCE..MAX_MOVE_DISTANCE,
131+
onClick = { onClickOk() },
132+
modifier = Modifier.padding(8.dp),
133+
)
134+
}
135+
117136
// to lay out the arrow drawable correctly, view must have been layouted first
118137
viewLifecycleOwner.lifecycleScope.launch {
119138
view.awaitLayout()
@@ -143,7 +162,6 @@ class MoveNodeFragment :
143162

144163
private fun onClickOk() {
145164
val position = getMarkerPosition() ?: return
146-
if (!checkIsDistanceOkAndUpdateText(position)) return
147165
viewLifecycleScope.launch {
148166
moveNodeTo(position)
149167
}
@@ -163,36 +181,14 @@ class MoveNodeFragment :
163181
@UiThread override fun onMapMoved(position: LatLon) {
164182
updateArrowDrawable()
165183

166-
if (checkIsDistanceOkAndUpdateText(position)) {
167-
binding.okButton.popIn()
168-
} else {
169-
binding.okButton.popOut()
170-
}
184+
distance.floatValue = position.distanceTo(node.position).toFloat()
171185
}
172186

173187
private fun updateArrowDrawable() {
174188
arrowDrawable.startPoint = listener?.getScreenPositionAt(node.position)
175189
arrowDrawable.endPoint = getMarkerScreenPosition()
176190
}
177191

178-
private fun checkIsDistanceOkAndUpdateText(position: LatLon): Boolean {
179-
val moveDistance = position.distanceTo(node.position)
180-
return when {
181-
moveDistance < MIN_MOVE_DISTANCE -> {
182-
binding.titleLabel.setText(R.string.node_moved_not_far_enough)
183-
false
184-
}
185-
moveDistance > MAX_MOVE_DISTANCE -> {
186-
binding.titleLabel.setText(R.string.node_moved_too_far)
187-
false
188-
}
189-
else -> {
190-
binding.titleLabel.text = resources.getString(R.string.node_moved, displayUnit.format(moveDistance.toFloat()))
191-
true
192-
}
193-
}
194-
}
195-
196192
@UiThread override fun onClickClose(onConfirmed: () -> Unit) {
197193
if (!hasChanges) {
198194
onConfirmed()
@@ -223,14 +219,3 @@ class MoveNodeFragment :
223219
}
224220
}
225221
}
226-
227-
// Require a minimum distance because the map is not perfectly precise, it may be hard to tell
228-
// whether something really is misplaced without good aerial imagery.
229-
// Also, POIs are objects with a certain extent, so as long as the node is within this extent, it's
230-
// fine, there is little value of putting the point at exactly the center point of the POI
231-
private const val MIN_MOVE_DISTANCE = 1.0
232-
// Move node functionality is meant for fixing slightly misplaced elements. If something moved far
233-
// away, it is reasonable to assume there are more substantial changes required, also to nearby
234-
// elements. Additionally, the default radius for highlighted elements is 30 m, so moving outside
235-
// should not be allowed.
236-
private const val MAX_MOVE_DISTANCE = 30.0

app/src/androidMain/res/layout/fragment_move_node.xml

Lines changed: 11 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -54,81 +54,25 @@
5454
android:background="@drawable/speech_bubble_none"
5555
android:elevation="@dimen/speech_bubble_elevation">
5656

57-
<LinearLayout
57+
<androidx.compose.ui.platform.ComposeView
58+
android:id="@+id/composeView"
5859
android:layout_width="match_parent"
5960
android:layout_height="wrap_content"
60-
android:orientation="vertical"
61-
android:showDividers="middle"
62-
android:divider="@drawable/button_bar_divider">
63-
64-
<RelativeLayout
65-
android:id="@+id/content"
66-
android:layout_height="wrap_content"
67-
android:layout_width="match_parent"
68-
android:paddingStart="16dp"
69-
android:paddingEnd="16dp"
70-
android:paddingTop="10dp"
71-
android:paddingBottom="24dp">
72-
73-
<TextView
74-
android:id="@+id/titleLabel"
75-
android:text="@string/node_moved_not_far_enough"
76-
android:layout_width="match_parent"
77-
android:layout_height="wrap_content"
78-
android:textAppearance="@style/TextAppearance.TitleLarge"
79-
android:layout_marginBottom="8dp"/>
80-
81-
<TextView
82-
android:layout_width="match_parent"
83-
android:layout_height="wrap_content"
84-
android:text="@string/move_node_description"
85-
android:layout_below="@id/titleLabel"
86-
/>
87-
88-
</RelativeLayout>
89-
90-
<LinearLayout
91-
android:id="@+id/buttonPanel"
92-
android:layout_width="match_parent"
93-
android:layout_height="wrap_content"
94-
android:orientation="horizontal"
95-
android:layoutDirection="locale"
96-
style="@style/ButtonBar">
97-
98-
<Button
99-
android:id="@+id/cancelButton"
100-
android:layout_width="wrap_content"
101-
android:layout_height="wrap_content"
102-
android:text="@android:string/cancel"
103-
style="@style/BottomSheetButtonBarItem"/>
104-
105-
</LinearLayout>
106-
107-
</LinearLayout>
61+
android:paddingStart="16dp"
62+
android:paddingEnd="16dp"
63+
android:paddingTop="10dp"
64+
android:paddingBottom="24dp" />
10865

10966
</de.westnordost.streetcomplete.view.MaskSpeechbubbleCornersFrameLayout>
11067

11168
</FrameLayout>
11269

113-
<ImageView
114-
android:id="@+id/okButton"
115-
app:srcCompat="@drawable/ic_check_48dp"
116-
android:layout_width="@dimen/ok_button_size"
117-
android:layout_height="@dimen/ok_button_size"
70+
<androidx.compose.ui.platform.ComposeView
71+
android:id="@+id/okButtonComposeView"
72+
android:layout_width="wrap_content"
73+
android:layout_height="wrap_content"
11874
android:layout_alignParentEnd="true"
119-
android:layout_alignParentBottom="true"
120-
android:scaleType="centerInside"
121-
style="@style/RoundAccentButton"
122-
android:layout_margin="8dp"
123-
android:visibility="gone"
124-
android:scaleX="0.5"
125-
android:scaleY="0.5"
126-
android:alpha="0"
127-
tools:alpha="1"
128-
tools:visibility="visible"
129-
tools:scaleX="1"
130-
tools:scaleY="1"
131-
android:padding="20dp"/>
75+
android:layout_alignParentBottom="true" />
13276

13377
</de.westnordost.streetcomplete.view.SlidingRelativeLayout>
13478

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="48dp"
3+
android:height="48dp"
4+
android:viewportWidth="24"
5+
android:viewportHeight="24">
6+
<path
7+
android:fillColor="#000"
8+
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
9+
</vector>

app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/measure/MeasureDisplayUnit.kt renamed to app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/bottom_sheet/move_node/MeasureDisplayUnit.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
package de.westnordost.streetcomplete.screens.measure
1+
package de.westnordost.streetcomplete.screens.main.bottom_sheet.move_node
22

3+
import de.westnordost.streetcomplete.util.locale.NumberFormatter
34
import kotlinx.serialization.Serializable
45
import kotlin.math.floor
56
import kotlin.math.round
@@ -23,7 +24,8 @@ data class MeasureDisplayUnitMeter(val cmStep: Int) : MeasureDisplayUnit() {
2324
cmStep % 10 == 0 -> 1
2425
else -> 2
2526
}
26-
return "%.${decimals}f m".format(getRounded(distanceMeters))
27+
val formatter = NumberFormatter(minFractionDigits = decimals, maxFractionDigits = decimals)
28+
return "${formatter.format(getRounded(distanceMeters))} m"
2729
}
2830

2931
/** Returns the given distance in meters rounded to the given precision */
@@ -40,7 +42,7 @@ data class MeasureDisplayUnitFeetInch(val inchStep: Int) : MeasureDisplayUnit()
4042

4143
override fun format(distanceMeters: Float): String {
4244
val (feet, inches) = getRounded(distanceMeters)
43-
return if (inches < 10) "$feet$inches" else "$feet$inches"
45+
return if (inches < 10) "$feet\u2007$inches" else "$feet$inches"
4446
}
4547

4648
/** Returns the given distance in meters as feet + inch */
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package de.westnordost.streetcomplete.screens.main.bottom_sheet.move_node
2+
3+
import androidx.compose.foundation.layout.Column
4+
import androidx.compose.foundation.layout.fillMaxWidth
5+
import androidx.compose.foundation.layout.padding
6+
import androidx.compose.material.ContentAlpha
7+
import androidx.compose.material.Divider
8+
import androidx.compose.material.MaterialTheme
9+
import androidx.compose.material.Text
10+
import androidx.compose.material.TextButton
11+
import androidx.compose.runtime.Composable
12+
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.draw.alpha
14+
import androidx.compose.ui.unit.dp
15+
import de.westnordost.streetcomplete.resources.*
16+
import de.westnordost.streetcomplete.ui.common.ButtonBar
17+
import de.westnordost.streetcomplete.ui.theme.titleLarge
18+
import org.jetbrains.compose.resources.stringResource
19+
20+
// Require a minimum distance because the map is not perfectly precise, it may be hard to tell
21+
// whether something really is misplaced without good aerial imagery.
22+
// Also, POIs are objects with a certain extent, so as long as the node is within this extent, it's
23+
// fine, there is little value of putting the point at exactly the center point of the POI
24+
const val MIN_MOVE_DISTANCE = 1.0
25+
// Move node functionality is meant for fixing slightly misplaced elements. If something moved far
26+
// away, it is reasonable to assume there are more substantial changes required, also to nearby
27+
// elements. Additionally, the default radius for highlighted elements is 30 m, so moving outside
28+
// should not be allowed.
29+
const val MAX_MOVE_DISTANCE = 30.0
30+
31+
/** Bottom sheet content for the move node UI */
32+
@Composable
33+
fun MoveNodeForm(
34+
distance: Float,
35+
displayUnit: MeasureDisplayUnit,
36+
onClickCancel: () -> Unit,
37+
modifier: Modifier = Modifier,
38+
) {
39+
Column(
40+
modifier = modifier.fillMaxWidth()
41+
) {
42+
Text(
43+
text = when {
44+
distance < MIN_MOVE_DISTANCE ->
45+
stringResource(Res.string.node_moved_not_far_enough)
46+
distance > MAX_MOVE_DISTANCE ->
47+
stringResource(Res.string.node_moved_too_far)
48+
else ->
49+
stringResource(Res.string.node_moved, displayUnit.format(distance))
50+
},
51+
style = MaterialTheme.typography.titleLarge,
52+
modifier = Modifier.padding(bottom = 8.dp)
53+
)
54+
Text(
55+
text = stringResource(Res.string.move_node_description),
56+
style = MaterialTheme.typography.body2,
57+
modifier = Modifier.alpha(ContentAlpha.medium)
58+
)
59+
60+
Divider()
61+
62+
// button panel
63+
ButtonBar {
64+
TextButton(onClick = onClickCancel) {
65+
Text(stringResource(Res.string.cancel))
66+
}
67+
}
68+
}
69+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package de.westnordost.streetcomplete.ui.common
2+
3+
import androidx.compose.foundation.layout.IntrinsicSize
4+
import androidx.compose.foundation.layout.Row
5+
import androidx.compose.foundation.layout.RowScope
6+
import androidx.compose.foundation.layout.fillMaxWidth
7+
import androidx.compose.foundation.layout.height
8+
import androidx.compose.runtime.Composable
9+
import androidx.compose.ui.Alignment
10+
import androidx.compose.ui.Modifier
11+
12+
/** Horizontal button bar for bottom sheets. Add [HorizontalDivider] between buttons as needed. */
13+
@Composable
14+
fun ButtonBar(
15+
modifier: Modifier = Modifier,
16+
content: @Composable RowScope.() -> Unit,
17+
) {
18+
Row(
19+
modifier = modifier
20+
.fillMaxWidth()
21+
.height(IntrinsicSize.Min),
22+
verticalAlignment = Alignment.CenterVertically,
23+
content = content,
24+
)
25+
}

0 commit comments

Comments
 (0)