mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-03-15 02:38:59 +03:00
Show location preview and allow to share with external apps.
This commit is contained in:
parent
6495bd9e5e
commit
a0afab45fb
26 changed files with 601 additions and 100 deletions
|
@ -36,6 +36,7 @@ import com.airbnb.epoxy.EpoxyController
|
|||
import com.airbnb.mvrx.Mavericks
|
||||
import com.facebook.stetho.Stetho
|
||||
import com.gabrielittner.threetenbp.LazyThreeTen
|
||||
import com.mapbox.mapboxsdk.Mapbox
|
||||
import com.vanniktech.emoji.EmojiManager
|
||||
import com.vanniktech.emoji.google.GoogleEmojiProvider
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
|
@ -195,6 +196,9 @@ class VectorApplication :
|
|||
})
|
||||
|
||||
EmojiManager.install(GoogleEmojiProvider())
|
||||
|
||||
// Initialize Mapbox before inflating mapViews
|
||||
Mapbox.getInstance(this)
|
||||
}
|
||||
|
||||
private val startSyncOnFirstStart = object : DefaultLifecycleObserver {
|
||||
|
|
|
@ -61,6 +61,7 @@ import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
|
|||
import im.vector.app.features.home.room.detail.RoomDetailFragment
|
||||
import im.vector.app.features.home.room.detail.search.SearchFragment
|
||||
import im.vector.app.features.home.room.list.RoomListFragment
|
||||
import im.vector.app.features.location.LocationPreviewFragment
|
||||
import im.vector.app.features.location.LocationSharingFragment
|
||||
import im.vector.app.features.login.LoginCaptchaFragment
|
||||
import im.vector.app.features.login.LoginFragment
|
||||
|
@ -861,4 +862,9 @@ interface FragmentModule {
|
|||
@IntoMap
|
||||
@FragmentKey(LocationSharingFragment::class)
|
||||
fun bindLocationSharingFragment(fragment: LocationSharingFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(LocationPreviewFragment::class)
|
||||
fun bindLocationPreviewFragment(fragment: LocationPreviewFragment): Fragment
|
||||
}
|
||||
|
|
|
@ -297,6 +297,26 @@ fun openMedia(activity: Activity, savedMediaPath: String, mimeType: String) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open external location
|
||||
* @param activity the activity
|
||||
* @param latitude latitude of the location
|
||||
* @param longitude longitude of the location
|
||||
*/
|
||||
fun openLocation(activity: Activity, latitude: Double, longitude: Double) {
|
||||
val locationUri = buildString {
|
||||
append("geo:")
|
||||
append(latitude)
|
||||
append(",")
|
||||
append(longitude)
|
||||
append("?q=") // This is required to drop a pin to the location
|
||||
append(latitude)
|
||||
append(",")
|
||||
append(longitude)
|
||||
}
|
||||
openUri(activity, locationUri)
|
||||
}
|
||||
|
||||
fun shareMedia(context: Context, file: File, mediaMimeType: String?) {
|
||||
val mediaUri = try {
|
||||
FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", file)
|
||||
|
|
|
@ -20,6 +20,7 @@ import android.net.Uri
|
|||
import android.view.View
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
import im.vector.app.features.call.conference.ConferenceEvent
|
||||
import im.vector.app.features.location.LocationData
|
||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
|
||||
|
@ -110,4 +111,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
|||
|
||||
// Poll
|
||||
data class EndPoll(val eventId: String) : RoomDetailAction()
|
||||
|
||||
// Location
|
||||
data class ShowLocation(val locationData: LocationData, val userId: String) : RoomDetailAction()
|
||||
}
|
||||
|
|
|
@ -465,6 +465,7 @@ class RoomDetailFragment @Inject constructor(
|
|||
RoomDetailViewEvents.StopChatEffects -> handleStopChatEffects()
|
||||
is RoomDetailViewEvents.DisplayAndAcceptCall -> acceptIncomingCall(it)
|
||||
RoomDetailViewEvents.RoomReplacementStarted -> handleRoomReplacement()
|
||||
is RoomDetailViewEvents.ShowLocation -> handleShowLocationPreview(it)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
|
@ -596,6 +597,17 @@ class RoomDetailFragment @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleShowLocationPreview(viewEvent: RoomDetailViewEvents.ShowLocation) {
|
||||
navigator
|
||||
.openLocationSharing(
|
||||
context = requireContext(),
|
||||
roomId = roomDetailArgs.roomId,
|
||||
mode = LocationSharingMode.PREVIEW,
|
||||
initialLocationData = viewEvent.locationData,
|
||||
locationOwnerId = viewEvent.userId
|
||||
)
|
||||
}
|
||||
|
||||
private fun requestNativeWidgetPermission(it: RoomDetailViewEvents.RequestNativeWidgetPermission) {
|
||||
val tag = RoomWidgetPermissionBottomSheet::class.java.name
|
||||
val dFrag = childFragmentManager.findFragmentByTag(tag) as? RoomWidgetPermissionBottomSheet
|
||||
|
@ -2221,7 +2233,14 @@ class RoomDetailFragment @Inject constructor(
|
|||
AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment)
|
||||
AttachmentTypeSelectorView.Type.POLL -> navigator.openCreatePoll(requireContext(), roomDetailArgs.roomId)
|
||||
AttachmentTypeSelectorView.Type.LOCATION -> {
|
||||
navigator.openLocationSharing(requireContext(), roomDetailArgs.roomId, LocationSharingMode.STATIC_SHARING)
|
||||
navigator
|
||||
.openLocationSharing(
|
||||
context = requireContext(),
|
||||
roomId = roomDetailArgs.roomId,
|
||||
mode = LocationSharingMode.STATIC_SHARING,
|
||||
initialLocationData = null,
|
||||
locationOwnerId = session.myUserId
|
||||
)
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import android.net.Uri
|
|||
import android.view.View
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
import im.vector.app.features.call.webrtc.WebRtcCall
|
||||
import im.vector.app.features.location.LocationData
|
||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
|
||||
|
@ -81,4 +82,6 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
|
|||
data class StartChatEffect(val type: ChatEffect) : RoomDetailViewEvents()
|
||||
object StopChatEffects : RoomDetailViewEvents()
|
||||
object RoomReplacementStarted : RoomDetailViewEvents()
|
||||
|
||||
data class ShowLocation(val locationData: LocationData, val userId: String) : RoomDetailViewEvents()
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandle
|
|||
import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory
|
||||
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
|
||||
import im.vector.app.features.home.room.typing.TypingHelper
|
||||
import im.vector.app.features.location.LocationData
|
||||
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
|
||||
import im.vector.app.features.session.coroutineScope
|
||||
import im.vector.app.features.settings.VectorDataStore
|
||||
|
@ -330,9 +331,14 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
_viewEvents.post(RoomDetailViewEvents.OpenRoom(action.replacementRoomId, closeCurrentRoom = true))
|
||||
}
|
||||
is RoomDetailAction.EndPoll -> handleEndPoll(action.eventId)
|
||||
is RoomDetailAction.ShowLocation -> handleShowLocation(action.locationData, action.userId)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun handleShowLocation(locationData: LocationData, userId: String) {
|
||||
_viewEvents.post(RoomDetailViewEvents.ShowLocation(locationData, userId))
|
||||
}
|
||||
|
||||
private fun handleJitsiCallJoinStatus(action: RoomDetailAction.UpdateJoinJitsiCallStatus) = withState { state ->
|
||||
if (state.jitsiState.confId == null) {
|
||||
// If jitsi widget is removed while on the call
|
||||
|
|
|
@ -37,6 +37,7 @@ import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
|||
import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
||||
|
@ -49,6 +50,8 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem_
|
|||
import im.vector.app.features.home.room.detail.timeline.item.MessageImageVideoItem
|
||||
import im.vector.app.features.home.room.detail.timeline.item.MessageImageVideoItem_
|
||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.MessageLocationItem
|
||||
import im.vector.app.features.home.room.detail.timeline.item.MessageLocationItem_
|
||||
import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem
|
||||
import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem_
|
||||
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem
|
||||
|
@ -67,6 +70,7 @@ import im.vector.app.features.html.EventHtmlRenderer
|
|||
import im.vector.app.features.html.PillsPostProcessor
|
||||
import im.vector.app.features.html.SpanUtils
|
||||
import im.vector.app.features.html.VectorHtmlCompressor
|
||||
import im.vector.app.features.location.LocationData
|
||||
import im.vector.app.features.media.ImageContentRenderer
|
||||
import im.vector.app.features.media.VideoContentRenderer
|
||||
import me.gujun.android.span.span
|
||||
|
@ -82,6 +86,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithF
|
|||
import org.matrix.android.sdk.api.session.room.model.message.MessageEmoteContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageNoticeContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||
|
@ -116,7 +121,8 @@ class MessageItemFactory @Inject constructor(
|
|||
private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
|
||||
private val spanUtils: SpanUtils,
|
||||
private val session: Session,
|
||||
private val voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker) {
|
||||
private val voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker,
|
||||
private val locationPinProvider: LocationPinProvider) {
|
||||
|
||||
// TODO inject this properly?
|
||||
private var roomId: String = ""
|
||||
|
@ -168,16 +174,36 @@ class MessageItemFactory @Inject constructor(
|
|||
}
|
||||
}
|
||||
is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessagePollContent -> buildPollContent(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, callback, attributes)
|
||||
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildPollContent(pollContent: MessagePollContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): PollItem? {
|
||||
private fun buildLocationItem(locationContent: MessageLocationContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): MessageLocationItem? {
|
||||
|
||||
val geoUri = locationContent.locationInfo?.geoUri ?: locationContent.geoUri
|
||||
val locationData = LocationData.create(geoUri)
|
||||
|
||||
return MessageLocationItem_()
|
||||
.attributes(attributes)
|
||||
.locationData(locationData)
|
||||
.userId(informationData.senderId)
|
||||
.locationPinProvider(locationPinProvider)
|
||||
.highlighted(highlight)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.callback(callback)
|
||||
}
|
||||
|
||||
private fun buildPollItem(pollContent: MessagePollContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): PollItem? {
|
||||
val optionViewStates = mutableListOf<PollOptionViewState>()
|
||||
|
||||
val pollResponseSummary = informationData.pollResponseAggregatedSummary
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.detail.timeline.helper
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.glide.GlideApp
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import org.billcarsonfr.jsonviewer.Utils
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class LocationPinProvider @Inject constructor(
|
||||
private val context: Context,
|
||||
private val session: Session,
|
||||
private val avatarRenderer: AvatarRenderer
|
||||
) {
|
||||
private val cache = mutableMapOf<String, Drawable>()
|
||||
|
||||
private val glideRequests by lazy {
|
||||
GlideApp.with(context)
|
||||
}
|
||||
|
||||
fun create(userId: String, callback: (Drawable) -> Unit) {
|
||||
if (cache.contains(userId)) {
|
||||
callback(cache[userId]!!)
|
||||
return
|
||||
}
|
||||
|
||||
session.getUser(userId)?.toMatrixItem()?.let {
|
||||
val size = Utils.dpToPx(44, context)
|
||||
avatarRenderer.render(glideRequests, it, object : CustomTarget<Drawable>(size, size) {
|
||||
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
||||
val bgUserPin = ContextCompat.getDrawable(context, R.drawable.bg_map_user_pin)!!
|
||||
val layerDrawable = LayerDrawable(arrayOf(bgUserPin, resource))
|
||||
val horizontalInset = Utils.dpToPx(4, context)
|
||||
val topInset = Utils.dpToPx(4, context)
|
||||
val bottomInset = Utils.dpToPx(8, context)
|
||||
layerDrawable.setLayerInset(1, horizontalInset, topInset, horizontalInset, bottomInset)
|
||||
|
||||
cache[userId] = layerDrawable
|
||||
|
||||
callback(layerDrawable)
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {
|
||||
// Is it possible? Put placeholder instead?
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.detail.timeline.item
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.onClick
|
||||
import im.vector.app.features.home.room.detail.RoomDetailAction
|
||||
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
|
||||
import im.vector.app.features.location.LocationData
|
||||
import im.vector.app.features.location.MapTilerMapView
|
||||
import im.vector.app.features.location.VectorMapListener
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
|
||||
abstract class MessageLocationItem : AbsMessageItem<MessageLocationItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
var callback: TimelineEventController.Callback? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var locationData: LocationData? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var userId: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var locationPinProvider: LocationPinProvider? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
renderSendState(holder.mapViewContainer, null)
|
||||
|
||||
val location = locationData ?: return
|
||||
val locationOwnerId = userId ?: return
|
||||
|
||||
holder.mapView.initialize(object : VectorMapListener {
|
||||
override fun onMapReady() {
|
||||
holder.mapView.zoomToLocation(location.latitude, location.longitude, INITIAL_ZOOM)
|
||||
|
||||
locationPinProvider?.create(locationOwnerId) { pinDrawable ->
|
||||
holder.mapView.addPinToMap(locationOwnerId, pinDrawable)
|
||||
holder.mapView.updatePinLocation(locationOwnerId, location.latitude, location.longitude)
|
||||
}
|
||||
|
||||
holder.mapView.onClick {
|
||||
callback?.onTimelineItemAction(RoomDetailAction.ShowLocation(location, locationOwnerId))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun getViewType() = STUB_ID
|
||||
|
||||
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
||||
val mapViewContainer by bind<ConstraintLayout>(R.id.mapViewContainer)
|
||||
val mapView by bind<MapTilerMapView>(R.id.mapView)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val STUB_ID = R.id.messageContentLocationStub
|
||||
private const val INITIAL_ZOOM = 15.0
|
||||
}
|
||||
}
|
|
@ -16,9 +16,56 @@
|
|||
|
||||
package im.vector.app.features.location
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class LocationData(
|
||||
val latitude: Double,
|
||||
val longitude: Double,
|
||||
val uncertainty: Double?
|
||||
)
|
||||
) : Parcelable {
|
||||
|
||||
fun toGeoUri(): String {
|
||||
return buildString {
|
||||
append("geo:")
|
||||
append(latitude)
|
||||
append(",")
|
||||
append(longitude)
|
||||
append("?q=")
|
||||
append(latitude)
|
||||
append(",")
|
||||
append(longitude)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Creates location data from geo uri
|
||||
* @param geoUri geo:latitude,longitude;uncertainty
|
||||
* @return location data or null if geo uri is not valid
|
||||
*/
|
||||
fun create(geoUri: String): LocationData? {
|
||||
val geoParts = geoUri
|
||||
.split(":")
|
||||
.takeIf { it.firstOrNull() == "geo" }
|
||||
?.getOrNull(1)
|
||||
?.split(",")
|
||||
|
||||
val latitude = geoParts?.firstOrNull()
|
||||
val geoTailParts = geoParts?.getOrNull(1)?.split(";")
|
||||
val longitude = geoTailParts?.firstOrNull()
|
||||
val uncertainty = geoTailParts?.getOrNull(1)
|
||||
|
||||
return if (latitude != null && longitude != null) {
|
||||
LocationData(
|
||||
latitude = latitude.toDouble(),
|
||||
longitude = longitude.toDouble(),
|
||||
uncertainty = uncertainty?.toDouble()
|
||||
)
|
||||
} else null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.location
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentLocationPreviewBinding
|
||||
import javax.inject.Inject
|
||||
import com.airbnb.mvrx.args
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.utils.openLocation
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
|
||||
class LocationPreviewFragment @Inject constructor(
|
||||
private val locationPinProvider: LocationPinProvider
|
||||
) : VectorBaseFragment<FragmentLocationPreviewBinding>(), VectorMapListener {
|
||||
|
||||
private val args: LocationSharingArgs by args()
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocationPreviewBinding {
|
||||
return FragmentLocationPreviewBinding.inflate(layoutInflater, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
views.mapView.initialize(this)
|
||||
}
|
||||
|
||||
override fun getMenuRes() = R.menu.menu_location_preview
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.share_external -> {
|
||||
onShareLocationExternal()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun onShareLocationExternal() {
|
||||
val location = args.initialLocationData ?: return
|
||||
openLocation(requireActivity(), location.latitude, location.longitude)
|
||||
}
|
||||
|
||||
override fun onMapReady() {
|
||||
val location = args.initialLocationData ?: return
|
||||
val userId = args.locationOwnerId
|
||||
|
||||
locationPinProvider.create(userId) { pinDrawable ->
|
||||
views.mapView.apply {
|
||||
zoomToLocation(location.latitude, location.longitude, INITIAL_ZOOM)
|
||||
deleteAllPins()
|
||||
addPinToMap(userId, pinDrawable)
|
||||
updatePinLocation(userId, location.latitude, location.longitude)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val INITIAL_ZOOM = 15.0
|
||||
}
|
||||
}
|
|
@ -30,7 +30,9 @@ import kotlinx.parcelize.Parcelize
|
|||
@Parcelize
|
||||
data class LocationSharingArgs(
|
||||
val roomId: String,
|
||||
val mode: LocationSharingMode
|
||||
val mode: LocationSharingMode,
|
||||
val initialLocationData: LocationData?,
|
||||
val locationOwnerId: String
|
||||
) : Parcelable
|
||||
|
||||
@AndroidEntryPoint
|
||||
|
@ -62,6 +64,11 @@ class LocationSharingActivity : VectorBaseActivity<ActivityLocationSharingBindin
|
|||
)
|
||||
}
|
||||
LocationSharingMode.PREVIEW -> {
|
||||
addFragment(
|
||||
views.fragmentContainer,
|
||||
LocationPreviewFragment::class.java,
|
||||
locationSharingArgs
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,41 +16,24 @@
|
|||
|
||||
package im.vector.app.features.location
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.mapbox.mapboxsdk.Mapbox
|
||||
import com.mapbox.mapboxsdk.camera.CameraPosition
|
||||
import com.mapbox.mapboxsdk.geometry.LatLng
|
||||
import com.mapbox.mapboxsdk.maps.MapboxMap
|
||||
import com.mapbox.mapboxsdk.maps.Style
|
||||
import com.mapbox.mapboxsdk.plugins.annotation.SymbolManager
|
||||
import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions
|
||||
import com.mapbox.mapboxsdk.style.layers.Property
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.glide.GlideApp
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentLocationSharingBinding
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import org.billcarsonfr.jsonviewer.Utils
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class LocationSharingFragment @Inject constructor(
|
||||
private val locationTracker: LocationTracker,
|
||||
private val session: Session,
|
||||
private val avatarRenderer: AvatarRenderer
|
||||
) : VectorBaseFragment<FragmentLocationSharingBinding>(), LocationTracker.Callback {
|
||||
private val locationPinProvider: LocationPinProvider
|
||||
) : VectorBaseFragment<FragmentLocationSharingBinding>(), LocationTracker.Callback, VectorMapListener {
|
||||
|
||||
init {
|
||||
locationTracker.callback = this
|
||||
|
@ -58,28 +41,16 @@ class LocationSharingFragment @Inject constructor(
|
|||
|
||||
private val viewModel: LocationSharingViewModel by activityViewModel()
|
||||
|
||||
private val glideRequests by lazy {
|
||||
GlideApp.with(this)
|
||||
}
|
||||
|
||||
private var map: MapboxMap? = null
|
||||
private var symbolManager: SymbolManager? = null
|
||||
private var lastZoomValue: Double = -1.0
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocationSharingBinding {
|
||||
return FragmentLocationSharingBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
// Initialize Mapbox before inflating mapView
|
||||
Mapbox.getInstance(requireContext())
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
initMapView(savedInstanceState)
|
||||
views.mapView.initialize(this)
|
||||
|
||||
views.shareLocationContainer.debouncedClicks {
|
||||
viewModel.handle(LocationSharingAction.OnShareLocation)
|
||||
|
@ -98,64 +69,23 @@ class LocationSharingFragment @Inject constructor(
|
|||
locationTracker.stop()
|
||||
}
|
||||
|
||||
private fun initMapView(savedInstanceState: Bundle?) {
|
||||
val key = BuildConfig.mapTilerKey
|
||||
val styleUrl = "https://api.maptiler.com/maps/streets/style.json?key=$key"
|
||||
views.mapView.onCreate(savedInstanceState)
|
||||
views.mapView.getMapAsync { map ->
|
||||
map.setStyle(styleUrl) { style ->
|
||||
addUserPinToMap(style)
|
||||
this.symbolManager = SymbolManager(views.mapView, map, style)
|
||||
this.map = map
|
||||
// All set, start location tracker
|
||||
locationTracker.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addUserPinToMap(style: Style) {
|
||||
session.getUser(session.myUserId)?.toMatrixItem()?.let {
|
||||
val size = Utils.dpToPx(44, requireContext())
|
||||
avatarRenderer.render(glideRequests, it, object : CustomTarget<Drawable>(size, size) {
|
||||
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
||||
val bgUserPin = ContextCompat.getDrawable(requireActivity(), R.drawable.bg_map_user_pin)!!
|
||||
val layerDrawable = LayerDrawable(arrayOf(bgUserPin, resource))
|
||||
val horizontalInset = Utils.dpToPx(4, requireContext())
|
||||
val topInset = Utils.dpToPx(4, requireContext())
|
||||
val bottomInset = Utils.dpToPx(8, requireContext())
|
||||
layerDrawable.setLayerInset(1, horizontalInset, topInset, horizontalInset, bottomInset)
|
||||
|
||||
style.addImage(
|
||||
USER_PIN_NAME,
|
||||
layerDrawable
|
||||
)
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {
|
||||
// Is it possible? Put placeholder instead?
|
||||
}
|
||||
})
|
||||
override fun onMapReady() {
|
||||
locationPinProvider.create(session.myUserId) {
|
||||
views.mapView.addPinToMap(
|
||||
pinId = USER_PIN_NAME,
|
||||
image = it,
|
||||
)
|
||||
// All set, start location tracker
|
||||
locationTracker.start()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLocationUpdate(locationData: LocationData) {
|
||||
lastZoomValue = if (lastZoomValue == -1.0) INITIAL_ZOOM else map?.cameraPosition?.zoom ?: INITIAL_ZOOM
|
||||
lastZoomValue = if (lastZoomValue == -1.0) INITIAL_ZOOM else views.mapView.getCurrentZoom() ?: INITIAL_ZOOM
|
||||
|
||||
val latLng = LatLng(locationData.latitude, locationData.longitude)
|
||||
|
||||
map?.cameraPosition = CameraPosition.Builder()
|
||||
.target(latLng)
|
||||
.zoom(lastZoomValue)
|
||||
.build()
|
||||
|
||||
symbolManager?.deleteAll()
|
||||
|
||||
symbolManager?.create(
|
||||
SymbolOptions()
|
||||
.withLatLng(latLng)
|
||||
.withIconImage(USER_PIN_NAME)
|
||||
.withIconAnchor(Property.ICON_ANCHOR_BOTTOM)
|
||||
)
|
||||
views.mapView.zoomToLocation(locationData.latitude, locationData.longitude, lastZoomValue)
|
||||
views.mapView.deleteAllPins()
|
||||
views.mapView.updatePinLocation(USER_PIN_NAME, locationData.latitude, locationData.longitude)
|
||||
|
||||
viewModel.handle(LocationSharingAction.OnLocationUpdate(locationData))
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@ class LocationTracker @Inject constructor(
|
|||
|
||||
fun stop() {
|
||||
locationManager?.removeUpdates(this)
|
||||
callback = null
|
||||
}
|
||||
|
||||
override fun onLocationChanged(location: Location) {
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.location
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.AttributeSet
|
||||
import com.mapbox.mapboxsdk.Mapbox
|
||||
import com.mapbox.mapboxsdk.camera.CameraPosition
|
||||
import com.mapbox.mapboxsdk.geometry.LatLng
|
||||
import com.mapbox.mapboxsdk.maps.MapView
|
||||
import com.mapbox.mapboxsdk.maps.MapboxMap
|
||||
import com.mapbox.mapboxsdk.maps.Style
|
||||
import com.mapbox.mapboxsdk.plugins.annotation.SymbolManager
|
||||
import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions
|
||||
import com.mapbox.mapboxsdk.style.layers.Property
|
||||
import im.vector.app.BuildConfig
|
||||
|
||||
class MapTilerMapView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : MapView(context, attrs, defStyleAttr), VectorMapView {
|
||||
|
||||
private var map: MapboxMap? = null
|
||||
private var symbolManager: SymbolManager? = null
|
||||
private var style: Style? = null
|
||||
|
||||
override fun initialize(listener: VectorMapListener) {
|
||||
getMapAsync { map ->
|
||||
map.setStyle(styleUrl) { style ->
|
||||
this.symbolManager = SymbolManager(this, map, style)
|
||||
this.map = map
|
||||
this.style = style
|
||||
listener.onMapReady()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun addPinToMap(pinId: String, image: Drawable) {
|
||||
style?.addImage(pinId, image)
|
||||
}
|
||||
|
||||
override fun updatePinLocation(pinId: String, latitude: Double, longitude: Double) {
|
||||
symbolManager?.create(
|
||||
SymbolOptions()
|
||||
.withLatLng(LatLng(latitude, longitude))
|
||||
.withIconImage(pinId)
|
||||
.withIconAnchor(Property.ICON_ANCHOR_BOTTOM)
|
||||
)
|
||||
}
|
||||
|
||||
override fun deleteAllPins() {
|
||||
symbolManager?.deleteAll()
|
||||
}
|
||||
|
||||
override fun zoomToLocation(latitude: Double, longitude: Double, zoom: Double) {
|
||||
map?.cameraPosition = CameraPosition.Builder()
|
||||
.target(LatLng(latitude, longitude))
|
||||
.zoom(zoom)
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun getCurrentZoom(): Double? {
|
||||
return map?.cameraPosition?.zoom
|
||||
}
|
||||
|
||||
override fun onClick(callback: () -> Unit) {
|
||||
map?.addOnMapClickListener {
|
||||
callback()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val styleUrl = "https://api.maptiler.com/maps/streets/style.json?key=${BuildConfig.mapTilerKey}"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.location
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
interface VectorMapListener {
|
||||
fun onMapReady()
|
||||
}
|
||||
|
||||
interface VectorMapView {
|
||||
fun initialize(listener: VectorMapListener)
|
||||
|
||||
fun addPinToMap(pinId: String, image: Drawable)
|
||||
fun updatePinLocation(pinId: String, latitude: Double, longitude: Double)
|
||||
fun deleteAllPins()
|
||||
|
||||
fun zoomToLocation(latitude: Double, longitude: Double, zoom: Double)
|
||||
fun getCurrentZoom(): Double?
|
||||
|
||||
fun onClick(callback: () -> Unit)
|
||||
}
|
|
@ -57,6 +57,7 @@ import im.vector.app.features.home.room.detail.search.SearchActivity
|
|||
import im.vector.app.features.home.room.detail.search.SearchArgs
|
||||
import im.vector.app.features.home.room.filtered.FilteredRoomsActivity
|
||||
import im.vector.app.features.invite.InviteUsersToRoomActivity
|
||||
import im.vector.app.features.location.LocationData
|
||||
import im.vector.app.features.location.LocationSharingActivity
|
||||
import im.vector.app.features.location.LocationSharingArgs
|
||||
import im.vector.app.features.location.LocationSharingMode
|
||||
|
@ -536,10 +537,14 @@ class DefaultNavigator @Inject constructor(
|
|||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
override fun openLocationSharing(context: Context, roomId: String, mode: LocationSharingMode) {
|
||||
override fun openLocationSharing(context: Context,
|
||||
roomId: String,
|
||||
mode: LocationSharingMode,
|
||||
initialLocationData: LocationData?,
|
||||
locationOwnerId: String) {
|
||||
val intent = LocationSharingActivity.getIntent(
|
||||
context,
|
||||
LocationSharingArgs(roomId = roomId, mode = mode)
|
||||
LocationSharingArgs(roomId = roomId, mode = mode, initialLocationData = initialLocationData, locationOwnerId = locationOwnerId)
|
||||
)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import androidx.activity.result.ActivityResultLauncher
|
|||
import androidx.core.util.Pair
|
||||
import im.vector.app.features.crypto.recover.SetupMode
|
||||
import im.vector.app.features.displayname.getBestName
|
||||
import im.vector.app.features.location.LocationData
|
||||
import im.vector.app.features.location.LocationSharingMode
|
||||
import im.vector.app.features.login.LoginConfig
|
||||
import im.vector.app.features.media.AttachmentData
|
||||
|
@ -151,5 +152,9 @@ interface Navigator {
|
|||
|
||||
fun openCreatePoll(context: Context, roomId: String)
|
||||
|
||||
fun openLocationSharing(context: Context, roomId: String, mode: LocationSharingMode)
|
||||
fun openLocationSharing(context: Context,
|
||||
roomId: String,
|
||||
mode: LocationSharingMode,
|
||||
initialLocationData: LocationData?,
|
||||
locationOwnerId: String)
|
||||
}
|
||||
|
|
5
vector/src/main/res/drawable/ic_share_external.xml
Normal file
5
vector/src/main/res/drawable/ic_share_external.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#0DBD8B" android:fillType="evenOdd" android:pathData="M21.768,2.8608V3.0305C21.7809,3.0862 21.7889,3.143 21.792,3.2002V8.4608C21.792,8.718 21.6908,8.9646 21.5108,9.1465C21.3308,9.3283 21.0866,9.4305 20.832,9.4305C20.5774,9.4305 20.3332,9.3283 20.1531,9.1465C19.9731,8.9646 19.872,8.718 19.872,8.4608V5.5517L11.256,14.2547C11.1661,14.3455 11.0595,14.4174 10.9421,14.4665C10.8248,14.5156 10.699,14.5409 10.572,14.5409C10.4449,14.5409 10.3192,14.5156 10.2018,14.4665C10.0844,14.4174 9.9778,14.3455 9.888,14.2547C9.7982,14.164 9.7269,14.0563 9.6783,13.9377C9.6297,13.8192 9.6046,13.6921 9.6046,13.5638C9.6046,13.4355 9.6297,13.3085 9.6783,13.1899C9.7269,13.0714 9.7982,12.9636 9.888,12.8729L18.504,4.2426H15.624C15.4979,4.2426 15.3731,4.2175 15.2566,4.1688C15.1401,4.1201 15.0343,4.0486 14.9451,3.9586C14.856,3.8686 14.7853,3.7617 14.737,3.644C14.6888,3.5264 14.664,3.4003 14.664,3.2729C14.664,3.1456 14.6888,3.0195 14.737,2.9018C14.7853,2.7842 14.856,2.6773 14.9451,2.5872C15.0343,2.4972 15.1401,2.4258 15.2566,2.377C15.3731,2.3283 15.4979,2.3032 15.624,2.3032H21.192L21.288,2.3517L21.36,2.4002L21.552,2.5457L21.672,2.6911L21.72,2.7638L21.768,2.8608ZM16.464,22.0122H5.088C4.3242,22.0122 3.5917,21.7057 3.0515,21.1602C2.5114,20.6146 2.208,19.8747 2.208,19.1031V7.6122C2.208,6.8407 2.5114,6.1007 3.0515,5.5552C3.5917,5.0096 4.3242,4.7031 5.088,4.7031H11.88C12.1346,4.7031 12.3788,4.8053 12.5588,4.9871C12.7389,5.169 12.84,5.4156 12.84,5.6728C12.84,5.93 12.7389,6.1767 12.5588,6.3585C12.3788,6.5403 12.1346,6.6425 11.88,6.6425H5.088C4.8334,6.6425 4.5892,6.7447 4.4092,6.9265C4.2291,7.1084 4.128,7.355 4.128,7.6122V19.1031C4.128,19.3603 4.2291,19.607 4.4092,19.7888C4.5892,19.9707 4.8334,20.0728 5.088,20.0728H16.464C16.7186,20.0728 16.9628,19.9707 17.1428,19.7888C17.3229,19.607 17.424,19.3603 17.424,19.1031V12.2425C17.424,11.9853 17.5252,11.7387 17.7052,11.5568C17.8852,11.375 18.1294,11.2728 18.384,11.2728C18.6386,11.2728 18.8828,11.375 19.0628,11.5568C19.2429,11.7387 19.344,11.9853 19.344,12.2425V19.1031C19.344,19.8747 19.0406,20.6146 18.5005,21.1602C17.9604,21.7057 17.2278,22.0122 16.464,22.0122Z"/>
|
||||
</vector>
|
11
vector/src/main/res/layout/fragment_location_preview.xml
Normal file
11
vector/src/main/res/layout/fragment_location_preview.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<im.vector.app.features.location.MapTilerMapView
|
||||
android:id="@+id/mapView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -4,7 +4,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.mapbox.mapboxsdk.maps.MapView
|
||||
<im.vector.app.features.location.MapTilerMapView
|
||||
android:id="@+id/mapView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
|
|
@ -130,6 +130,11 @@
|
|||
style="@style/TimelineContentStubBaseParams"
|
||||
android:layout="@layout/item_timeline_event_poll" />
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/messageContentLocationStub"
|
||||
style="@style/TimelineContentStubBaseParams"
|
||||
android:layout="@layout/item_timeline_event_location_stub" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<im.vector.app.core.ui.views.SendStateImageView
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/mapViewContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<im.vector.app.features.location.MapTilerMapView
|
||||
android:id="@+id/mapView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="200dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
12
vector/src/main/res/menu/menu_location_preview.xml
Normal file
12
vector/src/main/res/menu/menu_location_preview.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/share_external"
|
||||
android:icon="@drawable/ic_share_external"
|
||||
android:title="@string/location_share_external"
|
||||
app:iconTint="?colorPrimary"
|
||||
app:showAsAction="always" />
|
||||
|
||||
</menu>
|
|
@ -3710,4 +3710,5 @@
|
|||
<string name="location_share">Share location</string>
|
||||
<string name="location_not_available_dialog_title">Element could not access your location</string>
|
||||
<string name="location_not_available_dialog_content">Element could not access your location. Please try again later.</string>
|
||||
<string name="location_share_external">Open with</string>
|
||||
</resources>
|
||||
|
|
Loading…
Add table
Reference in a new issue