Send pin-drop location (#636)

Share pindrop location

This feature allows the user to share any location by just selecting it from the map.

Closes: https://github.com/vector-im/element-x-android/issues/690
This commit is contained in:
Marco Romano 2023-06-30 00:07:47 +02:00 committed by GitHub
parent b292c3a29b
commit 0858cfb272
30 changed files with 534 additions and 113 deletions

View file

@ -29,6 +29,7 @@ import com.bumble.appyx.navmodel.backstack.operation.push
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.location.api.SendLocationEntryPoint
import io.element.android.features.messages.api.MessagesEntryPoint
import io.element.android.features.messages.impl.attachments.Attachment
import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewNode
@ -57,6 +58,7 @@ import kotlinx.parcelize.Parcelize
class MessagesFlowNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val sendLocationEntryPoint: SendLocationEntryPoint,
) : BackstackNode<MessagesFlowNode.NavTarget>(
backstack = BackStack(
initialElement = NavTarget.Messages,
@ -88,6 +90,9 @@ class MessagesFlowNode @AssistedInject constructor(
@Parcelize
data class ReportMessage(val eventId: EventId, val senderId: UserId) : NavTarget
@Parcelize
object SendLocation : NavTarget
}
private val callback = plugins<MessagesEntryPoint.Callback>().firstOrNull()
@ -123,6 +128,10 @@ class MessagesFlowNode @AssistedInject constructor(
override fun onReportMessage(eventId: EventId, senderId: UserId) {
backstack.push(NavTarget.ReportMessage(eventId, senderId))
}
override fun onSendLocationClicked() {
backstack.push(NavTarget.SendLocation)
}
}
createNode<MessagesNode>(buildContext, listOf(callback))
}
@ -155,6 +164,9 @@ class MessagesFlowNode @AssistedInject constructor(
val inputs = ReportMessageNode.Inputs(navTarget.eventId, navTarget.senderId)
createNode<ReportMessageNode>(buildContext, listOf(inputs))
}
NavTarget.SendLocation -> {
sendLocationEntryPoint.createNode(this, buildContext)
}
}
}

View file

