diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/AbstractQuestForm.kt b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/AbstractQuestForm.kt index 40429331c68..4a110cce19c 100644 --- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/AbstractQuestForm.kt +++ b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/AbstractQuestForm.kt @@ -27,6 +27,7 @@ import de.westnordost.streetcomplete.databinding.ButtonPanelButtonBinding import de.westnordost.streetcomplete.databinding.FragmentQuestAnswerBinding import de.westnordost.streetcomplete.screens.main.bottom_sheet.AbstractBottomSheetFragment import de.westnordost.streetcomplete.screens.main.bottom_sheet.IsMapOrientationAware +import de.westnordost.streetcomplete.ui.common.quest.QuestHeader import de.westnordost.streetcomplete.ui.util.content import de.westnordost.streetcomplete.util.FragmentViewBindingPropertyDelegate import de.westnordost.streetcomplete.util.ktx.popIn @@ -137,7 +138,7 @@ abstract class AbstractQuestForm : protected open fun getHintImages(): List = questType.hintImages @Composable - protected open fun ContentBeforeSpeechbubbleContent() {} + protected open fun ContentBeforeSpeechBubbleContent() {} override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -152,7 +153,7 @@ abstract class AbstractQuestForm : } } binding.contentBeforeSpeechbubbleContent.content { - ContentBeforeSpeechbubbleContent() + ContentBeforeSpeechBubbleContent() } binding.okButton.setOnClickListener { diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/LeaveNoteInsteadFragment.kt b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/LeaveNoteInsteadFragment.kt index c268df450af..478fb9bf52e 100644 --- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/LeaveNoteInsteadFragment.kt +++ b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/LeaveNoteInsteadFragment.kt @@ -26,6 +26,7 @@ import de.westnordost.streetcomplete.databinding.FragmentQuestAnswerBinding import de.westnordost.streetcomplete.quests.note_comments.NoteForm import de.westnordost.streetcomplete.resources.* import de.westnordost.streetcomplete.screens.main.bottom_sheet.AbstractCreateNoteFragment +import de.westnordost.streetcomplete.ui.common.quest.QuestHeader import de.westnordost.streetcomplete.ui.theme.titleLarge import de.westnordost.streetcomplete.ui.util.content import de.westnordost.streetcomplete.util.ktx.viewLifecycleScope diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/note_discussion/NoteDiscussionForm.kt b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/note_discussion/NoteDiscussionForm.kt index 9926e285442..2e69d893bfb 100644 --- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/note_discussion/NoteDiscussionForm.kt +++ b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/note_discussion/NoteDiscussionForm.kt @@ -37,6 +37,7 @@ import de.westnordost.streetcomplete.quests.AnswerItem import de.westnordost.streetcomplete.quests.note_comments.NoteCommentItem import de.westnordost.streetcomplete.quests.note_comments.NoteForm import de.westnordost.streetcomplete.screens.main.bottom_sheet.AbstractCreateNoteFragment +import de.westnordost.streetcomplete.ui.theme.defaultTextLinkStyles import de.westnordost.streetcomplete.ui.util.content import de.westnordost.streetcomplete.ui.util.rememberSerializable import de.westnordost.streetcomplete.util.image.loadImageBitmap @@ -126,16 +127,8 @@ class NoteDiscussionForm : AbstractQuestForm(), TakePhotoFragment.Listener { } @Composable - override fun ContentBeforeSpeechbubbleContent() { - val textLinkStyles = TextLinkStyles( - style = SpanStyle( - color = MaterialTheme.colors.primary, - textDecoration = TextDecoration.Underline - ), - focusedStyle = SpanStyle( - color = MaterialTheme.colors.secondary, - ) - ) + override fun ContentBeforeSpeechBubbleContent() { + val textLinkStyles = MaterialTheme.typography.defaultTextLinkStyles() ProvideTextStyle(MaterialTheme.typography.body2) { Column( verticalArrangement = Arrangement.spacedBy(8.dp), diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/main/bottom_sheet/CreateNoteFragment.kt b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/main/bottom_sheet/CreateNoteFragment.kt index 9c474bd104f..11bfddee13e 100644 --- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/main/bottom_sheet/CreateNoteFragment.kt +++ b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/main/bottom_sheet/CreateNoteFragment.kt @@ -26,9 +26,9 @@ import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditAction import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditsController import de.westnordost.streetcomplete.data.osmtracks.Trackpoint import de.westnordost.streetcomplete.databinding.FragmentCreateNoteBinding -import de.westnordost.streetcomplete.quests.QuestHeader import de.westnordost.streetcomplete.quests.note_comments.NoteForm import de.westnordost.streetcomplete.resources.* +import de.westnordost.streetcomplete.ui.common.quest.QuestHeader import de.westnordost.streetcomplete.ui.util.content import de.westnordost.streetcomplete.util.ktx.getLocationInWindow import de.westnordost.streetcomplete.util.ktx.viewLifecycleScope diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/main/edithistory/EditDetails.kt b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/main/edithistory/EditDetails.kt index c96a5365efe..c1bc9bf8163 100644 --- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/main/edithistory/EditDetails.kt +++ b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/main/edithistory/EditDetails.kt @@ -7,10 +7,12 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.ContentAlpha import androidx.compose.material.Divider +import androidx.compose.material.LocalContentAlpha import androidx.compose.material.LocalContentColor import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -45,11 +47,12 @@ fun EditDetails( modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp) ) { - Text( - text = dateTimeFormatter.format(createdTime), - style = MaterialTheme.typography.body2, - color = LocalContentColor.current.copy(alpha = ContentAlpha.medium), - ) + CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) { + Text( + text = dateTimeFormatter.format(createdTime), + style = MaterialTheme.typography.body2, + ) + } Row( horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/main/teammode/TeamModeWizard.kt b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/main/teammode/TeamModeWizard.kt index bc0e1763633..884c06a4042 100644 --- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/main/teammode/TeamModeWizard.kt +++ b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/main/teammode/TeamModeWizard.kt @@ -52,6 +52,7 @@ import kotlinx.coroutines.delay import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource import androidx.compose.ui.tooling.preview.Preview +import de.westnordost.streetcomplete.ui.theme.divider /** Wizard which enables team mode */ @Composable @@ -200,7 +201,7 @@ private fun SplitQuestsIllustration( } val arrangement = Arrangement.spacedBy((-48 + 64 * padding.value).dp) - val dividerColor = MaterialTheme.colors.onSurface.copy(alpha = 0.12f) + val dividerColor = MaterialTheme.colors.divider val dividerWidth = 4.dp.toPx() Column( modifier = Modifier diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/settings/overlay_selection/OverlaySelectionRow.kt b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/settings/overlay_selection/OverlaySelectionRow.kt index f46854feaf6..dad24414fb4 100644 --- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/settings/overlay_selection/OverlaySelectionRow.kt +++ b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/settings/overlay_selection/OverlaySelectionRow.kt @@ -13,10 +13,12 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.Checkbox import androidx.compose.material.ContentAlpha +import androidx.compose.material.LocalContentAlpha import androidx.compose.material.LocalContentColor import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -73,10 +75,11 @@ fun OverlaySelectionRow( @Composable private fun DisabledHint(text: String) { - Text( - text = text, - style = MaterialTheme.typography.body2, - fontStyle = FontStyle.Italic, - color = LocalContentColor.current.copy(alpha = ContentAlpha.medium), - ) + CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) { + Text( + text = text, + style = MaterialTheme.typography.body2, + fontStyle = FontStyle.Italic, + ) + } } diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/settings/presets/EditTypePresetsScreen.kt b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/settings/presets/EditTypePresetsScreen.kt index e5cf9fd6e73..faee9af68c4 100644 --- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/settings/presets/EditTypePresetsScreen.kt +++ b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/settings/presets/EditTypePresetsScreen.kt @@ -100,7 +100,7 @@ import org.jetbrains.compose.resources.stringResource private fun EditTypePresetsList( viewModel: EditTypePresetsViewModel, modifier: Modifier = Modifier, - contentPadding: PaddingValues = PaddingValues(0.dp) + contentPadding: PaddingValues = PaddingValues.Zero ) { val presets by viewModel.presets.collectAsState() diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/settings/quest_selection/QuestSelectionList.kt b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/settings/quest_selection/QuestSelectionList.kt index 69f060c52f9..abccc03fde4 100644 --- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/settings/quest_selection/QuestSelectionList.kt +++ b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/settings/quest_selection/QuestSelectionList.kt @@ -45,7 +45,7 @@ fun QuestSelectionList( onSelect: (questType: QuestType, selected: Boolean) -> Unit, onReorder: (questType: QuestType, toAfter: QuestType) -> Unit, modifier: Modifier = Modifier, - contentPadding: PaddingValues = PaddingValues(0.dp), + contentPadding: PaddingValues = PaddingValues.Zero, ) { var showEnableQuestDialog by remember { mutableStateOf(null) } diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/settings/quest_selection/QuestSelectionRow.kt b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/settings/quest_selection/QuestSelectionRow.kt index b95669c8ab1..4671dce5232 100644 --- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/settings/quest_selection/QuestSelectionRow.kt +++ b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/settings/quest_selection/QuestSelectionRow.kt @@ -15,10 +15,12 @@ import androidx.compose.foundation.layout.width import androidx.compose.material.Checkbox import androidx.compose.material.ContentAlpha import androidx.compose.material.Icon +import androidx.compose.material.LocalContentAlpha import androidx.compose.material.LocalContentColor import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -92,12 +94,13 @@ fun QuestSelectionRow( @Composable private fun DisabledHint(text: String) { - Text( - text = text, - style = MaterialTheme.typography.body2, - fontStyle = FontStyle.Italic, - color = LocalContentColor.current.copy(alpha = ContentAlpha.medium), - ) + CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) { + Text( + text = text, + style = MaterialTheme.typography.body2, + fontStyle = FontStyle.Italic, + ) + } } @Preview diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/user/edits/EditTypeStatisticsColumn.kt b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/user/edits/EditTypeStatisticsColumn.kt index 9e60237b48a..314a51957bc 100644 --- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/user/edits/EditTypeStatisticsColumn.kt +++ b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/screens/user/edits/EditTypeStatisticsColumn.kt @@ -26,7 +26,7 @@ import de.westnordost.streetcomplete.ui.theme.GrassGreen fun EditTypeStatisticsColumn( statistics: List, modifier: Modifier = Modifier, - contentPadding: PaddingValues = PaddingValues(0.dp), + contentPadding: PaddingValues = PaddingValues.Zero, ) { var showInfo by remember { mutableStateOf(null) } diff --git a/app/src/commonMain/composeResources/drawable/ic_check_48.xml b/app/src/commonMain/composeResources/drawable/ic_check_32.xml similarity index 83% rename from app/src/commonMain/composeResources/drawable/ic_check_48.xml rename to app/src/commonMain/composeResources/drawable/ic_check_32.xml index 7328e3761b4..ad0346dc138 100644 --- a/app/src/commonMain/composeResources/drawable/ic_check_48.xml +++ b/app/src/commonMain/composeResources/drawable/ic_check_32.xml @@ -1,6 +1,6 @@ LanesButtonContent( laneCount = laneCount, diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/max_speed/MaxSpeedForm.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/max_speed/MaxSpeedForm.kt index 12074d8e211..d6b33c4e04b 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/max_speed/MaxSpeedForm.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/max_speed/MaxSpeedForm.kt @@ -6,10 +6,12 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.ContentAlpha +import androidx.compose.material.LocalContentAlpha import androidx.compose.material.LocalContentColor import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -66,11 +68,12 @@ fun MaxSpeedForm( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp) ) { - Text( - text = stringResource(Res.string.quest_maxspeed_type_description), - color = LocalContentColor.current.copy(alpha = ContentAlpha.medium), - style = MaterialTheme.typography.body2, - ) + CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) { + Text( + text = stringResource(Res.string.quest_maxspeed_type_description), + style = MaterialTheme.typography.body2, + ) + } DropdownButton( items = selectableMaxSpeedTypes, onSelectedItem = { onAnswer(it) }, diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/max_speed/RoadTypeSelect.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/max_speed/RoadTypeSelect.kt index 955c62aa307..c4f5c005482 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/max_speed/RoadTypeSelect.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/max_speed/RoadTypeSelect.kt @@ -2,10 +2,12 @@ package de.westnordost.streetcomplete.quests.max_speed import androidx.compose.foundation.layout.Column import androidx.compose.material.ContentAlpha +import androidx.compose.material.LocalContentAlpha import androidx.compose.material.LocalContentColor import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -33,11 +35,12 @@ fun RoadTypeSelect( horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier, ) { - Text( - text = stringResource(Res.string.quest_maxspeed_answer_roadtype_description), - color = LocalContentColor.current.copy(alpha = ContentAlpha.medium), - style = MaterialTheme.typography.body2, - ) + CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) { + Text( + text = stringResource(Res.string.quest_maxspeed_answer_roadtype_description), + style = MaterialTheme.typography.body2, + ) + } ItemSelectGrid( columns = SimpleGridCells.Fixed(cells), items = selectable, diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/note_comments/NoteCommentItem.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/note_comments/NoteCommentItem.kt index 4b4aa8a0aa7..48561078899 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/note_comments/NoteCommentItem.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/note_comments/NoteCommentItem.kt @@ -1,6 +1,5 @@ package de.westnordost.streetcomplete.quests.note_comments -import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -8,11 +7,9 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.ContentAlpha import androidx.compose.material.Divider -import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -24,11 +21,13 @@ import androidx.compose.ui.text.TextLinkStyles import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.withLink +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import de.westnordost.streetcomplete.data.osmnotes.NoteComment import de.westnordost.streetcomplete.resources.* -import de.westnordost.streetcomplete.ui.common.SpeechBubbleArrowDirection -import de.westnordost.streetcomplete.ui.common.SpeechBubbleShape +import de.westnordost.streetcomplete.ui.common.speech_bubble.SpeechBubble +import de.westnordost.streetcomplete.ui.common.speech_bubble.SpeechBubbleArrowDirection +import de.westnordost.streetcomplete.ui.common.speech_bubble.SpeechBubbleNoArrow import de.westnordost.streetcomplete.ui.util.annotateLinks import de.westnordost.streetcomplete.ui.util.formatAnnotated import de.westnordost.streetcomplete.util.ktx.toLocalDateTime @@ -47,6 +46,7 @@ fun NoteCommentItem( noteComment: NoteComment, avatarPainter: Painter?, modifier: Modifier = Modifier, + elevation: Dp = 16.dp, textLinkStyles: TextLinkStyles? = null ) { val annotatedUserName = buildAnnotatedString { @@ -83,9 +83,8 @@ fun NoteCommentItem( Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { Surface( modifier = Modifier.size(50.dp), - elevation = 16.dp, + elevation = elevation, shape = CircleShape, - border = BorderStroke(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.12f)) ) { Image( painter = avatarPainter ?: painterResource(Res.drawable.avatar_osm_anonymous), @@ -93,23 +92,12 @@ fun NoteCommentItem( ) } - val speechBubbleShape = SpeechBubbleShape( + SpeechBubble( arrowDirection = SpeechBubbleArrowDirection.Start, - cornerRadius = 16.dp, - arrowSize = 10.dp, - ) - Surface( - elevation = 16.dp, - shape = speechBubbleShape, - border = BorderStroke(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.12f)) + elevation = elevation, ) { SelectionContainer { - Column( - modifier = Modifier - .padding(speechBubbleShape.contentPadding) - .padding(horizontal = 16.dp, vertical = 12.dp), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { Text(annotatedCommentText) Divider() Text( @@ -125,11 +113,7 @@ fun NoteCommentItem( // the action (if anything else than a normal comment) is shown in a separate bubble, just // like for example in github ("comment and close") if (actionTextResource != null) { - Surface( - elevation = 16.dp, - shape = RoundedCornerShape(16.dp), - border = BorderStroke(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.12f)) - ) { + SpeechBubbleNoArrow(elevation = elevation) { Text( text = stringResource(actionTextResource) .formatAnnotated(annotatedUserName, dateText), diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/bottom_sheet/move_node/MoveNodeForm.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/bottom_sheet/move_node/MoveNodeForm.kt index 88ebd4971d3..14dff59b08e 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/bottom_sheet/move_node/MoveNodeForm.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/bottom_sheet/move_node/MoveNodeForm.kt @@ -5,15 +5,16 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.ContentAlpha import androidx.compose.material.Divider +import androidx.compose.material.LocalContentAlpha import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.unit.dp import de.westnordost.streetcomplete.resources.* -import de.westnordost.streetcomplete.ui.common.ButtonBar import de.westnordost.streetcomplete.ui.theme.titleLarge import org.jetbrains.compose.resources.stringResource @@ -51,19 +52,18 @@ fun MoveNodeForm( style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(bottom = 8.dp) ) - Text( - text = stringResource(Res.string.move_node_description), - style = MaterialTheme.typography.body2, - modifier = Modifier.alpha(ContentAlpha.medium) - ) + CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) { + Text( + text = stringResource(Res.string.move_node_description), + style = MaterialTheme.typography.body2, + ) + } Divider() // button panel - ButtonBar { - TextButton(onClick = onClickCancel) { - Text(stringResource(Res.string.cancel)) - } + Column(Modifier.fillMaxWidth()) { + TextButton(onClickCancel) { Text(stringResource(Res.string.cancel)) } } } } diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/controls/Crosshair.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/controls/Crosshair.kt index 04b2e6b1b72..106412162c3 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/controls/Crosshair.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/controls/Crosshair.kt @@ -1,6 +1,6 @@ package de.westnordost.streetcomplete.screens.main.controls -import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material.ContentAlpha @@ -10,22 +10,23 @@ import androidx.compose.material.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalWindowInfo import de.westnordost.streetcomplete.resources.* import de.westnordost.streetcomplete.ui.theme.AppTheme -import de.westnordost.streetcomplete.ui.theme.getOpenQuestFormMapPadding import org.jetbrains.compose.resources.painterResource import androidx.compose.ui.tooling.preview.Preview +import de.westnordost.streetcomplete.ui.theme.Dimensions /** A crosshair at the position at which a new POI should be created */ @Composable fun Crosshair(modifier: Modifier = Modifier) { - BoxWithConstraints(modifier.fillMaxSize()) { + Box(modifier.fillMaxSize()) { Icon( painter = painterResource(Res.drawable.crosshair), contentDescription = null, modifier = Modifier .align(Alignment.Center) - .padding(getOpenQuestFormMapPadding(maxWidth, maxHeight)), + .padding(Dimensions.getOpenQuestFormMapPadding(LocalWindowInfo.current)), tint = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.medium) ) } diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/controls/MapButton.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/controls/MapButton.kt index 44338fc6d1b..1e7a19038ea 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/controls/MapButton.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/controls/MapButton.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.unit.dp import de.westnordost.streetcomplete.resources.* import org.jetbrains.compose.resources.painterResource import androidx.compose.ui.tooling.preview.Preview +import de.westnordost.streetcomplete.ui.theme.divider /** Small floating button on top of the map */ @OptIn(ExperimentalMaterialApi::class) @@ -39,7 +40,7 @@ fun MapButton( shape = CircleShape, color = colors.backgroundColor(enabled).value, contentColor = colors.contentColor(enabled).value, - border = BorderStroke(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.12f)), + border = BorderStroke(1.dp, MaterialTheme.colors.divider), elevation = 4.dp ) { Box(modifier = Modifier.padding(contentPadding), content = content) diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/controls/PointerPinButton.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/controls/PointerPinButton.kt index b7a955cb3a6..cf67551556d 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/controls/PointerPinButton.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/controls/PointerPinButton.kt @@ -33,6 +33,7 @@ import androidx.compose.ui.unit.dp import de.westnordost.streetcomplete.resources.* import de.westnordost.streetcomplete.ui.ktx.proportionalAbsoluteOffset import de.westnordost.streetcomplete.ui.ktx.proportionalPadding +import de.westnordost.streetcomplete.ui.theme.divider import org.jetbrains.compose.resources.painterResource import kotlin.math.PI import kotlin.math.cos @@ -69,7 +70,7 @@ fun PointerPinButton( shape = pointerPinShape, color = colors.backgroundColor(enabled).value, contentColor = colors.contentColor(enabled).value, - border = BorderStroke(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.12f)), + border = BorderStroke(1.dp, MaterialTheme.colors.divider), elevation = 4.dp ) { Box(Modifier diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/controls/ZoomButtons.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/controls/ZoomButtons.kt index 61c9fe07450..7a54f4b4006 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/controls/ZoomButtons.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/main/controls/ZoomButtons.kt @@ -20,6 +20,7 @@ import de.westnordost.streetcomplete.ui.common.ZoomInIcon import de.westnordost.streetcomplete.ui.common.ZoomOutIcon import androidx.compose.ui.tooling.preview.Preview import de.westnordost.streetcomplete.ui.ktx.pxToDp +import de.westnordost.streetcomplete.ui.theme.divider /** Combined control for zooming in and out */ @Composable @@ -39,7 +40,7 @@ fun ZoomButtons( shape = CircleShape, color = colors.backgroundColor(enabled).value, contentColor = colors.contentColor(enabled).value, - border = BorderStroke(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.12f)), + border = BorderStroke(1.dp, MaterialTheme.colors.divider), elevation = 4.dp ) { Column(Modifier diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/user/achievements/LazyAchievementsGrid.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/user/achievements/LazyAchievementsGrid.kt index 8428f0872f9..fd0d3472974 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/user/achievements/LazyAchievementsGrid.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/user/achievements/LazyAchievementsGrid.kt @@ -21,7 +21,7 @@ fun LazyAchievementsGrid( achievements: List>, onClickAchievement: (achievement: Achievement, level: Int) -> Unit, modifier: Modifier = Modifier, - contentPadding: PaddingValues = PaddingValues(0.dp) + contentPadding: PaddingValues = PaddingValues.Zero ) { LazyVerticalGrid( columns = GridCells.Adaptive(minSize = 144.dp), diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/user/edits/CountryStatisticsColumn.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/user/edits/CountryStatisticsColumn.kt index 5fa2eef59c8..a4f8de54770 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/user/edits/CountryStatisticsColumn.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/user/edits/CountryStatisticsColumn.kt @@ -29,7 +29,7 @@ fun CountryStatisticsColumn( flagAlignments: FlagAlignments, isCurrentWeek: Boolean, modifier: Modifier = Modifier, - contentPadding: PaddingValues = PaddingValues(0.dp), + contentPadding: PaddingValues = PaddingValues.Zero, ) { var showInfo by remember { mutableStateOf(null) } diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/user/edits/Flag.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/user/edits/Flag.kt index e48c20b12e5..9ac47f3cb95 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/user/edits/Flag.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/user/edits/Flag.kt @@ -16,6 +16,7 @@ import de.westnordost.streetcomplete.data.flags.FlagAlignment import de.westnordost.streetcomplete.resources.* import de.westnordost.streetcomplete.ui.ktx.innerBorder import de.westnordost.streetcomplete.ui.ktx.pxToDp +import de.westnordost.streetcomplete.ui.theme.divider import org.jetbrains.compose.resources.DrawableResource import org.jetbrains.compose.resources.painterResource @@ -27,7 +28,7 @@ fun Flag( modifier: Modifier = Modifier, ) { val resource = Res.getFlag(countryCode) ?: return - val color = MaterialTheme.colors.onSurface.copy(alpha = 0.12f) + val color = MaterialTheme.colors.divider Image( painter = painterResource(resource), contentDescription = countryCode, @@ -45,7 +46,7 @@ fun CircularFlag( ) { val resource = Res.getFlag(countryCode) ?: return val painter = painterResource(resource) - val color = MaterialTheme.colors.onSurface.copy(alpha = 0.12f) + val color = MaterialTheme.colors.divider Image( painter = painter, diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/user/links/LazyLinksColumn.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/user/links/LazyLinksColumn.kt index bd7bcdddab2..fb79786c46b 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/user/links/LazyLinksColumn.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/screens/user/links/LazyLinksColumn.kt @@ -19,7 +19,7 @@ import androidx.compose.ui.tooling.preview.Preview fun LazyGroupedLinksColumn( allLinks: List, modifier: Modifier = Modifier, - contentPadding: PaddingValues = PaddingValues(0.dp) + contentPadding: PaddingValues = PaddingValues.Zero ) { val groupedLinks = remember(allLinks) { allLinks.groupBy { it.category }.map { (k, v) -> k to v } @@ -46,7 +46,7 @@ fun LazyGroupedLinksColumn( fun LazyLinksColumn( links: List, modifier: Modifier = Modifier, - contentPadding: PaddingValues = PaddingValues(0.dp) + contentPadding: PaddingValues = PaddingValues.Zero ) { LazyLinksGrid(modifier, contentPadding = contentPadding) { items(links) { link -> @@ -58,7 +58,7 @@ fun LazyLinksColumn( @Composable private fun LazyLinksGrid( modifier: Modifier = Modifier, - contentPadding: PaddingValues = PaddingValues(0.dp), + contentPadding: PaddingValues = PaddingValues.Zero, content: LazyGridScope.() -> Unit ) { LazyVerticalGrid( diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/AutoFitFontSize.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/AutoFitFontSize.kt index 7c1bf9cf74e..4709cb960d2 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/AutoFitFontSize.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/AutoFitFontSize.kt @@ -16,7 +16,7 @@ fun AutoFitFontSize( value: String, modifier: Modifier = Modifier, maxLines: Int = 1, - contentPadding: PaddingValues = PaddingValues(0.dp), + contentPadding: PaddingValues = PaddingValues.Zero, content: @Composable () -> Unit, ) { val textStyle = LocalTextStyle.current diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/ButtonBar.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/ButtonBar.kt deleted file mode 100644 index b71b1a286b5..00000000000 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/ButtonBar.kt +++ /dev/null @@ -1,25 +0,0 @@ -package de.westnordost.streetcomplete.ui.common - -import androidx.compose.foundation.layout.IntrinsicSize -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier - -/** Horizontal button bar for bottom sheets. Add [HorizontalDivider] between buttons as needed. */ -@Composable -fun ButtonBar( - modifier: Modifier = Modifier, - content: @Composable RowScope.() -> Unit, -) { - Row( - modifier = modifier - .fillMaxWidth() - .height(IntrinsicSize.Min), - verticalAlignment = Alignment.CenterVertically, - content = content, - ) -} diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/FloatingActionButton.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/FloatingActionButton.kt new file mode 100644 index 00000000000..3d021f5bb7f --- /dev/null +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/FloatingActionButton.kt @@ -0,0 +1,85 @@ +package de.westnordost.streetcomplete.ui.common + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.material.ButtonColors +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.ButtonElevation +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.LocalContentAlpha +import androidx.compose.material.MaterialTheme +import androidx.compose.material.ProvideTextStyle +import androidx.compose.material.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.dp + +/** Same as the normal FloatingActionButton, but the FloatingActionButton from Material2 doesn't + * have an [enabled] parameter … */ +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun FloatingActionButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + interactionSource: MutableInteractionSource? = null, + elevation: ButtonElevation? = FloatingActionButtonDefaults.elevation(), + shape: Shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)), + border: BorderStroke? = null, + colors: ButtonColors = FloatingActionButtonDefaults.buttonColors(), + content: @Composable () -> Unit, +) { + @Suppress("NAME_SHADOWING") + val interactionSource = interactionSource ?: remember { MutableInteractionSource() } + val contentColor by colors.contentColor(enabled) + Surface( + onClick = onClick, + modifier = modifier.semantics { role = Role.Button }, + enabled = enabled, + shape = shape, + color = colors.backgroundColor(enabled).value, + contentColor = contentColor.copy(alpha = 1f), + border = border, + elevation = elevation?.elevation(enabled, interactionSource)?.value ?: 0.dp, + interactionSource = interactionSource, + ) { + CompositionLocalProvider(LocalContentAlpha provides contentColor.alpha) { + ProvideTextStyle(value = MaterialTheme.typography.button) { + Box( + modifier = Modifier.defaultMinSize(minWidth = FabSize, minHeight = FabSize), + contentAlignment = Alignment.Center + ) { + content() + } + } + } + } +} + +private val FabSize = 56.dp + +object FloatingActionButtonDefaults { + @Composable fun elevation() = ButtonDefaults.elevation( + defaultElevation = 6.dp, + pressedElevation = 12.dp, + disabledElevation = 6.dp, + hoveredElevation = 8.dp, + focusedElevation = 8.dp, + ) + + @Composable fun buttonColors() = ButtonDefaults.buttonColors( + backgroundColor = MaterialTheme.colors.secondary + ) +} + diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/FloatingOkButton.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/FloatingOkButton.kt index 22fa6d68c69..16c844a623c 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/FloatingOkButton.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/FloatingOkButton.kt @@ -6,14 +6,10 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.FloatingActionButton import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp import de.westnordost.streetcomplete.resources.* import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource @@ -21,9 +17,10 @@ import org.jetbrains.compose.resources.stringResource /** Floating OK (check) button with animated pop-in/pop-out*/ @Composable fun FloatingOkButton( - visible: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, + visible: Boolean = true, + enabled: Boolean = true, ) { AnimatedVisibility( visible = visible, @@ -33,14 +30,11 @@ fun FloatingOkButton( ) { FloatingActionButton( onClick = onClick, - shape = CircleShape, - backgroundColor = MaterialTheme.colors.secondary, - modifier = Modifier.size(72.dp), + enabled = enabled ) { Icon( - painter = painterResource(Res.drawable.ic_check_48), + painter = painterResource(Res.drawable.ic_check_32), contentDescription = stringResource(Res.string.ok), - tint = MaterialTheme.colors.onSecondary, ) } } diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/StepperButton.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/StepperButton.kt index fc9d8bdab54..79a2dae201c 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/StepperButton.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/StepperButton.kt @@ -56,7 +56,7 @@ fun StepperButton( onClick = onIncrease, style = style, enabled = increaseEnabled, - contentPadding = PaddingValues(0.dp), + contentPadding = PaddingValues.Zero, content = increaseContent ) Divider() @@ -65,7 +65,7 @@ fun StepperButton( onClick = onDecrease, style = style, enabled = decreaseEnabled, - contentPadding = PaddingValues(0.dp), + contentPadding = PaddingValues.Zero, content = decreaseContent ) } diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/VerticalDivider.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/VerticalDivider.kt index f7dbbf159b7..4b38c720c45 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/VerticalDivider.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/VerticalDivider.kt @@ -1,31 +1,33 @@ package de.westnordost.streetcomplete.ui.common -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.width import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import de.westnordost.streetcomplete.ui.theme.divider /** Same as a Divider, only vertical. (In Material3, this is already available.) */ @Composable fun VerticalDivider( modifier: Modifier = Modifier, - color: Color = MaterialTheme.colors.onSurface.copy(alpha = DividerAlpha), + color: Color = MaterialTheme.colors.divider, thickness: Dp = 1.dp ) { - val targetThickness = - if (thickness == Dp.Hairline) { - (1f / LocalDensity.current.density).dp - } else { - thickness - } - Box(modifier.fillMaxHeight().width(targetThickness).background(color = color)) + Canvas(modifier.fillMaxHeight().width(thickness)) { + val width = thickness.toPx() + val offset = width / 2f + drawLine( + color = color, + strokeWidth = width, + start = Offset(offset, 0f), + end = Offset(offset, size.height), + ) + } } -private const val DividerAlpha = 0.12f diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/bottom_sheet/BottomSheet.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/bottom_sheet/BottomSheet.kt new file mode 100644 index 00000000000..d350b9bce6c --- /dev/null +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/bottom_sheet/BottomSheet.kt @@ -0,0 +1,162 @@ +package de.westnordost.streetcomplete.ui.common.bottom_sheet + +import androidx.compose.foundation.gestures.AnchoredDraggableDefaults +import androidx.compose.foundation.gestures.AnchoredDraggableState +import androidx.compose.foundation.gestures.DraggableAnchors +import androidx.compose.foundation.gestures.FlingBehavior +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.ScrollScope +import androidx.compose.foundation.gestures.anchoredDraggable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.offset +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.Velocity +import androidx.compose.ui.unit.dp +import de.westnordost.streetcomplete.ui.common.bottom_sheet.BottomSheetState.Collapsed +import de.westnordost.streetcomplete.ui.common.bottom_sheet.BottomSheetState.Expanded +import de.westnordost.streetcomplete.ui.ktx.toPx +import kotlin.jvm.JvmName +import kotlin.math.max + +enum class BottomSheetState { Collapsed, Expanded } + +/** A simple bottom sheet, i.e. a Box that can be pulled up from below by dragging it up. Handles + * nested vertical scrolling properly. */ +@Composable +fun BottomSheet( + modifier: Modifier = Modifier, + initialState: BottomSheetState = Collapsed, + peekHeight: Dp = 64.dp, + content: @Composable () -> Unit, +) { + val state = rememberSaveable(saver = AnchoredDraggableState.Saver()) { + AnchoredDraggableState(initialState) + } + val flingBehavior = AnchoredDraggableDefaults.flingBehavior(state) + val nestedScrollConnection = remember(state, flingBehavior) { + ConsumeSwipeWithinBottomSheetBoundsNestedScrollConnection( + state = state, + flingBehavior = flingBehavior, + orientation = Orientation.Vertical + ) + } + val peekHeightPx = peekHeight.toPx() + + // outer box into which the sheet can slide into + BoxWithConstraints( + modifier = modifier.fillMaxSize(), + contentAlignment = Alignment.BottomCenter, + ) { + // inner box, i.e. the bottom sheet + Box( + modifier = Modifier + .fillMaxWidth() + .onSizeChanged { size -> + state.updateAnchors(DraggableAnchors { + Collapsed at max(size.height - peekHeightPx, 0f) + Expanded at 0f + }) + } + // while offset hasn't been initialized yet, it should not be visible, to avoid + // flickering + .alpha(if (state.offset.isNaN()) 0f else 1f) + .offset { IntOffset(0, state.offset.toInt()) } + // necessary because drag events are usually dispatched to nested scroll views + // first, we need to steal back control of it so we can first expand the sheet up + // via drag before allowing to scroll in the nested view + .nestedScroll(nestedScrollConnection) + .anchoredDraggable( + state = state, + orientation = Orientation.Vertical, + flingBehavior = flingBehavior + ), + content = { content() } + ) + } +} + +private fun ConsumeSwipeWithinBottomSheetBoundsNestedScrollConnection( + state: AnchoredDraggableState<*>, + flingBehavior: FlingBehavior, + orientation: Orientation, +): NestedScrollConnection = object : NestedScrollConnection { + + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + val delta = available.toFloat() + return if (delta < 0 && source == NestedScrollSource.UserInput) { + state.dispatchRawDelta(delta).toOffset() + } else { + Offset.Zero + } + } + + override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { + return if (source == NestedScrollSource.UserInput) { + state.dispatchRawDelta(available.toFloat()).toOffset() + } else { + Offset.Zero + } + } + + override suspend fun onPreFling(available: Velocity): Velocity { + val toFling = available.toFloat() + val currentOffset = state.requireOffset() + return if (toFling < 0 && currentOffset > state.anchors.minPosition()) { + // since we go to the anchor with tween settling, consume all for the best UX + state.anchoredDrag { + val scrollFlingScope = object : ScrollScope { + override fun scrollBy(pixels: Float): Float { + dragTo(state.offset + pixels) + return pixels + } + } + with(flingBehavior) { scrollFlingScope.performFling(toFling) } + } + available + } else { + Velocity.Zero + } + } + + override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { + // this API is crazy. Do I understand it? No. I got it from the example at + // https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt;l=416-432;drc=7440f70755e3735dbd8f04718d12dfeec7584dc8 + // pointed at from the comment of the now deprecated `state.settle(velocity)` API. + state.anchoredDrag { + val scrollFlingScope = object : ScrollScope { + override fun scrollBy(pixels: Float): Float { + dragTo(state.offset + pixels) + return pixels + } + } + with(flingBehavior) { scrollFlingScope.performFling(available.toFloat()) } + } + return available + } + + private fun Float.toOffset() = Offset( + x = if (orientation == Orientation.Horizontal) this else 0f, + y = if (orientation == Orientation.Vertical) this else 0f, + ) + + @JvmName("velocityToFloat") + private fun Velocity.toFloat() = if (orientation == Orientation.Horizontal) x else y + + @JvmName("offsetToFloat") + private fun Offset.toFloat(): Float = if (orientation == Orientation.Horizontal) x else y +} diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/overlay/OverlayForm.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/overlay/OverlayForm.kt new file mode 100644 index 00000000000..d9909bed078 --- /dev/null +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/overlay/OverlayForm.kt @@ -0,0 +1,179 @@ +package de.westnordost.streetcomplete.ui.common.overlay + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawingPadding +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.material.ContentAlpha +import androidx.compose.material.Divider +import androidx.compose.material.DropdownMenu +import androidx.compose.material.IconButton +import androidx.compose.material.LocalContentAlpha +import androidx.compose.material.LocalTextStyle +import androidx.compose.material.MaterialTheme +import androidx.compose.material.ProvideTextStyle +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalWindowInfo +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.datasource.LoremIpsum +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import de.westnordost.streetcomplete.ui.common.DropdownMenuItem +import de.westnordost.streetcomplete.ui.common.FloatingOkButton +import de.westnordost.streetcomplete.ui.common.MoreIcon +import de.westnordost.streetcomplete.ui.common.quest.Answer +import de.westnordost.streetcomplete.ui.common.quest.Answers +import de.westnordost.streetcomplete.ui.common.speech_bubble.SpeechBubbleNoArrow +import de.westnordost.streetcomplete.ui.theme.Dimensions +import de.westnordost.streetcomplete.ui.theme.titleMedium + +/** A generic overlay form containing the center-aligned [content], padded with [contentPadding]. + * Above it, an optional bubble with a [label] (in which the element is usually named). + * + * Below the content, there's an empty bar that contains only a "more" icon button on the start + * that, when tapped, opens a dropdown menu containing [otherAnswers]. + * + * Floating in the lower end corner, an OK button for confirmation. [okIsVisible] should be true + * when the form is complete, while [okIsEnabled] should be true when any changes have been made. + * */ +@Composable +fun OverlayForm( + okIsVisible: Boolean, + okIsEnabled: Boolean, + onClickOk: () -> Unit, + modifier: Modifier = Modifier, + label: AnnotatedString? = null, + otherAnswers: Answers = Answers(), + contentPadding: PaddingValues = PaddingValues(horizontal = 24.dp, vertical = 12.dp), + content: @Composable BoxScope.() -> Unit +) { + val windowInfo = LocalWindowInfo.current + + val elevation = 4.dp + + Box(modifier = modifier.sizeIn(maxWidth = Dimensions.getMaxQuestFormWidth(windowInfo))) { + Column( + modifier = Modifier + .fillMaxWidth() + .safeDrawingPadding(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + if (label != null) { + SpeechBubbleNoArrow( + modifier = Modifier.padding(horizontal = 8.dp), + elevation = elevation, + ) { + CompositionLocalProvider( + LocalTextStyle provides MaterialTheme.typography.titleMedium, + LocalContentAlpha provides ContentAlpha.medium + ) { + Text(label) + } + } + } + + OverlayContentBubble( + modifier = Modifier.fillMaxWidth(), + elevation = elevation, + otherAnswers = otherAnswers.answers, + contentPadding = contentPadding, + content = content + ) + } + FloatingOkButton( + visible = okIsVisible, + enabled = okIsEnabled, + onClick = onClickOk, + modifier = Modifier + .align(Alignment.BottomEnd) + .safeDrawingPadding() + .padding(8.dp) + ) + } +} + +/** Speech bubble for the overlay form content answer, i.e. content and more-button */ +@Composable +private fun OverlayContentBubble( + modifier: Modifier = Modifier, + elevation: Dp = 0.dp, + otherAnswers: List = emptyList(), + contentPadding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 12.dp), + content: @Composable BoxScope.() -> Unit +) { + SpeechBubbleNoArrow( + modifier = modifier, + elevation = elevation, + contentPadding = PaddingValues.Zero + ) { + ProvideTextStyle(MaterialTheme.typography.body1) { + Column { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(contentPadding), + contentAlignment = Alignment.Center, + content = content + ) + Divider() + MoreButton(answers = otherAnswers) + } + } + } +} + +/** …-button that opens a dropdown with the provided [answers] */ +@Composable +private fun MoreButton( + answers: List, + modifier: Modifier = Modifier, +) { + var expanded by rememberSaveable { mutableStateOf(false) } + + Box(modifier) { + IconButton(onClick = { expanded = true }) { + MoreIcon() + } + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + for (answer in answers) { + DropdownMenuItem(onClick = { expanded = false; answer.action() }) { + Text(answer.text) + } + } + } + } +} + +@Preview +@Composable +private fun OverlayFormPreview() { + OverlayForm( + okIsVisible = true, + okIsEnabled = false, + onClickOk = {}, + label = AnnotatedString("some text"), + otherAnswers = Answers( + Answer("Can't say") {}, + Answer("Can say") {}, + ) + ) { + Text(LoremIpsum(50).values.joinToString(" ")) + } +} diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/quest/QuestAnswerBubble.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/quest/QuestAnswerBubble.kt new file mode 100644 index 00000000000..06b776e476a --- /dev/null +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/quest/QuestAnswerBubble.kt @@ -0,0 +1,65 @@ +package de.westnordost.streetcomplete.ui.common.quest + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Divider +import androidx.compose.material.MaterialTheme +import androidx.compose.material.ProvideTextStyle +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import de.westnordost.streetcomplete.ui.common.speech_bubble.SpeechBubbleNoArrow +import de.westnordost.streetcomplete.ui.ktx.fadingVerticalScrollEdges + +/** Speech bubble for the quest answer, i.e. [content] and/or button bar answers */ +@Composable +fun QuestAnswerBubble( + modifier: Modifier = Modifier, + elevation: Dp = 0.dp, + answers: List = emptyList(), + otherAnswers: List = emptyList(), + contentPadding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 12.dp), + content: @Composable (BoxScope.() -> Unit)? = null +) { + val scrollState = rememberScrollState() + SpeechBubbleNoArrow( + modifier = modifier, + elevation = elevation, + contentPadding = PaddingValues.Zero + ) { + ProvideTextStyle(MaterialTheme.typography.body1) { + Column(Modifier + .fadingVerticalScrollEdges(scrollState, 32.dp) + .verticalScroll(scrollState), + ) { + if (content != null) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(contentPadding), + contentAlignment = Alignment.Center, + content = content + ) + } + if (content != null && (answers.isNotEmpty() || otherAnswers.isNotEmpty())) { + Divider() + } + if (answers.isNotEmpty() || otherAnswers.isNotEmpty()) { + QuestAnswerButtonBar( + modifier = Modifier.padding(horizontal = 8.dp), + answers = answers, + otherAnswers = otherAnswers + ) + } + } + } + } +} diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/quest/QuestAnswerButtonBar.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/quest/QuestAnswerButtonBar.kt new file mode 100644 index 00000000000..4d6e8e6627b --- /dev/null +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/quest/QuestAnswerButtonBar.kt @@ -0,0 +1,97 @@ +package de.westnordost.streetcomplete.ui.common.quest + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material.DropdownMenu +import androidx.compose.material.Text +import androidx.compose.material.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import de.westnordost.streetcomplete.resources.* +import de.westnordost.streetcomplete.ui.common.DropdownMenuItem +import de.westnordost.streetcomplete.ui.common.VerticalDivider +import org.jetbrains.compose.resources.stringResource + +@Immutable +data class Answer(val text: String, val action: () -> Unit) + +/** Horizontal button bar for bottom sheets that can be multi-line if it does not all fit in one + * line and places subtle dividers in-between the [answers]. Also, optionally [otherAnswers] will + * be shown in a dropdown button aligned to the start of the bar. */ +@Composable +fun QuestAnswerButtonBar( + modifier: Modifier = Modifier, + answers: List = emptyList(), + otherAnswers: List = emptyList(), +) { + FlowRow( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(4.dp, alignment = Alignment.End), + itemVerticalAlignment = Alignment.CenterVertically, + ) { + if (otherAnswers.isNotEmpty()) { + OtherAnswersTextButton(answers = otherAnswers) + Spacer(Modifier.weight(1f)) + } + for ((index, item) in answers.withIndex()) { + if (otherAnswers.isNotEmpty() || index != 0) { + VerticalDivider(Modifier.height(24.dp)) + } + TextButton(onClick = item.action) { Text(item.text) } + } + } +} + +@Composable +private fun OtherAnswersTextButton( + answers: List, + modifier: Modifier = Modifier, +) { + var expanded by rememberSaveable { mutableStateOf(false) } + + Box(modifier) { + TextButton(onClick = { expanded = true }) { + Text(stringResource(Res.string.quest_generic_otherAnswers2)) + } + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + for (answer in answers) { + DropdownMenuItem(onClick = { expanded = false; answer.action() }) { + Text(answer.text) + } + } + } + } +} + + +@Preview +@Composable +private fun QuestAnswerButtonBarPreview() { + QuestAnswerButtonBar( + answers = listOf( + Answer("No") {}, + Answer("Perhaps") {}, + Answer("Depends how you define \"No\"") {}, + Answer("Yes") {}, + ), + otherAnswers = listOf( + Answer("Depends how you define \"Yes\"") {}, + Answer("Can't say") {} + ) + ) +} diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/quest/QuestForm.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/quest/QuestForm.kt new file mode 100644 index 00000000000..f615ead8d94 --- /dev/null +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/quest/QuestForm.kt @@ -0,0 +1,209 @@ +package de.westnordost.streetcomplete.ui.common.quest + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawingPadding +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.ContentAlpha +import androidx.compose.material.LocalContentAlpha +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalWindowInfo +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.datasource.LoremIpsum +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import de.westnordost.streetcomplete.data.quest.QuestType +import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement +import de.westnordost.streetcomplete.resources.* +import de.westnordost.streetcomplete.ui.common.FloatingOkButton +import de.westnordost.streetcomplete.ui.common.bottom_sheet.BottomSheet +import de.westnordost.streetcomplete.ui.common.bottom_sheet.BottomSheetState +import de.westnordost.streetcomplete.ui.common.speech_bubble.SpeechBubble +import de.westnordost.streetcomplete.ui.common.speech_bubble.SpeechBubbleArrowDirection +import de.westnordost.streetcomplete.ui.common.speech_bubble.SpeechBubbleNoArrow +import de.westnordost.streetcomplete.ui.ktx.isLandscape +import de.westnordost.streetcomplete.ui.theme.Dimensions +import de.westnordost.streetcomplete.ui.theme.defaultTextLinkStyles +import de.westnordost.streetcomplete.ui.theme.titleSmall +import de.westnordost.streetcomplete.ui.util.annotateLinks +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.stringResource + +/** A quest form can **either** have an OK button for confirmation **or** a list of button answers + * at-a-time */ +sealed interface QuestAnswer + +data class Answers(val answers: List) : QuestAnswer { + // convenience constructors + constructor() : this(emptyList()) + constructor(vararg answers: Answer) : this(answers.toList()) +} +data class Confirm( + val isVisible: Boolean, + val onClick: () -> Unit, +) : QuestAnswer + + +/** A generic quest form, with a [title], [subtitle], [hintText] and [hintImages] in the + * header speech bubble, then an optional [note] by another mapper shown below as another speech + * bubble, then finally the speech bubble containing the center-aligned [content] padded with a + * [contentPadding] (if there is any content) and below *either* a row of text buttons showing + * different [answers] (defined from start to end) *or* an OK confirmation button. + * + * At the very start of the text button row, there's a text button labeled "Uh…" that, when tapped, + * opens a dropdown menu containing [otherAnswers] (defined from start to bottom). */ +@Composable +fun QuestForm( + questType: QuestType, + answers: QuestAnswer, + modifier: Modifier = Modifier, + title: String = stringResource(questType.title), + subtitle: AnnotatedString? = null, + hintText: String? = questType.hint?.let { stringResource(it) }, + hintImages: List = questType.hintImages, + note: String? = null, + otherAnswers: Answers = Answers(), + contentPadding: PaddingValues = PaddingValues(horizontal = 24.dp, vertical = 12.dp), + content: @Composable (BoxScope.() -> Unit)? = null +) { + val windowInfo = LocalWindowInfo.current + + val initialState = + if (LocalWindowInfo.current.isLandscape) BottomSheetState.Expanded + else BottomSheetState.Collapsed + + val elevation = 4.dp + + Box(modifier = modifier.sizeIn(maxWidth = Dimensions.getMaxQuestFormWidth(windowInfo))) { + BottomSheet( + initialState = initialState, + peekHeight = Dimensions.QuestFormPeekHeight + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .safeDrawingPadding(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + SpeechBubble( + elevation = elevation, + arrowDirection = SpeechBubbleArrowDirection.Top, + arrowPlacementBias = 0.1f, + modifier = Modifier.padding(horizontal = 8.dp) + ) { + QuestHeader( + title = title, + subtitle = subtitle, + hintText = hintText, + hintImages = hintImages, + modifier = Modifier.fillMaxWidth(), + ) + } + + if (note != null) { + NoteBubble( + text = note, + elevation = elevation, + modifier = Modifier + .padding(horizontal = 8.dp) + .fillMaxWidth() + ) + } + + QuestAnswerBubble( + modifier = Modifier.fillMaxWidth(), + elevation = elevation, + answers = (answers as? Answers)?.answers ?: emptyList(), + otherAnswers = otherAnswers.answers, + contentPadding = contentPadding, + content = content, + ) + } + } + if (answers is Confirm) { + FloatingOkButton( + visible = answers.isVisible, + onClick = answers.onClick, + modifier = Modifier + .align(Alignment.BottomEnd) + .safeDrawingPadding() + .padding(8.dp) + ) + } + } +} + +/** Speech bubble (without arrow) that contains a note another user left for this object */ +@Composable +private fun NoteBubble( + text: String, + modifier: Modifier = Modifier, + elevation: Dp = 0.dp, +) { + SpeechBubbleNoArrow( + modifier = modifier, + elevation = elevation + ) { + Column { + Text( + text = stringResource(Res.string.note_for_object), + style = MaterialTheme.typography.titleSmall + ) + CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) { + SelectionContainer { + Text( + text = text.annotateLinks(MaterialTheme.typography.defaultTextLinkStyles()), + style = MaterialTheme.typography.body2, + ) + } + } + } + } +} + + +@Preview +@Composable +private fun QuestFormPreview() { + QuestForm( + questType = object : QuestType { + override val icon = 0 + override val title = Res.string.quest_streetName_title + override val wikiLink = null + override val achievements = emptyList() + override val hint = Res.string.quest_streetName_hint + }, + subtitle = AnnotatedString("Tertiary Road"), + note = "unpaved", + answers = Answers( + Answer("No") {}, + Answer("Maybe") {}, + Answer("Yes") {}, + ), + otherAnswers = Answers( + Answer("Can't say") {}, + Answer("Can say") {}, + ) + ) { + Text(LoremIpsum(200).values.joinToString(" ")) + } +} diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/QuestHeader.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/quest/QuestHeader.kt similarity index 94% rename from app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/QuestHeader.kt rename to app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/quest/QuestHeader.kt index 54d9dcdd34f..097cc094bd3 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/QuestHeader.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/quest/QuestHeader.kt @@ -1,8 +1,7 @@ -package de.westnordost.streetcomplete.quests +package de.westnordost.streetcomplete.ui.common.quest import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -27,11 +26,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties import de.westnordost.streetcomplete.ui.common.InfoFilledIcon import de.westnordost.streetcomplete.ui.common.InfoOutlineIcon import de.westnordost.streetcomplete.ui.ktx.fadingHorizontalScrollEdges diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/speech_bubble/SpeechBubble.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/speech_bubble/SpeechBubble.kt new file mode 100644 index 00000000000..9f865436ffe --- /dev/null +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/speech_bubble/SpeechBubble.kt @@ -0,0 +1,56 @@ +package de.westnordost.streetcomplete.ui.common.speech_bubble + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.contentColorFor +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import de.westnordost.streetcomplete.ui.theme.Dimensions +import de.westnordost.streetcomplete.ui.theme.divider + +/** Surface in the shape of a speech bubble with a default border stroke and inner padding. */ +@Composable +fun SpeechBubble( + modifier: Modifier = Modifier, + cornerRadius: Dp = Dimensions.speechBubbleCornerRadius, + arrowSize: Dp = cornerRadius * 0.75f, + arrowDirection: SpeechBubbleArrowDirection = SpeechBubbleArrowDirection.Bottom, + arrowPlacementBias: Float = 0f, + color: Color = MaterialTheme.colors.surface, + contentColor: Color = contentColorFor(color), + border: BorderStroke? = BorderStroke(1.dp, MaterialTheme.colors.divider), + elevation: Dp = 0.dp, + contentPadding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 12.dp), + content: @Composable () -> Unit, +) { + val shape = SpeechBubbleShape( + cornerRadius = cornerRadius, + arrowSize = arrowSize, + arrowDirection = arrowDirection, + arrowPlacementBias = arrowPlacementBias, + ) + Surface( + modifier = modifier, + shape = shape, + color = color, + contentColor = contentColor, + border = border, + elevation = elevation, + ) { + Box(modifier = Modifier + .clip(shape) + .padding(shape.contentPadding) + .padding(contentPadding) + ) { + content() + } + } +} diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/speech_bubble/SpeechBubbleNoArrow.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/speech_bubble/SpeechBubbleNoArrow.kt new file mode 100644 index 00000000000..c0f8f62219f --- /dev/null +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/speech_bubble/SpeechBubbleNoArrow.kt @@ -0,0 +1,45 @@ +package de.westnordost.streetcomplete.ui.common.speech_bubble + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.contentColorFor +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import de.westnordost.streetcomplete.ui.theme.Dimensions +import de.westnordost.streetcomplete.ui.theme.divider + +/** A spech bubble without an arrow, so basically mostly a surface with rounded corners. However, + * there are some common defaults, so it makes sense to put it into an own composable. */ +@Composable +fun SpeechBubbleNoArrow( + modifier: Modifier = Modifier, + cornerRadius: Dp = Dimensions.speechBubbleCornerRadius, + color: Color = MaterialTheme.colors.surface, + contentColor: Color = contentColorFor(color), + elevation: Dp = 0.dp, + border: BorderStroke? = BorderStroke(1.dp, MaterialTheme.colors.divider), + contentPadding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 12.dp), + content: @Composable () -> Unit, +) { + Surface( + modifier = modifier, + shape = RoundedCornerShape(cornerRadius), + color = color, + contentColor = contentColor, + border = border, + elevation = elevation, + ) { + Box(modifier = Modifier.padding(contentPadding)) { + content() + } + } +} diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/SpeechBubbleShape.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/speech_bubble/SpeechBubbleShape.kt similarity index 98% rename from app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/SpeechBubbleShape.kt rename to app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/speech_bubble/SpeechBubbleShape.kt index b4b4bbd7c30..43bc4fcbeb8 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/SpeechBubbleShape.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/speech_bubble/SpeechBubbleShape.kt @@ -1,4 +1,4 @@ -package de.westnordost.streetcomplete.ui.common +package de.westnordost.streetcomplete.ui.common.speech_bubble import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -7,8 +7,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CornerSize -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Button import androidx.compose.material.Slider import androidx.compose.material.Text diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/street_side_select/MiniCompass.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/street_side_select/MiniCompass.kt index 5d2a9c358f4..d9aa93a641b 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/street_side_select/MiniCompass.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/street_side_select/MiniCompass.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.dp import de.westnordost.streetcomplete.resources.* +import de.westnordost.streetcomplete.ui.theme.divider import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource @@ -36,7 +37,7 @@ fun MiniCompass( painter = painterResource(Res.drawable.compass_needle_48), contentDescription = stringResource(Res.string.compass), modifier = Modifier - .border(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.12f), CircleShape) + .border(1.dp, MaterialTheme.colors.divider, CircleShape) .background(MaterialTheme.colors.surface, CircleShape) .padding(4.dp) .size(24.dp) diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/street_side_select/StreetSideForm.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/street_side_select/StreetSideForm.kt index 3aaa28945e3..c67896bc1a2 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/street_side_select/StreetSideForm.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/street_side_select/StreetSideForm.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.unit.dp import de.westnordost.streetcomplete.osm.Sides import de.westnordost.streetcomplete.resources.* import de.westnordost.streetcomplete.ui.common.last_picked.LastPickedChipsRow +import de.westnordost.streetcomplete.ui.theme.divider import org.jetbrains.compose.resources.painterResource /** Form to input the something for the left and right side of a street */ @@ -78,7 +79,7 @@ import org.jetbrains.compose.resources.painterResource .padding(8.dp) .align(Alignment.BottomStart) .padding(lastPickedContentPadding), - chipBorder = BorderStroke(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.12f)), + chipBorder = BorderStroke(1.dp, MaterialTheme.colors.divider), chipContentPadding = PaddingValues.Zero, ) { value -> StreetSideIllustration( diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/ktx/WindowInfo.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/ktx/WindowInfo.kt new file mode 100644 index 00000000000..78906512d80 --- /dev/null +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/ktx/WindowInfo.kt @@ -0,0 +1,6 @@ +package de.westnordost.streetcomplete.ui.ktx + +import androidx.compose.ui.platform.WindowInfo + +val WindowInfo.isLandscape: Boolean get() = + containerSize.width > containerSize.height diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/theme/Color.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/theme/Color.kt index f760c2864ae..cd11ae5ac4e 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/theme/Color.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/theme/Color.kt @@ -63,6 +63,9 @@ val DarkColors = darkColors( onSecondary = Color.White ) +val Colors.divider @ReadOnlyComposable @Composable get() = + onSurface.copy(alpha = 0.12f) + val Colors.surfaceContainer @ReadOnlyComposable @Composable get() = if (isLight) Color(0xffdddddd) else Color(0xff222222) diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/theme/Dimensions.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/theme/Dimensions.kt index 268c13f9a66..031093e1eb2 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/theme/Dimensions.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/theme/Dimensions.kt @@ -1,25 +1,39 @@ package de.westnordost.streetcomplete.ui.theme import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.material.Shapes +import androidx.compose.ui.platform.WindowInfo import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import de.westnordost.streetcomplete.ui.ktx.isLandscape -/** Padding on the map due to an open quest form */ -fun getOpenQuestFormMapPadding(totalWidth: Dp, totalHeight: Dp): PaddingValues { - val isLandscape = totalWidth > totalHeight - return PaddingValues.Absolute( - left = if (isLandscape) getMaxQuestFormWidth(totalWidth) else 0.dp, - top = 0.dp, - right = 0.dp, - bottom = if (isLandscape) 0.dp else 320.dp - ) -} +object Dimensions { + + val speechBubbleCornerRadius: Dp get() = 16.dp -fun getMaxQuestFormWidth(totalWidth: Dp): Dp = - if (totalWidth >= 820.dp) 480.dp - else if (totalWidth >= 600.dp) 360.dp - else 480.dp + val QuestFormPeekHeight = 400.dp -fun getQuestFormPeekHeight(isLandscape: Boolean): Dp = - if (isLandscape) 540.dp - else 400.dp + fun getMaxQuestFormWidth(windowInfo: WindowInfo): Dp = + if (windowInfo.isLandscape) { + // in landscape mode, the quest form is by default expanded, so it already takes up + // more/all vertical space. Especially on smaller screens, the width thus must be + // somewhat limited so that the map is still visible + if (windowInfo.containerDpSize.width > 820.dp) 480.dp + else 360.dp + } + else { + // in portrait mode, it may stretch very wide + 480.dp + } + + /** Padding on the map due to an open quest form */ + fun getOpenQuestFormMapPadding(windowInfo: WindowInfo): PaddingValues { + val isLandscape = windowInfo.isLandscape + return PaddingValues.Absolute( + left = if (isLandscape) getMaxQuestFormWidth(windowInfo) else 0.dp, + top = 0.dp, + right = 0.dp, + bottom = if (isLandscape) 0.dp else 320.dp + ) + } +} diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/theme/Shapes.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/theme/Shapes.kt index 1fce497268f..be416b95698 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/theme/Shapes.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/theme/Shapes.kt @@ -2,10 +2,11 @@ package de.westnordost.streetcomplete.ui.theme import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Shapes +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp val Shapes = Shapes( // theme is more speech-bubbly than default - medium = RoundedCornerShape(10.dp), - large = RoundedCornerShape(10.dp) + medium = RoundedCornerShape(12.dp), + large = RoundedCornerShape(12.dp) ) diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/theme/Typography.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/theme/Typography.kt index fd0d88f8149..ee1d1549c55 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/theme/Typography.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/theme/Typography.kt @@ -1,8 +1,13 @@ package de.westnordost.streetcomplete.ui.theme +import androidx.compose.material.MaterialTheme import androidx.compose.material.Typography +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextLinkStyles import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration private val material2 = Typography() @@ -30,3 +35,14 @@ val Typography.headlineSmall get() = h5 val Typography.titleLarge get() = h6 val Typography.titleMedium get() = subtitle1 val Typography.titleSmall get() = subtitle2 + +@Composable +fun Typography.defaultTextLinkStyles() = TextLinkStyles( + style = SpanStyle( + color = MaterialTheme.colors.primary, + textDecoration = TextDecoration.Underline + ), + focusedStyle = SpanStyle( + color = MaterialTheme.colors.secondary, + ) +) diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/util/Html.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/util/Html.kt index 7fb43ae3f60..ce5c392a771 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/util/Html.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/util/Html.kt @@ -21,21 +21,14 @@ import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnitType import androidx.compose.ui.unit.sp import de.westnordost.streetcomplete.ui.ktx.pxToSp +import de.westnordost.streetcomplete.ui.theme.defaultTextLinkStyles import de.westnordost.streetcomplete.util.html.HtmlElementNode import de.westnordost.streetcomplete.util.html.HtmlNode import de.westnordost.streetcomplete.util.html.HtmlTextNode @Composable fun List.toAnnotatedString( - textLinkStyles: TextLinkStyles = TextLinkStyles( - style = SpanStyle( - color = MaterialTheme.colors.primary, - textDecoration = TextDecoration.Underline - ), - focusedStyle = SpanStyle( - color = MaterialTheme.colors.secondary, - ) - ) + textLinkStyles: TextLinkStyles = MaterialTheme.typography.defaultTextLinkStyles() ): AnnotatedString { val textStyle = LocalTextStyle.current val textMeasurer = rememberTextMeasurer()