@ -57,6 +57,7 @@ class MessagesNode @AssistedInject constructor(
fun onShowEventDebugInfoClicked(eventId: EventId, debugInfo: TimelineItemDebugInfo)
fun onForwardEventClicked(eventId: EventId)
fun onReportMessage(eventId: EventId, senderId: UserId)
fun onSendLocationClicked()
}
init {
@ -93,6 +94,10 @@ class MessagesNode @AssistedInject constructor(
override fun onReportContentClicked(eventId: EventId, senderId: UserId) {
callback?.onReportMessage(eventId, senderId)
}
private fun onSendLocationClicked() {
callback?.onSendLocationClicked()
}
@Composable
override fun View(modifier: Modifier) {
@ -104,6 +109,7 @@ class MessagesNode @AssistedInject constructor(
onEventClicked = this::onEventClicked,
onPreviewAttachments = this::onPreviewAttachments,
onUserDataClicked = this::onUserDataClicked,
onSendLocationClicked = this::onSendLocationClicked,
modifier = modifier,
)
}

View file

@ -92,6 +92,7 @@ fun MessagesView(
onEventClicked: (event: TimelineItem.Event) -> Unit,
onUserDataClicked: (UserId) -> Unit,
onPreviewAttachments: (ImmutableList<Attachment>) -> Unit,
onSendLocationClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
LogCompositions(tag = "MessagesScreen", msg = "Root")
@ -153,7 +154,8 @@ fun MessagesView(
state.retrySendMenuState.eventSink(RetrySendMenuEvents.EventSelected(event))
}
},
onReactionClicked = ::onEmojiReactionClicked
onReactionClicked = ::onEmojiReactionClicked,
onSendLocationClicked = onSendLocationClicked,
)
},
snackbarHost = {
@ -237,6 +239,7 @@ fun MessagesViewContent(
onReactionClicked: (key: String, TimelineItem.Event) -> Unit,
onMessageLongClicked: (TimelineItem.Event) -> Unit,
onTimestampClicked: (TimelineItem.Event) -> Unit,
onSendLocationClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
@ -260,6 +263,7 @@ fun MessagesViewContent(
if (state.userHasPermissionToSendMessage) {
MessageComposerView(
state = state.composerState,
onSendLocationClicked = onSendLocationClicked,
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(Alignment.Bottom)
@ -347,5 +351,6 @@ private fun ContentToPreview(state: MessagesState) {
onEventClicked = {},
onPreviewAttachments = {},
onUserDataClicked = {},
onSendLocationClicked = {},
)
}

View file

@ -25,6 +25,7 @@ import androidx.compose.material.ListItem
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AttachFile
import androidx.compose.material.icons.filled.Collections
import androidx.compose.material.icons.filled.LocationOn
import androidx.compose.material.icons.filled.PhotoCamera
import androidx.compose.material.icons.filled.Videocam
import androidx.compose.material3.ExperimentalMaterial3Api
@ -48,6 +49,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
@Composable
internal fun AttachmentsBottomSheet(
state: MessageComposerState,
onSendLocationClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
val localView = LocalView.current
@ -78,7 +80,10 @@ internal fun AttachmentsBottomSheet(
modifier = modifier,
onDismissRequest = { isVisible = false }
) {
AttachmentSourcePickerMenu(eventSink = state.eventSink)
AttachmentSourcePickerMenu(
eventSink = state.eventSink,
onSendLocationClicked = onSendLocationClicked,
)
}
}
}
@ -87,6 +92,7 @@ internal fun AttachmentsBottomSheet(
@Composable
internal fun AttachmentSourcePickerMenu(
eventSink: (MessageComposerEvents) -> Unit,
onSendLocationClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
@ -113,5 +119,13 @@ internal fun AttachmentSourcePickerMenu(
icon = { Icon(Icons.Default.Videocam, null) },
text = { Text(stringResource(R.string.screen_room_attachment_source_camera_video)) },
)
ListItem(
modifier = Modifier.clickable {
eventSink(MessageComposerEvents.PickAttachmentSource.Location)
onSendLocationClicked()
},
icon = { Icon(Icons.Default.LocationOn, null) },
text = { Text(stringResource(R.string.screen_room_attachment_source_location)) },
)
}
}

View file

@ -34,5 +34,6 @@ sealed interface MessageComposerEvents {
object FromFiles : PickAttachmentSource
object PhotoFromCamera : PickAttachmentSource
object VideoFromCamera : PickAttachmentSource
object Location : PickAttachmentSource
}
}

View file

@ -159,6 +159,10 @@ class MessageComposerPresenter @Inject constructor(
showAttachmentSourcePicker = false
cameraVideoPicker.launch()
}
MessageComposerEvents.PickAttachmentSource.Location -> {
showAttachmentSourcePicker = false
// Navigation to the location picker screen is done at the view layer
}
}
}

View file

@ -28,6 +28,7 @@ import io.element.android.libraries.textcomposer.TextComposer
@Composable
fun MessageComposerView(
state: MessageComposerState,
onSendLocationClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
fun onFullscreenToggle() {
@ -55,7 +56,10 @@ fun MessageComposerView(
}
Box {
AttachmentsBottomSheet(state = state)
AttachmentsBottomSheet(
state = state,
onSendLocationClicked = onSendLocationClicked,
)
TextComposer(
onSendMessage = ::sendMessage,
@ -83,5 +87,8 @@ internal fun MessageComposerViewDarkPreview(@PreviewParameter(MessageComposerSta
@Composable
private fun ContentToPreview(state: MessageComposerState) {
MessageComposerView(state)
MessageComposerView(
state = state,
onSendLocationClicked = {}
)
}