From 03a419404734dde97302f4800888f8c4b60ea235 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 14 Dec 2021 14:44:09 +0300 Subject: [PATCH 01/30] Add location attachment icon with required permissions. --- vector/src/main/AndroidManifest.xml | 4 ++++ .../vector/app/core/utils/PermissionsTools.kt | 1 + .../attachments/AttachmentTypeSelectorView.kt | 23 +++++++++++-------- .../home/room/detail/RoomDetailFragment.kt | 15 ++++++------ .../drawable/ic_attachment_location_white.xml | 9 ++++++++ .../layout/view_attachment_type_selector.xml | 21 +++++++++++++++++ vector/src/main/res/values/strings.xml | 1 + 7 files changed, 58 insertions(+), 16 deletions(-) create mode 100644 vector/src/main/res/drawable/ic_attachment_location_white.xml diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 93f4ac7632..f03b8735b4 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -42,6 +42,10 @@ android:name="android.permission.WRITE_CALENDAR" tools:node="remove" /> + + + + diff --git a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt index ba396ed252..5fc5e0de84 100644 --- a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt +++ b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt @@ -40,6 +40,7 @@ val PERMISSIONS_FOR_MEMBERS_SEARCH = listOf(Manifest.permission.READ_CONTACTS) val PERMISSIONS_FOR_ROOM_AVATAR = listOf(Manifest.permission.CAMERA) val PERMISSIONS_FOR_WRITING_FILES = listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE) val PERMISSIONS_FOR_PICKING_CONTACT = listOf(Manifest.permission.READ_CONTACTS) +val PERMISSIONS_FOR_LOCATION_SHARING = listOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION) val PERMISSIONS_EMPTY = emptyList() diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt index ccc07ef118..da3d40b36e 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt @@ -40,6 +40,7 @@ import com.amulyakhare.textdrawable.util.ColorGenerator import im.vector.app.R import im.vector.app.core.extensions.getMeasurements import im.vector.app.core.utils.PERMISSIONS_EMPTY +import im.vector.app.core.utils.PERMISSIONS_FOR_LOCATION_SHARING import im.vector.app.core.utils.PERMISSIONS_FOR_PICKING_CONTACT import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO import im.vector.app.databinding.ViewAttachmentTypeSelectorBinding @@ -77,6 +78,7 @@ class AttachmentTypeSelectorView(context: Context, views.attachmentAudioButton.configure(Type.AUDIO) views.attachmentContactButton.configure(Type.CONTACT) views.attachmentPollButton.configure(Type.POLL) + views.attachmentLocationButton.configure(Type.LOCATION) width = LinearLayout.LayoutParams.MATCH_PARENT height = LinearLayout.LayoutParams.WRAP_CONTENT animationStyle = 0 @@ -110,7 +112,8 @@ class AttachmentTypeSelectorView(context: Context, animateButtonIn(views.attachmentAudioButton, 0) animateButtonIn(views.attachmentContactButton, ANIMATION_DURATION / 4) animateButtonIn(views.attachmentStickersButton, ANIMATION_DURATION / 2) - animateButtonIn(views.attachmentPollButton, ANIMATION_DURATION / 4) + animateButtonIn(views.attachmentPollButton, ANIMATION_DURATION / 2) + animateButtonIn(views.attachmentLocationButton, ANIMATION_DURATION / 4) } override fun dismiss() { @@ -124,13 +127,14 @@ class AttachmentTypeSelectorView(context: Context, fun setAttachmentVisibility(type: Type, isVisible: Boolean) { when (type) { - Type.CAMERA -> views.attachmentCameraButtonContainer - Type.GALLERY -> views.attachmentGalleryButtonContainer - Type.FILE -> views.attachmentFileButtonContainer - Type.STICKER -> views.attachmentStickersButtonContainer - Type.AUDIO -> views.attachmentAudioButtonContainer - Type.CONTACT -> views.attachmentContactButtonContainer - Type.POLL -> views.attachmentPollButtonContainer + Type.CAMERA -> views.attachmentCameraButtonContainer + Type.GALLERY -> views.attachmentGalleryButtonContainer + Type.FILE -> views.attachmentFileButtonContainer + Type.STICKER -> views.attachmentStickersButtonContainer + Type.AUDIO -> views.attachmentAudioButtonContainer + Type.CONTACT -> views.attachmentContactButtonContainer + Type.POLL -> views.attachmentPollButtonContainer + Type.LOCATION -> views.attachmentLocationButtonContainer }.let { it.isVisible = isVisible } @@ -230,6 +234,7 @@ class AttachmentTypeSelectorView(context: Context, STICKER(PERMISSIONS_EMPTY), AUDIO(PERMISSIONS_EMPTY), CONTACT(PERMISSIONS_FOR_PICKING_CONTACT), - POLL(PERMISSIONS_EMPTY) + POLL(PERMISSIONS_EMPTY), + LOCATION(PERMISSIONS_FOR_LOCATION_SHARING) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index a77899a8b0..455f93f973 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -2207,18 +2207,19 @@ class RoomDetailFragment @Inject constructor( private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) { when (type) { - AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera( + AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera( activity = requireActivity(), vectorPreferences = vectorPreferences, cameraActivityResultLauncher = attachmentCameraActivityResultLauncher, cameraVideoActivityResultLauncher = attachmentCameraVideoActivityResultLauncher ) - AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher) - AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentMediaActivityResultLauncher) - AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher) - AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) - AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment) - AttachmentTypeSelectorView.Type.POLL -> navigator.openCreatePoll(requireContext(), roomDetailArgs.roomId) + AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher) + AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentMediaActivityResultLauncher) + AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher) + AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) + AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment) + AttachmentTypeSelectorView.Type.POLL -> navigator.openCreatePoll(requireContext(), roomDetailArgs.roomId) + AttachmentTypeSelectorView.Type.LOCATION -> Timber.d("On location attachment clicked") }.exhaustive } diff --git a/vector/src/main/res/drawable/ic_attachment_location_white.xml b/vector/src/main/res/drawable/ic_attachment_location_white.xml new file mode 100644 index 0000000000..865362312b --- /dev/null +++ b/vector/src/main/res/drawable/ic_attachment_location_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/vector/src/main/res/layout/view_attachment_type_selector.xml b/vector/src/main/res/layout/view_attachment_type_selector.xml index 4cd5e1910d..9ca7b8cdb0 100644 --- a/vector/src/main/res/layout/view_attachment_type_selector.xml +++ b/vector/src/main/res/layout/view_attachment_type_selector.xml @@ -194,6 +194,27 @@ android:importantForAccessibility="no" android:text="@string/attachment_type_poll" /> + + + + + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 34ac5fcddc..38c7d355c3 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2466,6 +2466,7 @@ "Gallery" "Sticker" Poll + Location Rotate and crop Couldn\'t handle share data From bf48617fc6354ed0f88a03e13b0ff1fb41525b28 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 15 Dec 2021 00:13:52 +0300 Subject: [PATCH 02/30] Create base UI components of location sharing screen. --- vector/src/main/AndroidManifest.xml | 1 + .../app/core/di/MavericksViewModelModule.kt | 6 ++ .../home/room/detail/RoomDetailFragment.kt | 5 +- .../location/LocationSharingAction.kt | 23 ++++++ .../location/LocationSharingActivity.kt | 80 +++++++++++++++++++ .../location/LocationSharingFragment.kt | 40 ++++++++++ .../location/LocationSharingViewEvents.kt | 23 ++++++ .../location/LocationSharingViewModel.kt | 41 ++++++++++ .../location/LocationSharingViewState.kt | 37 +++++++++ .../features/navigation/DefaultNavigator.kt | 11 +++ .../app/features/navigation/Navigator.kt | 3 + .../res/layout/activity_location_sharing.xml | 26 ++++++ .../res/layout/fragment_location_sharing.xml | 6 ++ vector/src/main/res/values/strings.xml | 4 + 14 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt create mode 100755 vector/src/main/res/layout/activity_location_sharing.xml create mode 100644 vector/src/main/res/layout/fragment_location_sharing.xml diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index f03b8735b4..28833ab333 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -336,6 +336,7 @@ + diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index d09cd21d19..b9757078ea 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -53,6 +53,7 @@ import im.vector.app.features.home.room.detail.upgrade.MigrateRoomViewModel import im.vector.app.features.home.room.list.RoomListViewModel import im.vector.app.features.homeserver.HomeServerCapabilitiesViewModel import im.vector.app.features.invite.InviteUsersToRoomViewModel +import im.vector.app.features.location.LocationSharingViewModel import im.vector.app.features.login.LoginViewModel import im.vector.app.features.login2.LoginViewModel2 import im.vector.app.features.login2.created.AccountCreatedViewModel @@ -576,4 +577,9 @@ interface MavericksViewModelModule { @IntoMap @MavericksViewModelKey(CreatePollViewModel::class) fun createPollViewModelFactory(factory: CreatePollViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @MavericksViewModelKey(LocationSharingViewModel::class) + fun createLocationSharingViewModelFactory(factory: LocationSharingViewModel.Factory): MavericksAssistedViewModelFactory<*, *> } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 455f93f973..22eeb7759b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -165,6 +165,7 @@ import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.PillImageSpan import im.vector.app.features.html.PillsPostProcessor import im.vector.app.features.invite.VectorInviteView +import im.vector.app.features.location.LocationSharingMode import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer import im.vector.app.features.notifications.NotificationDrawerManager @@ -2219,7 +2220,9 @@ class RoomDetailFragment @Inject constructor( AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment) AttachmentTypeSelectorView.Type.POLL -> navigator.openCreatePoll(requireContext(), roomDetailArgs.roomId) - AttachmentTypeSelectorView.Type.LOCATION -> Timber.d("On location attachment clicked") + AttachmentTypeSelectorView.Type.LOCATION -> { + navigator.openLocationSharing(requireContext(), roomDetailArgs.roomId, LocationSharingMode.STATIC_SHARING) + } }.exhaustive } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt new file mode 100644 index 0000000000..5139888b08 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt @@ -0,0 +1,23 @@ +/* + * 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 im.vector.app.core.platform.VectorViewModelAction + +sealed class LocationSharingAction : VectorViewModelAction { + data class OnShareLocation(val latitude: Double, val longitude: Double) : LocationSharingAction() +} diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt new file mode 100644 index 0000000000..fc685815db --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt @@ -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.location + +import android.content.Context +import android.content.Intent +import android.os.Parcelable +import com.google.android.material.appbar.MaterialToolbar +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.extensions.addFragment +import im.vector.app.core.platform.ToolbarConfigurable +import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.databinding.ActivityLocationSharingBinding +import kotlinx.parcelize.Parcelize + +@Parcelize +data class LocationSharingArgs( + val roomId: String, + val mode: LocationSharingMode +) : Parcelable + +@AndroidEntryPoint +class LocationSharingActivity : VectorBaseActivity(), + ToolbarConfigurable { + + override fun getBinding() = ActivityLocationSharingBinding.inflate(layoutInflater) + + override fun configure(toolbar: MaterialToolbar) { + configureToolbar(toolbar) + } + + override fun initUiAndData() { + val locationSharingArgs: LocationSharingArgs? = intent?.extras?.getParcelable(EXTRA_LOCATION_SHARING_ARGS) + if (locationSharingArgs == null) { + finish() + return + } + configure(views.toolbar) + supportActionBar?.title = getString(locationSharingArgs.mode.titleRes) + + if (isFirstCreation()) { + when (locationSharingArgs.mode) { + LocationSharingMode.STATIC_SHARING -> { + addFragment( + views.fragmentContainer, + LocationSharingFragment::class.java, + locationSharingArgs + ) + } + LocationSharingMode.PREVIEW -> { + } + } + } + } + + companion object { + + private const val EXTRA_LOCATION_SHARING_ARGS = "EXTRA_LOCATION_SHARING_ARGS" + + fun getIntent(context: Context, locationSharingArgs: LocationSharingArgs): Intent { + return Intent(context, LocationSharingActivity::class.java).apply { + putExtra(EXTRA_LOCATION_SHARING_ARGS, locationSharingArgs) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt new file mode 100644 index 0000000000..fd2e48546d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -0,0 +1,40 @@ +/* + * 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.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.airbnb.mvrx.activityViewModel +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentLocationSharingBinding +import javax.inject.Inject + +class LocationSharingFragment @Inject constructor() : + VectorBaseFragment() { + + private val viewModel: LocationSharingViewModel by activityViewModel() + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocationSharingBinding { + return FragmentLocationSharingBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + } +} diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt new file mode 100644 index 0000000000..1032a696ff --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt @@ -0,0 +1,23 @@ +/* + * 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 im.vector.app.core.platform.VectorViewEvents + +sealed class LocationSharingViewEvents : VectorViewEvents { + object Close : LocationSharingViewEvents() +} diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt new file mode 100644 index 0000000000..6da00f176d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -0,0 +1,41 @@ +/* + * 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 com.airbnb.mvrx.MavericksViewModelFactory +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.platform.VectorViewModel + +class LocationSharingViewModel @AssistedInject constructor( + @Assisted private val initialState: LocationSharingViewState +) : VectorViewModel(initialState) { + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: LocationSharingViewState): LocationSharingViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() { + } + + override fun handle(action: LocationSharingAction) { + } +} diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt new file mode 100644 index 0000000000..d62e568fb2 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt @@ -0,0 +1,37 @@ +/* + * 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 androidx.annotation.StringRes +import com.airbnb.mvrx.MavericksState +import im.vector.app.R + +enum class LocationSharingMode(@StringRes val titleRes: Int) { + STATIC_SHARING(R.string.location_activity_title_static_sharing), + PREVIEW(R.string.location_activity_title_preview) +} + +data class LocationSharingViewState( + val roomId: String, + val mode: LocationSharingMode +) : MavericksState { + + constructor(locationSharingArgs: LocationSharingArgs) : this( + roomId = locationSharingArgs.roomId, + mode = locationSharingArgs.mode + ) +} diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 6b035e7d49..16c50daf94 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -57,6 +57,9 @@ 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.LocationSharingActivity +import im.vector.app.features.location.LocationSharingArgs +import im.vector.app.features.location.LocationSharingMode import im.vector.app.features.login.LoginActivity import im.vector.app.features.login.LoginConfig import im.vector.app.features.login2.LoginActivity2 @@ -533,6 +536,14 @@ class DefaultNavigator @Inject constructor( context.startActivity(intent) } + override fun openLocationSharing(context: Context, roomId: String, mode: LocationSharingMode) { + val intent = LocationSharingActivity.getIntent( + context, + LocationSharingArgs(roomId = roomId, mode = mode) + ) + context.startActivity(intent) + } + private fun startActivity(context: Context, intent: Intent, buildTask: Boolean) { if (buildTask) { val stackBuilder = TaskStackBuilder.create(context) diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 6778c39a22..2668e10694 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -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.LocationSharingMode import im.vector.app.features.login.LoginConfig import im.vector.app.features.media.AttachmentData import im.vector.app.features.pin.PinMode @@ -149,4 +150,6 @@ interface Navigator { fun openCallTransfer(context: Context, callId: String) fun openCreatePoll(context: Context, roomId: String) + + fun openLocationSharing(context: Context, roomId: String, mode: LocationSharingMode) } diff --git a/vector/src/main/res/layout/activity_location_sharing.xml b/vector/src/main/res/layout/activity_location_sharing.xml new file mode 100755 index 0000000000..b278bb5a1a --- /dev/null +++ b/vector/src/main/res/layout/activity_location_sharing.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_location_sharing.xml b/vector/src/main/res/layout/fragment_location_sharing.xml new file mode 100644 index 0000000000..77d9ef65f8 --- /dev/null +++ b/vector/src/main/res/layout/fragment_location_sharing.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 38c7d355c3..0f26861d36 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3702,4 +3702,8 @@ Poll ended Remove poll Are you sure you want to remove this poll? You won\'t be able to recover it once removed. + + + Share location + Location From 824e713c51c5aa5b89a85ed5e4c105f5e76a4ba8 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 15 Dec 2021 13:57:43 +0300 Subject: [PATCH 03/30] Add mapview to the layout. --- build.gradle | 2 ++ vector/build.gradle | 3 +++ .../location/LocationSharingFragment.kt | 19 +++++++++++++++++++ .../res/layout/fragment_location_sharing.xml | 5 +++++ 4 files changed, 29 insertions(+) diff --git a/build.gradle b/build.gradle index e17f357905..29c3e61a44 100644 --- a/build.gradle +++ b/build.gradle @@ -61,6 +61,8 @@ allprojects { groups.jitsi.group.each { includeGroup it } } } + // TODO. MapTiler + mavenCentral() google { content { groups.google.regex.each { includeGroupByRegex it } diff --git a/vector/build.gradle b/vector/build.gradle index a578fdb52f..c3dd6ba50c 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -486,6 +486,9 @@ dependencies { } implementation 'commons-codec:commons-codec:1.15' + // MapTiler + implementation 'org.maplibre.gl:android-sdk:9.5.2' + // TESTS testImplementation libs.tests.junit diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt index fd2e48546d..7c59972871 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -21,6 +21,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.activityViewModel +import com.mapbox.mapboxsdk.Mapbox +import im.vector.app.BuildConfig import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentLocationSharingBinding import javax.inject.Inject @@ -34,7 +36,24 @@ class LocationSharingFragment @Inject constructor() : 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) + } + + 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) + } } } diff --git a/vector/src/main/res/layout/fragment_location_sharing.xml b/vector/src/main/res/layout/fragment_location_sharing.xml index 77d9ef65f8..6399b41796 100644 --- a/vector/src/main/res/layout/fragment_location_sharing.xml +++ b/vector/src/main/res/layout/fragment_location_sharing.xml @@ -3,4 +3,9 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + \ No newline at end of file From 5904a5955fed630169ee8ee669407e2412204e6c Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 15 Dec 2021 21:07:16 +0300 Subject: [PATCH 04/30] Show and track the current location of the user on map. --- vector/build.gradle | 1 + .../im/vector/app/core/di/FragmentModule.kt | 6 ++ .../location/LocationSharingFragment.kt | 102 +++++++++++++++++- .../app/features/location/LocationTracker.kt | 80 ++++++++++++++ .../src/main/res/drawable/bg_map_user_pin.xml | 10 ++ .../res/layout/fragment_location_sharing.xml | 38 +++++++ vector/src/main/res/values/strings.xml | 2 + 7 files changed, 235 insertions(+), 4 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationTracker.kt create mode 100644 vector/src/main/res/drawable/bg_map_user_pin.xml diff --git a/vector/build.gradle b/vector/build.gradle index c3dd6ba50c..197349f0f1 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -488,6 +488,7 @@ dependencies { // MapTiler implementation 'org.maplibre.gl:android-sdk:9.5.2' + implementation 'org.maplibre.gl:android-plugin-annotation-v9:1.0.0' // TESTS diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index ff84a46dab..3314c0565e 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -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.LocationSharingFragment import im.vector.app.features.login.LoginCaptchaFragment import im.vector.app.features.login.LoginFragment import im.vector.app.features.login.LoginGenericTextInputFormFragment @@ -855,4 +856,9 @@ interface FragmentModule { @IntoMap @FragmentKey(CreatePollFragment::class) fun bindCreatePollFragment(fragment: CreatePollFragment): Fragment + + @Binds + @IntoMap + @FragmentKey(LocationSharingFragment::class) + fun bindLocationSharingFragment(fragment: LocationSharingFragment): Fragment } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt index 7c59972871..390d1aafa5 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -16,22 +16,55 @@ 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.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 org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject -class LocationSharingFragment @Inject constructor() : - VectorBaseFragment() { +class LocationSharingFragment @Inject constructor( + private val locationTracker: LocationTracker, + private val session: Session, + private val avatarRenderer: AvatarRenderer +) : VectorBaseFragment(), LocationTracker.Callback { + + init { + locationTracker.callback = this + } 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) } @@ -48,12 +81,73 @@ class LocationSharingFragment @Inject constructor() : initMapView(savedInstanceState) } + override fun onDestroyView() { + super.onDestroyView() + locationTracker.stop() + } + private fun initMapView(savedInstanceState: Bundle?) { val key = BuildConfig.mapTilerKey - val styleUrl = "https://api.maptiler.com/maps/streets/style.json?key=${key}" + val styleUrl = "https://api.maptiler.com/maps/streets/style.json?key=$key" views.mapView.onCreate(savedInstanceState) views.mapView.getMapAsync { map -> - map.setStyle(styleUrl) + 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(size, size) { + override fun onResourceReady(resource: Drawable, transition: Transition?) { + 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 onLocationUpdate(latitude: Double, longitude: Double) { + lastZoomValue = if (lastZoomValue == -1.0) INITIAL_ZOOM else map?.cameraPosition?.zoom ?: INITIAL_ZOOM + + val latLng = LatLng(latitude, 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) + ) + } + + companion object { + const val INITIAL_ZOOM = 12.0 + const val USER_PIN_NAME = "USER_PIN_NAME" + } } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt new file mode 100644 index 0000000000..3cf2689aee --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt @@ -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.location + +import android.content.Context +import android.location.Location +import android.location.LocationListener +import android.location.LocationManager +import timber.log.Timber +import javax.inject.Inject + +class LocationTracker @Inject constructor( + private val context: Context) : LocationListener { + + interface Callback { + fun onLocationUpdate(latitude: Double, longitude: Double) + } + + private var locationManager: LocationManager? = null + var callback: Callback? = null + + fun start() { + val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as? LocationManager + + locationManager?.let { + val isGpsEnabled = it.isProviderEnabled(LocationManager.GPS_PROVIDER) + val isNetworkEnabled = it.isProviderEnabled(LocationManager.NETWORK_PROVIDER) + + val provider = when { + isGpsEnabled -> LocationManager.GPS_PROVIDER + isNetworkEnabled -> LocationManager.NETWORK_PROVIDER + else -> { + Timber.v("## LocationTracker. There is no location provider available") + return + } + } + + // Send last known location without waiting location updates + it.getLastKnownLocation(provider)?.let { lastKnownLocation -> + callback?.onLocationUpdate(lastKnownLocation.latitude, lastKnownLocation.longitude) + } + + it.requestLocationUpdates( + provider, + MIN_TIME_MILLIS_TO_UPDATE, + MIN_DISTANCE_METERS_TO_UPDATE, + this + ) + } ?: run { + Timber.v("## LocationTracker. LocationManager is not available") + } + } + + fun stop() { + locationManager?.removeUpdates(this) + } + + override fun onLocationChanged(location: Location) { + callback?.onLocationUpdate(location.latitude, location.longitude) + } + + companion object { + const val MIN_TIME_MILLIS_TO_UPDATE = 1 * 60 * 1000L // every 1 minute + const val MIN_DISTANCE_METERS_TO_UPDATE = 10f + } +} diff --git a/vector/src/main/res/drawable/bg_map_user_pin.xml b/vector/src/main/res/drawable/bg_map_user_pin.xml new file mode 100644 index 0000000000..148d3cfa29 --- /dev/null +++ b/vector/src/main/res/drawable/bg_map_user_pin.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/layout/fragment_location_sharing.xml b/vector/src/main/res/layout/fragment_location_sharing.xml index 6399b41796..bc587dc182 100644 --- a/vector/src/main/res/layout/fragment_location_sharing.xml +++ b/vector/src/main/res/layout/fragment_location_sharing.xml @@ -1,5 +1,6 @@ @@ -8,4 +9,41 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 0f26861d36..166f25c053 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3706,4 +3706,6 @@ Share location Location + Share location + Share location From 6495bd9e5ed373c33047a4135ec11d469cac8ecb Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 17 Dec 2021 15:09:02 +0300 Subject: [PATCH 05/30] Send location event. --- .../room/model/message/LocationInfo.kt | 22 +++--------- .../model/message/MessageLocationContent.kt | 8 ++--- .../sdk/api/session/room/send/SendService.kt | 8 +++++ .../session/room/send/DefaultSendService.kt | 6 ++++ .../room/send/LocalEchoEventFactory.kt | 35 +++++++++++++++++++ .../app/features/location/LocationData.kt | 24 +++++++++++++ .../location/LocationSharingAction.kt | 3 +- .../location/LocationSharingFragment.kt | 28 +++++++++++++-- .../location/LocationSharingViewEvents.kt | 1 + .../location/LocationSharingViewModel.kt | 29 ++++++++++++++- .../location/LocationSharingViewState.kt | 3 +- .../app/features/location/LocationTracker.kt | 6 ++-- vector/src/main/res/values/strings.xml | 2 ++ 13 files changed, 145 insertions(+), 30 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationData.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt index a76c3c5b64..5110926294 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt @@ -18,29 +18,17 @@ package org.matrix.android.sdk.api.session.room.model.message import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo @JsonClass(generateAdapter = true) data class LocationInfo( /** - * The URL to the thumbnail of the file. Only present if the thumbnail is unencrypted. + * Required. Required. RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' representing this location. */ - @Json(name = "thumbnail_url") val thumbnailUrl: String? = null, + @Json(name = "uri") val geoUri: String? = null, /** - * Metadata about the image referred to in thumbnail_url. + * Required. A description of the location e.g. 'Big Ben, London, UK', or some kind + * of content description for accessibility e.g. 'location attachment'. */ - @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null, - - /** - * Information on the encrypted thumbnail file, as specified in End-to-end encryption. Only present if the thumbnail is encrypted. - */ - @Json(name = "thumbnail_file") val thumbnailFile: EncryptedFileInfo? = null + @Json(name = "description") val description: String? = null ) - -/** - * Get the url of the encrypted thumbnail or of the thumbnail - */ -fun LocationInfo.getThumbnailUrl(): String? { - return thumbnailFile?.url ?: thumbnailUrl -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt index 6881c09924..9571cc4280 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt @@ -26,7 +26,7 @@ data class MessageLocationContent( /** * Required. Must be 'm.location'. */ - @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String, + @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_LOCATION, /** * Required. A description of the location e.g. 'Big Ben, London, UK', or some kind @@ -35,14 +35,14 @@ data class MessageLocationContent( @Json(name = "body") override val body: String, /** - * Required. A geo URI representing this location. + * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' representing this location. */ @Json(name = "geo_uri") val geoUri: String, /** - * + * See https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md */ - @Json(name = "info") val locationInfo: LocationInfo? = null, + @Json(name = "org.matrix.msc3488.location") val locationInfo: LocationInfo? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.new_content") override val newContent: Content? = null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index 5b387c3413..7983ad7068 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -122,6 +122,14 @@ interface SendService { */ fun resendMediaMessage(localEcho: TimelineEvent): Cancelable + /** + * Send a location event to the room + * @param latitude required latitude of the location + * @param longitude required longitude of the location + * @param uncertainty Accuracy of the location in meters + */ + fun sendLocation(latitude: Double, longitude: Double, uncertainty: Double?): Cancelable + /** * Remove this failed message from the timeline * @param localEcho the unsent local echo diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index d3162aef79..dbe4d5eb73 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -115,6 +115,12 @@ internal class DefaultSendService @AssistedInject constructor( .let { sendEvent(it) } } + override fun sendLocation(latitude: Double, longitude: Double, uncertainty: Double?): Cancelable { + return localEchoEventFactory.createLocationEvent(roomId, latitude, longitude, uncertainty) + .also { createLocalEcho(it) } + .let { sendEvent(it) } + } + override fun redactEvent(event: Event, reason: String?): Cancelable { // TODO manage media/attachements? val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 85b22628d7..179df22326 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -32,6 +32,7 @@ import org.matrix.android.sdk.api.session.room.model.message.AudioInfo import org.matrix.android.sdk.api.session.room.model.message.AudioWaveformInfo import org.matrix.android.sdk.api.session.room.model.message.FileInfo import org.matrix.android.sdk.api.session.room.model.message.ImageInfo +import org.matrix.android.sdk.api.session.room.model.message.LocationInfo import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody @@ -39,6 +40,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollConte import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent import org.matrix.android.sdk.api.session.room.model.message.MessageFormat import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent +import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent @@ -194,6 +196,22 @@ internal class LocalEchoEventFactory @Inject constructor( unsignedData = UnsignedData(age = null, transactionId = localId)) } + fun createLocationEvent(roomId: String, + latitude: Double, + longitude: Double, + uncertainty: Double?): Event { + val geoUri = buildGeoUri(latitude, longitude, uncertainty) + val content = MessageLocationContent( + geoUri = geoUri, + body = geoUri, + locationInfo = LocationInfo( + geoUri = geoUri, + description = geoUri + ) + ) + return createMessageEvent(roomId, content) + } + fun createReplaceTextOfReply(roomId: String, eventReplaced: TimelineEvent, originalEvent: TimelineEvent, @@ -463,6 +481,23 @@ internal class LocalEchoEventFactory @Inject constructor( } } + /** + * Returns RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' + * Uncertainty of the location is in meters and not required. + */ + private fun buildGeoUri(latitude: Double, longitude: Double, uncertainty: Double?): String { + return buildString { + append("geo:") + append(latitude) + append(",") + append(longitude) + uncertainty?.let { + append(";") + append(it) + } + } + } + /* * { "content": { diff --git a/vector/src/main/java/im/vector/app/features/location/LocationData.kt b/vector/src/main/java/im/vector/app/features/location/LocationData.kt new file mode 100644 index 0000000000..f85b8713d9 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationData.kt @@ -0,0 +1,24 @@ +/* + * 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 + +data class LocationData( + val latitude: Double, + val longitude: Double, + val uncertainty: Double? +) + diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt index 5139888b08..0efaefaa5b 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt @@ -19,5 +19,6 @@ package im.vector.app.features.location import im.vector.app.core.platform.VectorViewModelAction sealed class LocationSharingAction : VectorViewModelAction { - data class OnShareLocation(val latitude: Double, val longitude: Double) : LocationSharingAction() + data class OnLocationUpdate(val locationData: LocationData) : LocationSharingAction() + object OnShareLocation : LocationSharingAction() } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt index 390d1aafa5..8cd86a2356 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -26,6 +26,7 @@ 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 @@ -79,6 +80,17 @@ class LocationSharingFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) initMapView(savedInstanceState) + + views.shareLocationContainer.debouncedClicks { + viewModel.handle(LocationSharingAction.OnShareLocation) + } + + viewModel.observeViewEvents { + when (it) { + LocationSharingViewEvents.LocationNotAvailableError -> handleLocationNotAvailableError() + LocationSharingViewEvents.Close -> activity?.finish() + } + } } override fun onDestroyView() { @@ -126,10 +138,10 @@ class LocationSharingFragment @Inject constructor( } } - override fun onLocationUpdate(latitude: Double, longitude: Double) { + override fun onLocationUpdate(locationData: LocationData) { lastZoomValue = if (lastZoomValue == -1.0) INITIAL_ZOOM else map?.cameraPosition?.zoom ?: INITIAL_ZOOM - val latLng = LatLng(latitude, longitude) + val latLng = LatLng(locationData.latitude, locationData.longitude) map?.cameraPosition = CameraPosition.Builder() .target(latLng) @@ -144,10 +156,20 @@ class LocationSharingFragment @Inject constructor( .withIconImage(USER_PIN_NAME) .withIconAnchor(Property.ICON_ANCHOR_BOTTOM) ) + + viewModel.handle(LocationSharingAction.OnLocationUpdate(locationData)) + } + + private fun handleLocationNotAvailableError() { + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.location_not_available_dialog_title) + .setMessage(R.string.location_not_available_dialog_content) + .setPositiveButton(R.string.ok, null) + .show() } companion object { - const val INITIAL_ZOOM = 12.0 + const val INITIAL_ZOOM = 15.0 const val USER_PIN_NAME = "USER_PIN_NAME" } } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt index 1032a696ff..743daaf5e0 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt @@ -20,4 +20,5 @@ import im.vector.app.core.platform.VectorViewEvents sealed class LocationSharingViewEvents : VectorViewEvents { object Close : LocationSharingViewEvents() + object LocationNotAvailableError : LocationSharingViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index 6da00f176d..4525cac446 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -23,11 +23,15 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel +import org.matrix.android.sdk.api.session.Session class LocationSharingViewModel @AssistedInject constructor( - @Assisted private val initialState: LocationSharingViewState + @Assisted private val initialState: LocationSharingViewState, + session: Session ) : VectorViewModel(initialState) { + private val room = session.getRoom(initialState.roomId)!! + @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { override fun create(initialState: LocationSharingViewState): LocationSharingViewModel @@ -37,5 +41,28 @@ class LocationSharingViewModel @AssistedInject constructor( } override fun handle(action: LocationSharingAction) { + when (action) { + is LocationSharingAction.OnLocationUpdate -> handleLocationUpdate(action.locationData) + LocationSharingAction.OnShareLocation -> handleShareLocation() + } + } + + private fun handleShareLocation() = withState { state -> + state.lastKnownLocation?.let { location -> + room.sendLocation( + latitude = location.latitude, + longitude = location.longitude, + uncertainty = location.uncertainty + ) + _viewEvents.post(LocationSharingViewEvents.Close) + } ?: run { + _viewEvents.post(LocationSharingViewEvents.LocationNotAvailableError) + } + } + + private fun handleLocationUpdate(locationData: LocationData) { + setState { + copy(lastKnownLocation = locationData) + } } } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt index d62e568fb2..2869929b12 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt @@ -27,7 +27,8 @@ enum class LocationSharingMode(@StringRes val titleRes: Int) { data class LocationSharingViewState( val roomId: String, - val mode: LocationSharingMode + val mode: LocationSharingMode, + val lastKnownLocation: LocationData? = null ) : MavericksState { constructor(locationSharingArgs: LocationSharingArgs) : this( diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt index 3cf2689aee..93993245f8 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt @@ -27,7 +27,7 @@ class LocationTracker @Inject constructor( private val context: Context) : LocationListener { interface Callback { - fun onLocationUpdate(latitude: Double, longitude: Double) + fun onLocationUpdate(locationData: LocationData) } private var locationManager: LocationManager? = null @@ -51,7 +51,7 @@ class LocationTracker @Inject constructor( // Send last known location without waiting location updates it.getLastKnownLocation(provider)?.let { lastKnownLocation -> - callback?.onLocationUpdate(lastKnownLocation.latitude, lastKnownLocation.longitude) + callback?.onLocationUpdate(LocationData(lastKnownLocation.latitude, lastKnownLocation.longitude, lastKnownLocation.accuracy.toDouble())) } it.requestLocationUpdates( @@ -70,7 +70,7 @@ class LocationTracker @Inject constructor( } override fun onLocationChanged(location: Location) { - callback?.onLocationUpdate(location.latitude, location.longitude) + callback?.onLocationUpdate(LocationData(location.latitude, location.longitude, location.accuracy.toDouble())) } companion object { diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 166f25c053..086469a1fd 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3708,4 +3708,6 @@ Location Share location Share location + Element could not access your location + Element could not access your location. Please try again later. From a0afab45fbc421e86e0647d96fa9aa37a0aec64f Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 27 Dec 2021 14:03:59 +0300 Subject: [PATCH 06/30] Show location preview and allow to share with external apps. --- .../java/im/vector/app/VectorApplication.kt | 4 + .../im/vector/app/core/di/FragmentModule.kt | 6 ++ .../core/utils/ExternalApplicationsUtil.kt | 20 ++++ .../home/room/detail/RoomDetailAction.kt | 4 + .../home/room/detail/RoomDetailFragment.kt | 21 +++- .../home/room/detail/RoomDetailViewEvents.kt | 3 + .../home/room/detail/RoomDetailViewModel.kt | 6 ++ .../timeline/factory/MessageItemFactory.kt | 40 +++++-- .../timeline/helper/LocationPinProvider.kt | 74 +++++++++++++ .../timeline/item/MessageLocationItem.kt | 80 ++++++++++++++ .../app/features/location/LocationData.kt | 49 ++++++++- .../location/LocationPreviewFragment.kt | 85 +++++++++++++++ .../location/LocationSharingActivity.kt | 9 +- .../location/LocationSharingFragment.kt | 102 +++--------------- .../app/features/location/LocationTracker.kt | 1 + .../app/features/location/MapTilerMapView.kt | 92 ++++++++++++++++ .../app/features/location/VectorMapView.kt | 36 +++++++ .../features/navigation/DefaultNavigator.kt | 9 +- .../app/features/navigation/Navigator.kt | 7 +- .../main/res/drawable/ic_share_external.xml | 5 + .../res/layout/fragment_location_preview.xml | 11 ++ .../res/layout/fragment_location_sharing.xml | 2 +- .../res/layout/item_timeline_event_base.xml | 5 + .../item_timeline_event_location_stub.xml | 17 +++ .../main/res/menu/menu_location_preview.xml | 12 +++ vector/src/main/res/values/strings.xml | 1 + 26 files changed, 601 insertions(+), 100 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt create mode 100644 vector/src/main/java/im/vector/app/features/location/VectorMapView.kt create mode 100644 vector/src/main/res/drawable/ic_share_external.xml create mode 100644 vector/src/main/res/layout/fragment_location_preview.xml create mode 100644 vector/src/main/res/layout/item_timeline_event_location_stub.xml create mode 100644 vector/src/main/res/menu/menu_location_preview.xml diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index 400fb7eb89..52de28a1cf 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -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 { diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index 3314c0565e..4d70f49186 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -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 } diff --git a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt index bdaf520ba1..f3138218eb 100644 --- a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt +++ b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt @@ -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) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index f20a32848c..ce04b6812d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -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() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 22eeb7759b..d530b35c0c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -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 } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt index 2e7f2bfd63..de1110dfcb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt @@ -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() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index f438c6e1e4..0c67c66af8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -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 diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 22d282d567..76e8908d2d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -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() val pollResponseSummary = informationData.pollResponseAggregatedSummary diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt new file mode 100644 index 0000000000..92a4bb8473 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt @@ -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() + + 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(size, size) { + override fun onResourceReady(resource: Drawable, transition: Transition?) { + 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? + } + }) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt new file mode 100644 index 0000000000..d4995d3fad --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt @@ -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() { + + @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(R.id.mapViewContainer) + val mapView by bind(R.id.mapView) + } + + companion object { + private const val STUB_ID = R.id.messageContentLocationStub + private const val INITIAL_ZOOM = 15.0 + } +} diff --git a/vector/src/main/java/im/vector/app/features/location/LocationData.kt b/vector/src/main/java/im/vector/app/features/location/LocationData.kt index f85b8713d9..874e159e80 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationData.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationData.kt @@ -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 + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt new file mode 100644 index 0000000000..e13456dbdc --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt @@ -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(), 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 + } +} diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt index fc685815db..6a200435dc 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt @@ -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 { + addFragment( + views.fragmentContainer, + LocationPreviewFragment::class.java, + locationSharingArgs + ) } } } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt index 8cd86a2356..c7c4c0fed1 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -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(), LocationTracker.Callback { + private val locationPinProvider: LocationPinProvider +) : VectorBaseFragment(), 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(size, size) { - override fun onResourceReady(resource: Drawable, transition: Transition?) { - 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)) } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt index 93993245f8..0ea1e6810b 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt @@ -67,6 +67,7 @@ class LocationTracker @Inject constructor( fun stop() { locationManager?.removeUpdates(this) + callback = null } override fun onLocationChanged(location: Location) { diff --git a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt new file mode 100644 index 0000000000..627b9e5ec3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt @@ -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}" + } +} diff --git a/vector/src/main/java/im/vector/app/features/location/VectorMapView.kt b/vector/src/main/java/im/vector/app/features/location/VectorMapView.kt new file mode 100644 index 0000000000..5dbeced175 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/VectorMapView.kt @@ -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) +} diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 16c50daf94..fb923dabb2 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -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) } diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 2668e10694..06f38b381d 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -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) } diff --git a/vector/src/main/res/drawable/ic_share_external.xml b/vector/src/main/res/drawable/ic_share_external.xml new file mode 100644 index 0000000000..c4b78c8a83 --- /dev/null +++ b/vector/src/main/res/drawable/ic_share_external.xml @@ -0,0 +1,5 @@ + + + diff --git a/vector/src/main/res/layout/fragment_location_preview.xml b/vector/src/main/res/layout/fragment_location_preview.xml new file mode 100644 index 0000000000..ae3e8f1778 --- /dev/null +++ b/vector/src/main/res/layout/fragment_location_preview.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_location_sharing.xml b/vector/src/main/res/layout/fragment_location_sharing.xml index bc587dc182..8122b5def5 100644 --- a/vector/src/main/res/layout/fragment_location_sharing.xml +++ b/vector/src/main/res/layout/fragment_location_sharing.xml @@ -4,7 +4,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index 5a04acf677..6360b287d0 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -130,6 +130,11 @@ style="@style/TimelineContentStubBaseParams" android:layout="@layout/item_timeline_event_poll" /> + + + + + + + diff --git a/vector/src/main/res/menu/menu_location_preview.xml b/vector/src/main/res/menu/menu_location_preview.xml new file mode 100644 index 0000000000..2616674b8a --- /dev/null +++ b/vector/src/main/res/menu/menu_location_preview.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 086469a1fd..e5fe5ad5c7 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3710,4 +3710,5 @@ Share location Element could not access your location Element could not access your location. Please try again later. + Open with From ccd4396336927dc165374743e60e3ef285121788 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 27 Dec 2021 14:04:36 +0300 Subject: [PATCH 07/30] Allow to reply location messages. --- .../BottomSheetMessagePreviewItem.kt | 27 +++++++++++++++++++ .../action/MessageActionsEpoxyController.kt | 13 ++++++++- .../action/MessageActionsViewModel.kt | 5 ++-- .../item_bottom_sheet_message_preview.xml | 13 +++++++++ 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt index eef057efd4..efa4702532 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt @@ -30,10 +30,16 @@ import im.vector.app.core.epoxy.onClick import im.vector.app.core.epoxy.util.preventMutation import im.vector.app.core.extensions.setTextOrHide import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.home.room.detail.timeline.item.BindingOptions import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess +import im.vector.app.features.location.LocationData +import im.vector.app.features.location.MapTilerMapView +import im.vector.app.features.location.VectorMapListener import im.vector.app.features.media.ImageContentRenderer import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.room.model.message.LocationInfo +import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent import org.matrix.android.sdk.api.util.MatrixItem /** @@ -66,6 +72,12 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel + holder.mapView.initialize(object : VectorMapListener { + override fun onMapReady() { + holder.mapView.zoomToLocation(location.latitude, location.longitude, 15.0) + locationPinProvider?.create(matrixItem.id) { pinDrawable -> + holder.mapView.addPinToMap(matrixItem.id, pinDrawable) + holder.mapView.updatePinLocation(matrixItem.id, location.latitude, location.longitude) + } + } + }) + } } override fun unbind(holder: Holder) { @@ -105,5 +131,6 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel(R.id.bottom_sheet_message_preview_body_details) val timestamp by bind(R.id.bottom_sheet_message_preview_timestamp) val imagePreview by bind(R.id.bottom_sheet_message_preview_image) + val mapView by bind(R.id.bottom_sheet_message_preview_location) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt index 3826c4cbad..0bd4f1bf21 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt @@ -33,14 +33,19 @@ import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.format.EventDetailsFormatter +import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.home.room.detail.timeline.image.buildImageContentRendererData import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod import im.vector.app.features.home.room.detail.timeline.tools.linkify import im.vector.app.features.html.SpanUtils +import im.vector.app.features.location.LocationData import im.vector.app.features.media.ImageContentRenderer import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent import org.matrix.android.sdk.api.session.room.send.SendState import javax.inject.Inject @@ -56,7 +61,8 @@ class MessageActionsEpoxyController @Inject constructor( private val errorFormatter: ErrorFormatter, private val spanUtils: SpanUtils, private val eventDetailsFormatter: EventDetailsFormatter, - private val dateFormatter: VectorDateFormatter + private val dateFormatter: VectorDateFormatter, + private val locationPinProvider: LocationPinProvider ) : TypedEpoxyController() { var listener: MessageActionsEpoxyControllerListener? = null @@ -68,6 +74,9 @@ class MessageActionsEpoxyController @Inject constructor( val formattedDate = dateFormatter.format(date, DateFormatKind.MESSAGE_DETAIL) val body = state.messageBody.linkify(host.listener) val bindingOptions = spanUtils.getBindingOptions(body) + val locationData = state.timelineEvent()?.root?.getClearContent()?.toModel(catchError = true)?.let { + LocationData.create(it.locationInfo?.geoUri ?: it.geoUri) + } bottomSheetMessagePreviewItem { id("preview") avatarRenderer(host.avatarRenderer) @@ -80,6 +89,8 @@ class MessageActionsEpoxyController @Inject constructor( body(body) bodyDetails(host.eventDetailsFormatter.format(state.timelineEvent()?.root)) time(formattedDate) + locationData(locationData) + locationPinProvider(host.locationPinProvider) } // Send state diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index ff7d555ee3..2b3d391634 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -424,8 +424,9 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted MessageType.MSGTYPE_VIDEO, MessageType.MSGTYPE_AUDIO, MessageType.MSGTYPE_FILE, - MessageType.MSGTYPE_POLL_START -> true - else -> false + MessageType.MSGTYPE_POLL_START, + MessageType.MSGTYPE_LOCATION -> true + else -> false } } diff --git a/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml b/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml index 771d4d10f0..1959cfc3ba 100644 --- a/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml +++ b/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml @@ -103,4 +103,17 @@ tools:text="1080 x 1024 - 43s - 12kB" tools:visibility="visible" /> + + From de809d6013098011b7da929d5c2c9380d9a2b736 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 27 Dec 2021 17:17:20 +0300 Subject: [PATCH 08/30] Add settings item to allow location sharing. --- .../app/features/home/room/detail/RoomDetailFragment.kt | 1 + .../im/vector/app/features/settings/VectorPreferences.kt | 7 +++++++ vector/src/main/res/values/strings.xml | 1 + vector/src/main/res/xml/vector_settings_preferences.xml | 5 +++++ 4 files changed, 14 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index d530b35c0c..25bd5877f5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1379,6 +1379,7 @@ class RoomDetailFragment @Inject constructor( if (!::attachmentTypeSelector.isInitialized) { attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@RoomDetailFragment) attachmentTypeSelector.setAttachmentVisibility(AttachmentTypeSelectorView.Type.POLL, vectorPreferences.labsEnablePolls()) + attachmentTypeSelector.setAttachmentVisibility(AttachmentTypeSelectorView.Type.LOCATION, vectorPreferences.isLocationSharingEnabled()) } attachmentTypeSelector.show(views.composerLayout.views.attachmentButton, keyboardStateUtils.isKeyboardShowing) } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 64561cbc12..c367d628bd 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -185,6 +185,9 @@ class VectorPreferences @Inject constructor(private val context: Context) { private const val DID_ASK_TO_ENABLE_SESSION_PUSH = "DID_ASK_TO_ENABLE_SESSION_PUSH" private const val DID_PROMOTE_NEW_RESTRICTED_JOIN_RULE = "DID_PROMOTE_NEW_RESTRICTED_JOIN_RULE" + // Location Sharing + private const val SETTINGS_PREF_ENABLE_LOCATION_SHARING = "SETTINGS_PREF_ENABLE_LOCATION_SHARING" + private const val MEDIA_SAVING_3_DAYS = 0 private const val MEDIA_SAVING_1_WEEK = 1 private const val MEDIA_SAVING_1_MONTH = 2 @@ -990,4 +993,8 @@ class VectorPreferences @Inject constructor(private val context: Context) { fun labsEnablePolls(): Boolean { return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_POLLS, false) } + + fun isLocationSharingEnabled(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_PREF_ENABLE_LOCATION_SHARING, true) + } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index e5fe5ad5c7..1b4d137928 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3711,4 +3711,5 @@ Element could not access your location Element could not access your location. Please try again later. Open with + Enable location sharing diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml index 14c7dc7b80..bc9e06f754 100644 --- a/vector/src/main/res/xml/vector_settings_preferences.xml +++ b/vector/src/main/res/xml/vector_settings_preferences.xml @@ -28,6 +28,11 @@ android:persistent="false" android:title="@string/font_size" /> + + From 15fa42ba9b367057d4382f247fc8066a484cca5b Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 27 Dec 2021 17:55:48 +0300 Subject: [PATCH 09/30] Lint fixes. --- .../epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt | 2 -- .../timeline/action/MessageActionsEpoxyController.kt | 1 - .../room/detail/timeline/factory/MessageItemFactory.kt | 1 - .../java/im/vector/app/features/location/LocationData.kt | 1 - .../app/features/location/LocationPreviewFragment.kt | 9 +++------ .../im/vector/app/features/location/LocationTracker.kt | 3 +++ .../im/vector/app/features/location/MapTilerMapView.kt | 1 - 7 files changed, 6 insertions(+), 12 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt index ae9efe367a..7c18dd818e 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt @@ -39,8 +39,6 @@ import im.vector.app.features.location.MapTilerMapView import im.vector.app.features.location.VectorMapListener import im.vector.app.features.media.ImageContentRenderer import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.session.room.model.message.LocationInfo -import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent import org.matrix.android.sdk.api.util.MatrixItem /** diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt index 0bd4f1bf21..3e1edfa158 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt @@ -43,7 +43,6 @@ import im.vector.app.features.location.LocationData import im.vector.app.features.media.ImageContentRenderer import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent import org.matrix.android.sdk.api.session.room.send.SendState diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index b0df2d6a54..607e4c1252 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -185,7 +185,6 @@ class MessageItemFactory @Inject constructor( highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes): MessageLocationItem? { - val geoUri = locationContent.locationInfo?.geoUri ?: locationContent.geoUri val locationData = LocationData.create(geoUri) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationData.kt b/vector/src/main/java/im/vector/app/features/location/LocationData.kt index 874e159e80..b790bde710 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationData.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationData.kt @@ -68,4 +68,3 @@ data class LocationData( } } } - diff --git a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt index e13456dbdc..1a7ed49209 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt @@ -16,21 +16,18 @@ 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.platform.VectorBaseFragment import im.vector.app.core.utils.openLocation +import im.vector.app.databinding.FragmentLocationPreviewBinding import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider -import org.matrix.android.sdk.api.extensions.tryOrNull +import javax.inject.Inject class LocationPreviewFragment @Inject constructor( private val locationPinProvider: LocationPinProvider diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt index 0ea1e6810b..6e55735f9b 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt @@ -16,10 +16,12 @@ package im.vector.app.features.location +import android.Manifest import android.content.Context import android.location.Location import android.location.LocationListener import android.location.LocationManager +import androidx.annotation.RequiresPermission import timber.log.Timber import javax.inject.Inject @@ -65,6 +67,7 @@ class LocationTracker @Inject constructor( } } + @RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION]) fun stop() { locationManager?.removeUpdates(this) callback = null diff --git a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt index 627b9e5ec3..386e96988f 100644 --- a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt +++ b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt @@ -19,7 +19,6 @@ 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 From fc4d18f84e2f51cfb05cb10e3d28730439c49125 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 28 Dec 2021 14:00:37 +0300 Subject: [PATCH 10/30] Dummy maptiler key added for CI. --- vector/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/build.gradle b/vector/build.gradle index 562e0af679..2f10695c2f 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -150,6 +150,8 @@ android { // This *must* only be set in trusted environments. buildConfigField "Boolean", "handleCallAssertedIdentityEvents", "false" + buildConfigField "String", "mapTilerKey", "\"DUMMY_KEY\"" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // Keep abiFilter for the universalApk From 6e1911e686a402c8503b0377bbda3bbd65aee4dc Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 28 Dec 2021 17:04:51 +0300 Subject: [PATCH 11/30] Lint fixes. --- .../timeline/factory/MessageItemFactory.kt | 14 ++++++++- .../timeline/item/MessageLocationItem.kt | 29 +++++++++++-------- .../app/features/location/LocationTracker.kt | 3 +- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 607e4c1252..e293b1ecbd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -33,6 +33,7 @@ import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.containsOnlyEmojis +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.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder @@ -71,6 +72,7 @@ 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.location.VectorMapView import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer import me.gujun.android.span.span @@ -188,6 +190,16 @@ class MessageItemFactory @Inject constructor( val geoUri = locationContent.locationInfo?.geoUri ?: locationContent.geoUri val locationData = LocationData.create(geoUri) + val mapCallback: MessageLocationItem.Callback = object: MessageLocationItem.Callback { + override fun onMapReady(mapView: VectorMapView) { + mapView.onClick { + locationData?.let { + callback?.onTimelineItemAction(RoomDetailAction.ShowLocation(it, informationData.senderId)) + } + } + } + } + return MessageLocationItem_() .attributes(attributes) .locationData(locationData) @@ -195,7 +207,7 @@ class MessageItemFactory @Inject constructor( .locationPinProvider(locationPinProvider) .highlighted(highlight) .leftGuideline(avatarSizeProvider.leftGuideline) - .callback(callback) + .callback(mapCallback) } private fun buildPollItem(pollContent: MessagePollContent, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt index d4995d3fad..2d65fd3131 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt @@ -27,12 +27,17 @@ import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvid import im.vector.app.features.location.LocationData import im.vector.app.features.location.MapTilerMapView import im.vector.app.features.location.VectorMapListener +import im.vector.app.features.location.VectorMapView @EpoxyModelClass(layout = R.layout.item_timeline_event_base) abstract class MessageLocationItem : AbsMessageItem() { + interface Callback { + fun onMapReady(mapView: VectorMapView) + } + @EpoxyAttribute - var callback: TimelineEventController.Callback? = null + var callback: Callback? = null @EpoxyAttribute var locationData: LocationData? = null @@ -50,20 +55,20 @@ abstract class MessageLocationItem : AbsMessageItem( 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) + holder.mapView.apply { + initialize(object : VectorMapListener { + override fun onMapReady() { + zoomToLocation(location.latitude, location.longitude, INITIAL_ZOOM) - locationPinProvider?.create(locationOwnerId) { pinDrawable -> - holder.mapView.addPinToMap(locationOwnerId, pinDrawable) - holder.mapView.updatePinLocation(locationOwnerId, location.latitude, location.longitude) - } + locationPinProvider?.create(locationOwnerId) { pinDrawable -> + addPinToMap(locationOwnerId, pinDrawable) + updatePinLocation(locationOwnerId, location.latitude, location.longitude) + } - holder.mapView.onClick { - callback?.onTimelineItemAction(RoomDetailAction.ShowLocation(location, locationOwnerId)) + callback?.onMapReady(this@apply) } - } - }) + }) + } } override fun getViewType() = STUB_ID diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt index 6e55735f9b..48692ef216 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt @@ -24,6 +24,7 @@ import android.location.LocationManager import androidx.annotation.RequiresPermission import timber.log.Timber import javax.inject.Inject +import androidx.core.content.getSystemService class LocationTracker @Inject constructor( private val context: Context) : LocationListener { @@ -36,7 +37,7 @@ class LocationTracker @Inject constructor( var callback: Callback? = null fun start() { - val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as? LocationManager + val locationManager = context.getSystemService() locationManager?.let { val isGpsEnabled = it.isProviderEnabled(LocationManager.GPS_PROVIDER) From 4d6eec8972ebf7fd9eaf31762898538814eeaf0f Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 31 Dec 2021 15:12:46 +0300 Subject: [PATCH 12/30] Rate limited maptiler key is added. --- changelog.d/2210.bugfix | 1 + vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/2210.bugfix diff --git a/changelog.d/2210.bugfix b/changelog.d/2210.bugfix new file mode 100644 index 0000000000..6f7c09ce26 --- /dev/null +++ b/changelog.d/2210.bugfix @@ -0,0 +1 @@ +Static location sharing and rendering \ No newline at end of file diff --git a/vector/build.gradle b/vector/build.gradle index 2f10695c2f..76fe629d3c 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -150,7 +150,7 @@ android { // This *must* only be set in trusted environments. buildConfigField "Boolean", "handleCallAssertedIdentityEvents", "false" - buildConfigField "String", "mapTilerKey", "\"DUMMY_KEY\"" + buildConfigField "String", "mapTilerKey", "\"KF6tcY7YuKFgJSZ1EFQ2\"" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" From ae8ca5356ff34947e3f11642f04ef8b7b467a2b8 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 12 Jan 2022 15:14:13 +0300 Subject: [PATCH 13/30] Fix recyclerview issues on async map functions. --- .../detail/timeline/factory/MessageItemFactory.kt | 9 +++------ .../room/detail/timeline/item/MessageLocationItem.kt | 12 ++++++++---- .../res/layout/item_timeline_event_location_stub.xml | 9 +++++++++ 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 51aa51f070..4070532f80 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -73,7 +73,6 @@ 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.location.VectorMapView import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer import me.gujun.android.span.span @@ -192,11 +191,9 @@ class MessageItemFactory @Inject constructor( val locationData = LocationData.create(geoUri) val mapCallback: MessageLocationItem.Callback = object : MessageLocationItem.Callback { - override fun onMapReady(mapView: VectorMapView) { - mapView.onClick { - locationData?.let { - callback?.onTimelineItemAction(RoomDetailAction.ShowLocation(it, informationData.senderId)) - } + override fun onMapClicked() { + locationData?.let { + callback?.onTimelineItemAction(RoomDetailAction.ShowLocation(it, informationData.senderId)) } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt index cb37901245..d8ccf3761e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt @@ -16,21 +16,22 @@ package im.vector.app.features.home.room.detail.timeline.item +import android.widget.FrameLayout 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.timeline.helper.LocationPinProvider import im.vector.app.features.location.LocationData import im.vector.app.features.location.MapTilerMapView import im.vector.app.features.location.VectorMapListener -import im.vector.app.features.location.VectorMapView @EpoxyModelClass(layout = R.layout.item_timeline_event_base) abstract class MessageLocationItem : AbsMessageItem() { interface Callback { - fun onMapReady(mapView: VectorMapView) + fun onMapClicked() } @EpoxyAttribute @@ -52,6 +53,10 @@ abstract class MessageLocationItem : AbsMessageItem( val location = locationData ?: return val locationOwnerId = userId ?: return + holder.clickableMapArea.onClick { + callback?.onMapClicked() + } + holder.mapView.apply { initialize(object : VectorMapListener { override fun onMapReady() { @@ -61,8 +66,6 @@ abstract class MessageLocationItem : AbsMessageItem( addPinToMap(locationOwnerId, pinDrawable) updatePinLocation(locationOwnerId, location.latitude, location.longitude) } - - callback?.onMapReady(this@apply) } }) } @@ -73,6 +76,7 @@ abstract class MessageLocationItem : AbsMessageItem( class Holder : AbsMessageItem.Holder(STUB_ID) { val mapViewContainer by bind(R.id.mapViewContainer) val mapView by bind(R.id.mapView) + val clickableMapArea by bind(R.id.clickableMapArea) } companion object { diff --git a/vector/src/main/res/layout/item_timeline_event_location_stub.xml b/vector/src/main/res/layout/item_timeline_event_location_stub.xml index b126231ba1..0b211ec5e0 100644 --- a/vector/src/main/res/layout/item_timeline_event_location_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_location_stub.xml @@ -14,4 +14,13 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + From f9c5b2021de3859c14d12b4631770c125843942f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 13 Jan 2022 15:09:53 +0100 Subject: [PATCH 14/30] Add groups to mavenCentral white list --- build.gradle | 2 -- dependencies_groups.gradle | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 7ace92af9a..b7299b01f7 100644 --- a/build.gradle +++ b/build.gradle @@ -53,8 +53,6 @@ allprojects { groups.jitsi.group.each { includeGroup it } } } - // TODO. MapTiler - mavenCentral() google { content { groups.google.regex.each { includeGroupByRegex it } diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 3853919bcb..9cc5684255 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -84,6 +84,7 @@ ext.groups = [ 'com.jakewharton.android.repackaged', 'com.jakewharton.timber', 'com.linkedin.dexmaker', + 'com.mapbox.mapboxsdk', 'com.nulab-inc', 'com.otaliastudios.opengl', 'com.parse.bolts', @@ -159,6 +160,7 @@ ext.groups = [ 'org.junit.jupiter', 'org.junit.platform', 'org.jvnet.staxex', + 'org.maplibre.gl', 'org.matrix.android', 'org.mockito', 'org.mongodb', From 5581e0b5baa5dbc8119570989f0bd6c21e34690f Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 17 Jan 2022 18:30:04 +0300 Subject: [PATCH 15/30] Code review fixes. --- .../room/model/message/LocationInfo.kt | 2 +- .../BottomSheetMessagePreviewItem.kt | 7 ++- .../format/DisplayableEventFormatter.kt | 3 ++ .../timeline/helper/LocationPinProvider.kt | 6 +-- .../timeline/item/MessageLocationItem.kt | 15 +++--- .../im/vector/app/features/location/Config.kt | 21 ++++++++ .../app/features/location/LocationData.kt | 13 ----- .../location/LocationPreviewFragment.kt | 16 +++---- .../location/LocationSharingAction.kt | 1 + .../location/LocationSharingFragment.kt | 28 +++++++---- .../location/LocationSharingViewModel.kt | 12 +++-- .../app/features/location/LocationTracker.kt | 20 ++++---- .../app/features/location/MapTilerMapView.kt | 4 +- .../app/features/location/VectorMapView.kt | 6 +-- .../features/settings/VectorPreferences.kt | 2 +- .../item_bottom_sheet_message_preview.xml | 2 +- .../item_timeline_event_location_stub.xml | 48 +++++++++++-------- vector/src/main/res/values/strings.xml | 5 +- .../res/xml/vector_settings_preferences.xml | 2 +- 19 files changed, 123 insertions(+), 90 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/location/Config.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt index 5110926294..a1fd3bd2ec 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt @@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class LocationInfo( /** - * Required. Required. RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' representing this location. + * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' representing this location. */ @Json(name = "uri") val geoUri: String? = null, diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt index b5ac82e4e5..beb52d3082 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt @@ -36,7 +36,6 @@ import im.vector.app.features.home.room.detail.timeline.item.BindingOptions import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess import im.vector.app.features.location.LocationData import im.vector.app.features.location.MapTilerMapView -import im.vector.app.features.location.VectorMapListener import im.vector.app.features.media.ImageContentRenderer import org.matrix.android.sdk.api.util.MatrixItem @@ -101,15 +100,15 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel - holder.mapView.initialize(object : VectorMapListener { - override fun onMapReady() { + holder.mapView.initialize { + if (holder.view.isAttachedToWindow) { holder.mapView.zoomToLocation(location.latitude, location.longitude, 15.0) locationPinProvider?.create(matrixItem.id) { pinDrawable -> holder.mapView.addPinToMap(matrixItem.id, pinDrawable) holder.mapView.updatePinLocation(matrixItem.id, location.latitude, location.longitude) } } - }) + } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt index 3892bfff85..d5f3a74e4e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt @@ -89,6 +89,9 @@ class DisplayableEventFormatter @Inject constructor( MessageType.MSGTYPE_FILE -> { simpleFormat(senderName, stringProvider.getString(R.string.sent_a_file), appendAuthor) } + MessageType.MSGTYPE_LOCATION -> { + simpleFormat(senderName, stringProvider.getString(R.string.sent_location), appendAuthor) + } else -> { simpleFormat(senderName, messageContent.body, appendAuthor) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt index 92a4bb8473..2820f07b34 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt @@ -23,10 +23,10 @@ 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.di.ActiveSessionHolder 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 @@ -34,7 +34,7 @@ import javax.inject.Singleton @Singleton class LocationPinProvider @Inject constructor( private val context: Context, - private val session: Session, + private val activeSessionHolder: ActiveSessionHolder, private val avatarRenderer: AvatarRenderer ) { private val cache = mutableMapOf() @@ -49,7 +49,7 @@ class LocationPinProvider @Inject constructor( return } - session.getUser(userId)?.toMatrixItem()?.let { + activeSessionHolder.getActiveSession().getUser(userId)?.toMatrixItem()?.let { val size = Utils.dpToPx(44, context) avatarRenderer.render(glideRequests, it, object : CustomTarget(size, size) { override fun onResourceReady(resource: Drawable, transition: Transition?) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt index d8ccf3761e..3f030866a5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLocationItem.kt @@ -25,7 +25,6 @@ import im.vector.app.core.epoxy.onClick 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() { @@ -58,16 +57,14 @@ abstract class MessageLocationItem : AbsMessageItem( } holder.mapView.apply { - initialize(object : VectorMapListener { - override fun onMapReady() { - zoomToLocation(location.latitude, location.longitude, INITIAL_ZOOM) + initialize { + zoomToLocation(location.latitude, location.longitude, INITIAL_ZOOM) - locationPinProvider?.create(locationOwnerId) { pinDrawable -> - addPinToMap(locationOwnerId, pinDrawable) - updatePinLocation(locationOwnerId, location.latitude, location.longitude) - } + locationPinProvider?.create(locationOwnerId) { pinDrawable -> + addPinToMap(locationOwnerId, pinDrawable) + updatePinLocation(locationOwnerId, location.latitude, location.longitude) } - }) + } } } diff --git a/vector/src/main/java/im/vector/app/features/location/Config.kt b/vector/src/main/java/im/vector/app/features/location/Config.kt new file mode 100644 index 0000000000..630df16a37 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/Config.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022 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 + +const val INITIAL_MAP_ZOOM = 15.0 +const val MIN_TIME_MILLIS_TO_UPDATE_LOCATION = 1 * 60 * 1000L // every 1 minute +const val MIN_DISTANCE_METERS_TO_UPDATE_LOCATION = 10f diff --git a/vector/src/main/java/im/vector/app/features/location/LocationData.kt b/vector/src/main/java/im/vector/app/features/location/LocationData.kt index b790bde710..3233e8f721 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationData.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationData.kt @@ -26,19 +26,6 @@ data class LocationData( 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 { /** diff --git a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt index 1a7ed49209..f3914fbc2c 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt @@ -31,7 +31,7 @@ import javax.inject.Inject class LocationPreviewFragment @Inject constructor( private val locationPinProvider: LocationPinProvider -) : VectorBaseFragment(), VectorMapListener { +) : VectorBaseFragment() { private val args: LocationSharingArgs by args() @@ -42,7 +42,11 @@ class LocationPreviewFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - views.mapView.initialize(this) + views.mapView.initialize { + if (isAdded) { + onMapReady() + } + } } override fun getMenuRes() = R.menu.menu_location_preview @@ -62,21 +66,17 @@ class LocationPreviewFragment @Inject constructor( openLocation(requireActivity(), location.latitude, location.longitude) } - override fun onMapReady() { + private 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) + zoomToLocation(location.latitude, location.longitude, INITIAL_MAP_ZOOM) deleteAllPins() addPinToMap(userId, pinDrawable) updatePinLocation(userId, location.latitude, location.longitude) } } } - - companion object { - const val INITIAL_ZOOM = 15.0 - } } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt index 0efaefaa5b..71101d0612 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt @@ -21,4 +21,5 @@ import im.vector.app.core.platform.VectorViewModelAction sealed class LocationSharingAction : VectorViewModelAction { data class OnLocationUpdate(val locationData: LocationData) : LocationSharingAction() object OnShareLocation : LocationSharingAction() + object OnLocationProviderIsNotAvailable : LocationSharingAction() } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt index c7c4c0fed1..d3ccbf914e 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -20,9 +20,10 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import com.airbnb.mvrx.activityViewModel +import com.airbnb.mvrx.fragmentViewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R +import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentLocationSharingBinding import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider @@ -33,13 +34,13 @@ class LocationSharingFragment @Inject constructor( private val locationTracker: LocationTracker, private val session: Session, private val locationPinProvider: LocationPinProvider -) : VectorBaseFragment(), LocationTracker.Callback, VectorMapListener { +) : VectorBaseFragment(), LocationTracker.Callback { init { locationTracker.callback = this } - private val viewModel: LocationSharingViewModel by activityViewModel() + private val viewModel: LocationSharingViewModel by fragmentViewModel() private var lastZoomValue: Double = -1.0 @@ -50,7 +51,11 @@ class LocationSharingFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - views.mapView.initialize(this) + views.mapView.initialize { + if (isAdded) { + onMapReady() + } + } views.shareLocationContainer.debouncedClicks { viewModel.handle(LocationSharingAction.OnShareLocation) @@ -60,7 +65,7 @@ class LocationSharingFragment @Inject constructor( when (it) { LocationSharingViewEvents.LocationNotAvailableError -> handleLocationNotAvailableError() LocationSharingViewEvents.Close -> activity?.finish() - } + }.exhaustive } } @@ -69,7 +74,7 @@ class LocationSharingFragment @Inject constructor( locationTracker.stop() } - override fun onMapReady() { + private fun onMapReady() { locationPinProvider.create(session.myUserId) { views.mapView.addPinToMap( pinId = USER_PIN_NAME, @@ -81,7 +86,7 @@ class LocationSharingFragment @Inject constructor( } override fun onLocationUpdate(locationData: LocationData) { - lastZoomValue = if (lastZoomValue == -1.0) INITIAL_ZOOM else views.mapView.getCurrentZoom() ?: INITIAL_ZOOM + lastZoomValue = if (lastZoomValue == -1.0) INITIAL_MAP_ZOOM else views.mapView.getCurrentZoom() ?: INITIAL_MAP_ZOOM views.mapView.zoomToLocation(locationData.latitude, locationData.longitude, lastZoomValue) views.mapView.deleteAllPins() @@ -90,16 +95,21 @@ class LocationSharingFragment @Inject constructor( viewModel.handle(LocationSharingAction.OnLocationUpdate(locationData)) } + override fun onLocationProviderIsNotAvailable() { + viewModel.handle(LocationSharingAction.OnLocationProviderIsNotAvailable) + } + private fun handleLocationNotAvailableError() { MaterialAlertDialogBuilder(requireActivity()) .setTitle(R.string.location_not_available_dialog_title) .setMessage(R.string.location_not_available_dialog_content) - .setPositiveButton(R.string.ok, null) + .setPositiveButton(R.string.ok) { _, _ -> + activity?.finish() + } .show() } companion object { - const val INITIAL_ZOOM = 15.0 const val USER_PIN_NAME = "USER_PIN_NAME" } } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index 4525cac446..b3c97310e1 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -22,6 +22,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import org.matrix.android.sdk.api.session.Session @@ -42,9 +43,10 @@ class LocationSharingViewModel @AssistedInject constructor( override fun handle(action: LocationSharingAction) { when (action) { - is LocationSharingAction.OnLocationUpdate -> handleLocationUpdate(action.locationData) - LocationSharingAction.OnShareLocation -> handleShareLocation() - } + is LocationSharingAction.OnLocationUpdate -> handleLocationUpdate(action.locationData) + LocationSharingAction.OnShareLocation -> handleShareLocation() + LocationSharingAction.OnLocationProviderIsNotAvailable -> handleLocationProviderIsNotAvailable() + }.exhaustive } private fun handleShareLocation() = withState { state -> @@ -65,4 +67,8 @@ class LocationSharingViewModel @AssistedInject constructor( copy(lastKnownLocation = locationData) } } + + private fun handleLocationProviderIsNotAvailable() { + _viewEvents.post(LocationSharingViewEvents.LocationNotAvailableError) + } } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt index a669381981..0c0315cf34 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt @@ -27,15 +27,18 @@ import timber.log.Timber import javax.inject.Inject class LocationTracker @Inject constructor( - private val context: Context) : LocationListener { + private val context: Context +) : LocationListener { interface Callback { fun onLocationUpdate(locationData: LocationData) + fun onLocationProviderIsNotAvailable() } private var locationManager: LocationManager? = null var callback: Callback? = null + @RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION]) fun start() { val locationManager = context.getSystemService() @@ -47,6 +50,7 @@ class LocationTracker @Inject constructor( isGpsEnabled -> LocationManager.GPS_PROVIDER isNetworkEnabled -> LocationManager.NETWORK_PROVIDER else -> { + callback?.onLocationProviderIsNotAvailable() Timber.v("## LocationTracker. There is no location provider available") return } @@ -54,16 +58,17 @@ class LocationTracker @Inject constructor( // Send last known location without waiting location updates it.getLastKnownLocation(provider)?.let { lastKnownLocation -> - callback?.onLocationUpdate(LocationData(lastKnownLocation.latitude, lastKnownLocation.longitude, lastKnownLocation.accuracy.toDouble())) + callback?.onLocationUpdate(lastKnownLocation.toLocationData()) } it.requestLocationUpdates( provider, - MIN_TIME_MILLIS_TO_UPDATE, - MIN_DISTANCE_METERS_TO_UPDATE, + MIN_TIME_MILLIS_TO_UPDATE_LOCATION, + MIN_DISTANCE_METERS_TO_UPDATE_LOCATION, this ) } ?: run { + callback?.onLocationProviderIsNotAvailable() Timber.v("## LocationTracker. LocationManager is not available") } } @@ -75,11 +80,10 @@ class LocationTracker @Inject constructor( } override fun onLocationChanged(location: Location) { - callback?.onLocationUpdate(LocationData(location.latitude, location.longitude, location.accuracy.toDouble())) + callback?.onLocationUpdate(location.toLocationData()) } - companion object { - const val MIN_TIME_MILLIS_TO_UPDATE = 1 * 60 * 1000L // every 1 minute - const val MIN_DISTANCE_METERS_TO_UPDATE = 10f + private fun Location.toLocationData(): LocationData { + return LocationData(latitude, longitude, accuracy.toDouble()) } } diff --git a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt index 386e96988f..c64af1ebaa 100644 --- a/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt +++ b/vector/src/main/java/im/vector/app/features/location/MapTilerMapView.kt @@ -39,13 +39,13 @@ class MapTilerMapView @JvmOverloads constructor( private var symbolManager: SymbolManager? = null private var style: Style? = null - override fun initialize(listener: VectorMapListener) { + override fun initialize(onMapReady: () -> Unit) { getMapAsync { map -> map.setStyle(styleUrl) { style -> this.symbolManager = SymbolManager(this, map, style) this.map = map this.style = style - listener.onMapReady() + onMapReady() } } } diff --git a/vector/src/main/java/im/vector/app/features/location/VectorMapView.kt b/vector/src/main/java/im/vector/app/features/location/VectorMapView.kt index 5dbeced175..23b59bf99a 100644 --- a/vector/src/main/java/im/vector/app/features/location/VectorMapView.kt +++ b/vector/src/main/java/im/vector/app/features/location/VectorMapView.kt @@ -18,12 +18,8 @@ package im.vector.app.features.location import android.graphics.drawable.Drawable -interface VectorMapListener { - fun onMapReady() -} - interface VectorMapView { - fun initialize(listener: VectorMapListener) + fun initialize(onMapReady: () -> Unit) fun addPinToMap(pinId: String, image: Drawable) fun updatePinLocation(pinId: String, latitude: Double, longitude: Double) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 4db99c9a74..67d26231b2 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -1000,6 +1000,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { } fun isLocationSharingEnabled(): Boolean { - return defaultPrefs.getBoolean(SETTINGS_PREF_ENABLE_LOCATION_SHARING, true) + return defaultPrefs.getBoolean(SETTINGS_PREF_ENABLE_LOCATION_SHARING, false) } } diff --git a/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml b/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml index 1959cfc3ba..0b61ae2548 100644 --- a/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml +++ b/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml @@ -108,7 +108,7 @@ android:layout_width="0dp" android:layout_height="200dp" android:layout_marginTop="6dp" - android:importantForAccessibility="no" + android:contentDescription="@string/attachment_type_location" android:scaleType="centerCrop" android:visibility="gone" app:layout_constraintEnd_toEndOf="@id/bottom_sheet_message_preview_timestamp" diff --git a/vector/src/main/res/layout/item_timeline_event_location_stub.xml b/vector/src/main/res/layout/item_timeline_event_location_stub.xml index 0b211ec5e0..d989ad72af 100644 --- a/vector/src/main/res/layout/item_timeline_event_location_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_location_stub.xml @@ -1,26 +1,34 @@ - + android:layout_height="wrap_content" + app:cardCornerRadius="8dp"> - + - + - + + + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 78f6c9209a..2720b3c20b 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2755,6 +2755,7 @@ Poll Reacted with: %s Verification Conclusion + Shared their location Waiting… %s cancelled @@ -3704,8 +3705,8 @@ Location Share location Share location - Element could not access your location - Element could not access your location. Please try again later. + ${app_name} could not access your location + ${app_name} could not access your location. Please try again later. Open with Enable location sharing diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml index bc9e06f754..fd5c6f6062 100644 --- a/vector/src/main/res/xml/vector_settings_preferences.xml +++ b/vector/src/main/res/xml/vector_settings_preferences.xml @@ -29,7 +29,7 @@ android:title="@string/font_size" /> From 42dcf52eb05d57d26c1b6d2e5e65658c8038b4a2 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 18 Jan 2022 12:34:02 +0300 Subject: [PATCH 16/30] Use official maptiler key. --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index 65e3e0e9f0..a6085e0ae0 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -150,7 +150,7 @@ android { // This *must* only be set in trusted environments. buildConfigField "Boolean", "handleCallAssertedIdentityEvents", "false" - buildConfigField "String", "mapTilerKey", "\"KF6tcY7YuKFgJSZ1EFQ2\"" + buildConfigField "String", "mapTilerKey", "\"fU3vlMsMn4Jb6dnEIFsx\"" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" From ede1fe39fcbe94d22c10e21f92db18454b592026 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 18 Jan 2022 13:41:40 +0300 Subject: [PATCH 17/30] Disable location sharing from build settings. --- vector/build.gradle | 1 + .../java/im/vector/app/features/settings/VectorPreferences.kt | 4 ++-- .../features/settings/VectorSettingsPreferencesFragment.kt | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/vector/build.gradle b/vector/build.gradle index a6085e0ae0..c5c45f5ef3 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -150,6 +150,7 @@ android { // This *must* only be set in trusted environments. buildConfigField "Boolean", "handleCallAssertedIdentityEvents", "false" + buildConfigField "Boolean", "enableLocationSharing", "false" buildConfigField "String", "mapTilerKey", "\"fU3vlMsMn4Jb6dnEIFsx\"" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 67d26231b2..859c5ce678 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -188,7 +188,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { private const val DID_PROMOTE_NEW_RESTRICTED_JOIN_RULE = "DID_PROMOTE_NEW_RESTRICTED_JOIN_RULE" // Location Sharing - private const val SETTINGS_PREF_ENABLE_LOCATION_SHARING = "SETTINGS_PREF_ENABLE_LOCATION_SHARING" + const val SETTINGS_PREF_ENABLE_LOCATION_SHARING = "SETTINGS_PREF_ENABLE_LOCATION_SHARING" private const val MEDIA_SAVING_3_DAYS = 0 private const val MEDIA_SAVING_1_WEEK = 1 @@ -1000,6 +1000,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { } fun isLocationSharingEnabled(): Boolean { - return defaultPrefs.getBoolean(SETTINGS_PREF_ENABLE_LOCATION_SHARING, false) + return defaultPrefs.getBoolean(SETTINGS_PREF_ENABLE_LOCATION_SHARING, false) && BuildConfig.enableLocationSharing } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt index 2e2fab06a3..50e32ae453 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt @@ -22,6 +22,7 @@ import android.widget.CheckedTextView import androidx.core.view.children import androidx.preference.Preference import com.google.android.material.dialog.MaterialAlertDialogBuilder +import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.dialogs.PhotoOrVideoDialog import im.vector.app.core.extensions.restart @@ -149,6 +150,8 @@ class VectorSettingsPreferencesFragment @Inject constructor( }) true } + + findPreference(VectorPreferences.SETTINGS_PREF_ENABLE_LOCATION_SHARING)?.isVisible = BuildConfig.enableLocationSharing } private fun updateTakePhotoOrVideoPreferenceSummary() { From 7993ff39a5430e9bb8880b19929db36b97456f76 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 18 Jan 2022 15:33:48 +0300 Subject: [PATCH 18/30] Add missing fields to location event content. --- .../room/model/message/LocationAsset.kt | 25 ++++++++++++++++++ .../room/model/message/LocationAssetType.kt | 26 +++++++++++++++++++ .../model/message/MessageLocationContent.kt | 16 +++++++++++- .../room/send/LocalEchoEventFactory.kt | 10 ++++++- 4 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAsset.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAssetType.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAsset.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAsset.kt new file mode 100644 index 0000000000..e8b3cf2488 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAsset.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class LocationAsset( + @Json(name = "type") val type: LocationAssetType? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAssetType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAssetType.kt new file mode 100644 index 0000000000..ef40e21c47 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAssetType.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = false) +enum class LocationAssetType { + @Json(name = "m.self") + SELF +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt index 9571cc4280..00720b9ae5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt @@ -45,5 +45,19 @@ data class MessageLocationContent( @Json(name = "org.matrix.msc3488.location") val locationInfo: LocationInfo? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, - @Json(name = "m.new_content") override val newContent: Content? = null + @Json(name = "m.new_content") override val newContent: Content? = null, + + /** + * m.asset defines a generic asset that can be used for location tracking but also in other places like inventories, geofencing, checkins/checkouts etc. + * It should contain a mandatory namespaced type key defining what particular asset is being referred to. + * For the purposes of user location tracking m.self should be used in order to avoid duplicating the mxid. + */ + @Json(name = "m.asset") val locationAsset: LocationAsset? = null, + + /** + * Exact time that the data in the event refers to (milliseconds since the UNIX epoch) + */ + @Json(name = "org.matrix.msc3488.ts") val ts: Long? = null, + + @Json(name = "org.matrix.msc1767.text") val text: String? = null ) : MessageContent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index dc5e2a70f1..0cd33cd2d9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -32,6 +32,8 @@ import org.matrix.android.sdk.api.session.room.model.message.AudioInfo import org.matrix.android.sdk.api.session.room.model.message.AudioWaveformInfo import org.matrix.android.sdk.api.session.room.model.message.FileInfo import org.matrix.android.sdk.api.session.room.model.message.ImageInfo +import org.matrix.android.sdk.api.session.room.model.message.LocationAsset +import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType import org.matrix.android.sdk.api.session.room.model.message.LocationInfo import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent @@ -63,6 +65,7 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils +import java.util.concurrent.TimeUnit import javax.inject.Inject /** @@ -207,7 +210,12 @@ internal class LocalEchoEventFactory @Inject constructor( locationInfo = LocationInfo( geoUri = geoUri, description = geoUri - ) + ), + locationAsset = LocationAsset( + type = LocationAssetType.SELF + ), + ts = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()), + text = geoUri ) return createMessageEvent(roomId, content) } From 3994373387653c5df8ed058bb77dc1c01648f095 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 24 Jan 2022 15:27:20 +0300 Subject: [PATCH 19/30] Delabs location sharing. --- vector/build.gradle | 1 - .../java/im/vector/app/features/settings/VectorPreferences.kt | 2 +- .../app/features/settings/VectorSettingsPreferencesFragment.kt | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/vector/build.gradle b/vector/build.gradle index c5c45f5ef3..a6085e0ae0 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -150,7 +150,6 @@ android { // This *must* only be set in trusted environments. buildConfigField "Boolean", "handleCallAssertedIdentityEvents", "false" - buildConfigField "Boolean", "enableLocationSharing", "false" buildConfigField "String", "mapTilerKey", "\"fU3vlMsMn4Jb6dnEIFsx\"" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 859c5ce678..5d14983044 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -1000,6 +1000,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { } fun isLocationSharingEnabled(): Boolean { - return defaultPrefs.getBoolean(SETTINGS_PREF_ENABLE_LOCATION_SHARING, false) && BuildConfig.enableLocationSharing + return defaultPrefs.getBoolean(SETTINGS_PREF_ENABLE_LOCATION_SHARING, false) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt index 50e32ae453..0b0e27cca8 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt @@ -150,8 +150,6 @@ class VectorSettingsPreferencesFragment @Inject constructor( }) true } - - findPreference(VectorPreferences.SETTINGS_PREF_ENABLE_LOCATION_SHARING)?.isVisible = BuildConfig.enableLocationSharing } private fun updateTakePhotoOrVideoPreferenceSummary() { From c81654d769f107d20bbc066669cdf2d1e3d46d09 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 24 Jan 2022 16:07:11 +0300 Subject: [PATCH 20/30] Fix native crash (need more testing). --- .../im/vector/app/features/location/LocationData.kt | 2 +- .../app/features/location/LocationPreviewFragment.kt | 7 +++++++ .../app/features/location/LocationSharingFragment.kt | 11 +++++++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationData.kt b/vector/src/main/java/im/vector/app/features/location/LocationData.kt index 3233e8f721..c3ff09ebcd 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationData.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationData.kt @@ -43,7 +43,7 @@ data class LocationData( val latitude = geoParts?.firstOrNull() val geoTailParts = geoParts?.getOrNull(1)?.split(";") val longitude = geoTailParts?.firstOrNull() - val uncertainty = geoTailParts?.getOrNull(1) + val uncertainty = geoTailParts?.getOrNull(1)?.replace("u=", "") return if (latitude != null && longitude != null) { LocationData( diff --git a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt index f3914fbc2c..60c78ec520 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt @@ -49,6 +49,11 @@ class LocationPreviewFragment @Inject constructor( } } + override fun onPause() { + views.mapView.onPause() + super.onPause() + } + override fun getMenuRes() = R.menu.menu_location_preview override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -67,6 +72,8 @@ class LocationPreviewFragment @Inject constructor( } private fun onMapReady() { + if (!isAdded) return + val location = args.initialLocationData ?: return val userId = args.locationOwnerId diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt index d3ccbf914e..122ad7fbd6 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -69,12 +69,19 @@ class LocationSharingFragment @Inject constructor( } } - override fun onDestroyView() { - super.onDestroyView() + override fun onPause() { + views.mapView.onPause() + super.onPause() + } + + override fun onDestroy() { + super.onDestroy() locationTracker.stop() } private fun onMapReady() { + if (!isAdded) return + locationPinProvider.create(session.myUserId) { views.mapView.addPinToMap( pinId = USER_PIN_NAME, From 8bf39c3127af464d6c8cba8a1229e6ab9e6e0961 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 24 Jan 2022 17:12:17 +0300 Subject: [PATCH 21/30] Inform mapview when fragment is stopped. --- .../app/features/location/LocationPreviewFragment.kt | 5 +++++ .../app/features/location/LocationSharingFragment.kt | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt index 60c78ec520..6209bf5a4f 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationPreviewFragment.kt @@ -54,6 +54,11 @@ class LocationPreviewFragment @Inject constructor( super.onPause() } + override fun onStop() { + views.mapView.onStop() + super.onStop() + } + override fun getMenuRes() = R.menu.menu_location_preview override fun onOptionsItemSelected(item: MenuItem): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt index 122ad7fbd6..900f465f04 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -74,9 +74,14 @@ class LocationSharingFragment @Inject constructor( super.onPause() } + override fun onStop() { + views.mapView.onStop() + super.onStop() + } + override fun onDestroy() { - super.onDestroy() locationTracker.stop() + super.onDestroy() } private fun onMapReady() { From 12bfece89e54963ae67d609c71be455f22fea438 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 24 Jan 2022 17:36:26 +0300 Subject: [PATCH 22/30] Prevent anr in timeline. --- vector/src/main/res/layout/fragment_location_preview.xml | 4 +++- vector/src/main/res/layout/fragment_location_sharing.xml | 3 ++- .../src/main/res/layout/item_bottom_sheet_message_preview.xml | 1 + .../src/main/res/layout/item_timeline_event_location_stub.xml | 3 ++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/layout/fragment_location_preview.xml b/vector/src/main/res/layout/fragment_location_preview.xml index ae3e8f1778..c2b3bdd739 100644 --- a/vector/src/main/res/layout/fragment_location_preview.xml +++ b/vector/src/main/res/layout/fragment_location_preview.xml @@ -1,11 +1,13 @@ + android:layout_height="match_parent" + app:mapbox_renderTextureMode="true" /> \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_location_sharing.xml b/vector/src/main/res/layout/fragment_location_sharing.xml index 8122b5def5..b9f00786de 100644 --- a/vector/src/main/res/layout/fragment_location_sharing.xml +++ b/vector/src/main/res/layout/fragment_location_sharing.xml @@ -7,7 +7,8 @@ + android:layout_height="match_parent" + app:mapbox_renderTextureMode="true" /> diff --git a/vector/src/main/res/layout/item_timeline_event_location_stub.xml b/vector/src/main/res/layout/item_timeline_event_location_stub.xml index d989ad72af..ea49930763 100644 --- a/vector/src/main/res/layout/item_timeline_event_location_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_location_stub.xml @@ -18,7 +18,8 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent" + app:mapbox_renderTextureMode="true" /> Date: Mon, 24 Jan 2022 18:49:49 +0300 Subject: [PATCH 23/30] Keep build settings to enable location sharing for possible use cases of forks. --- vector/build.gradle | 1 + .../java/im/vector/app/features/settings/VectorPreferences.kt | 2 +- .../app/features/settings/VectorSettingsPreferencesFragment.kt | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index a6085e0ae0..73ec675be0 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -150,6 +150,7 @@ android { // This *must* only be set in trusted environments. buildConfigField "Boolean", "handleCallAssertedIdentityEvents", "false" + buildConfigField "Boolean", "enableLocationSharing", "true" buildConfigField "String", "mapTilerKey", "\"fU3vlMsMn4Jb6dnEIFsx\"" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 5d14983044..859c5ce678 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -1000,6 +1000,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { } fun isLocationSharingEnabled(): Boolean { - return defaultPrefs.getBoolean(SETTINGS_PREF_ENABLE_LOCATION_SHARING, false) + return defaultPrefs.getBoolean(SETTINGS_PREF_ENABLE_LOCATION_SHARING, false) && BuildConfig.enableLocationSharing } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt index 0b0e27cca8..50e32ae453 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt @@ -150,6 +150,8 @@ class VectorSettingsPreferencesFragment @Inject constructor( }) true } + + findPreference(VectorPreferences.SETTINGS_PREF_ENABLE_LOCATION_SHARING)?.isVisible = BuildConfig.enableLocationSharing } private fun updateTakePhotoOrVideoPreferenceSummary() { From d65899ef8864926f65d6d53a345b67ff4617cd23 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 24 Jan 2022 21:55:32 +0300 Subject: [PATCH 24/30] Fix sharing location action in timeline. --- .../model/message/MessageLocationContent.kt | 5 +++- .../home/room/detail/RoomDetailFragment.kt | 29 ++++++++++++------- .../action/MessageActionsEpoxyController.kt | 2 +- .../timeline/factory/MessageItemFactory.kt | 2 +- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt index 00720b9ae5..2f3db8ff51 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt @@ -60,4 +60,7 @@ data class MessageLocationContent( @Json(name = "org.matrix.msc3488.ts") val ts: Long? = null, @Json(name = "org.matrix.msc1767.text") val text: String? = null -) : MessageContent +) : MessageContent { + + fun getUri() = locationInfo?.geoUri ?: geoUri +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 128ac8f3e8..e0003a038c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -106,6 +106,7 @@ import im.vector.app.core.utils.createUIHandler import im.vector.app.core.utils.isValidUrl import im.vector.app.core.utils.onPermissionDeniedDialog import im.vector.app.core.utils.onPermissionDeniedSnackbar +import im.vector.app.core.utils.openLocation import im.vector.app.core.utils.openUrlInExternalBrowser import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.core.utils.safeStartActivity @@ -166,6 +167,7 @@ import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.PillImageSpan import im.vector.app.features.html.PillsPostProcessor import im.vector.app.features.invite.VectorInviteView +import im.vector.app.features.location.LocationData import im.vector.app.features.location.LocationSharingMode import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer @@ -207,6 +209,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageFormat 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.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent @@ -1912,16 +1915,22 @@ class RoomDetailFragment @Inject constructor( } private fun onShareActionClicked(action: EventSharedAction.Share) { - if (action.messageContent is MessageTextContent) { - shareText(requireContext(), action.messageContent.body) - } else if (action.messageContent is MessageWithAttachmentContent) { - lifecycleScope.launch { - val result = runCatching { session.fileService().downloadFile(messageContent = action.messageContent) } - if (!isAdded) return@launch - result.fold( - { shareMedia(requireContext(), it, getMimeTypeFromUri(requireContext(), it.toUri())) }, - { showErrorInSnackbar(it) } - ) + when (action.messageContent) { + is MessageTextContent -> shareText(requireContext(), action.messageContent.body) + is MessageLocationContent -> { + LocationData.create(action.messageContent.getUri())?.let { + openLocation(requireActivity(), it.latitude, it.longitude) + } + } + is MessageWithAttachmentContent -> { + lifecycleScope.launch { + val result = runCatching { session.fileService().downloadFile(messageContent = action.messageContent) } + if (!isAdded) return@launch + result.fold( + { shareMedia(requireContext(), it, getMimeTypeFromUri(requireContext(), it.toUri())) }, + { showErrorInSnackbar(it) } + ) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt index d86c4d793f..479459c4bc 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt @@ -75,7 +75,7 @@ class MessageActionsEpoxyController @Inject constructor( val body = state.messageBody.linkify(host.listener) val bindingOptions = spanUtils.getBindingOptions(body) val locationData = state.timelineEvent()?.root?.getClearContent()?.toModel(catchError = true)?.let { - LocationData.create(it.locationInfo?.geoUri ?: it.geoUri) + LocationData.create(it.getUri()) } bottomSheetMessagePreviewItem { id("preview") diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 4070532f80..2d045ec959 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -187,7 +187,7 @@ class MessageItemFactory @Inject constructor( highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes): MessageLocationItem? { - val geoUri = locationContent.locationInfo?.geoUri ?: locationContent.geoUri + val geoUri = locationContent.getUri() val locationData = LocationData.create(geoUri) val mapCallback: MessageLocationItem.Callback = object : MessageLocationItem.Callback { From 43c8624b33fe7c16f984900d472702e8767b50e6 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 25 Jan 2022 15:31:59 +0300 Subject: [PATCH 25/30] Code review fixes. --- .../detail/timeline/factory/MessageItemFactory.kt | 12 ++++++++++-- .../app/features/settings/VectorPreferences.kt | 6 ++++++ vector/src/main/res/values/strings.xml | 1 + vector/src/main/res/xml/vector_settings_labs.xml | 15 ++++++++++----- .../main/res/xml/vector_settings_preferences.xml | 10 +++++----- 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 2d045ec959..17eb076b09 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -75,6 +75,7 @@ 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 im.vector.app.features.settings.VectorPreferences import me.gujun.android.span.span import org.commonmark.node.Document import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl @@ -124,7 +125,8 @@ class MessageItemFactory @Inject constructor( private val spanUtils: SpanUtils, private val session: Session, private val voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker, - private val locationPinProvider: LocationPinProvider) { + private val locationPinProvider: LocationPinProvider, + private val vectorPreferences: VectorPreferences) { // TODO inject this properly? private var roomId: String = "" @@ -177,7 +179,13 @@ class MessageItemFactory @Inject constructor( } is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes) - is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, callback, attributes) + is MessageLocationContent -> { + if (vectorPreferences.labsRenderLocationsInTimeline()) { + buildLocationItem(messageContent, informationData, highlight, callback, attributes) + } else { + buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes) + } + } else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 859c5ce678..d1f96fe76c 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -201,6 +201,8 @@ class VectorPreferences @Inject constructor(private val context: Context) { private const val SETTINGS_LABS_ENABLE_POLLS = "SETTINGS_LABS_ENABLE_POLLS" + private const val SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE = "SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE" + // Possible values for TAKE_PHOTO_VIDEO_MODE const val TAKE_PHOTO_VIDEO_MODE_ALWAYS_ASK = 0 const val TAKE_PHOTO_VIDEO_MODE_PHOTO = 1 @@ -1002,4 +1004,8 @@ class VectorPreferences @Inject constructor(private val context: Context) { fun isLocationSharingEnabled(): Boolean { return defaultPrefs.getBoolean(SETTINGS_PREF_ENABLE_LOCATION_SHARING, false) && BuildConfig.enableLocationSharing } + + fun labsRenderLocationsInTimeline(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE, true) + } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 2720b3c20b..d9866bbdb3 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3709,6 +3709,7 @@ ${app_name} could not access your location. Please try again later. Open with Enable location sharing + Render user locations in the timeline Open camera Send images and videos diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml index d2e9df4985..ac9ce3099c 100644 --- a/vector/src/main/res/xml/vector_settings_labs.xml +++ b/vector/src/main/res/xml/vector_settings_labs.xml @@ -57,10 +57,15 @@ android:title="@string/labs_enable_polls" /> - + + + \ No newline at end of file diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml index fd5c6f6062..03bed63153 100644 --- a/vector/src/main/res/xml/vector_settings_preferences.xml +++ b/vector/src/main/res/xml/vector_settings_preferences.xml @@ -28,11 +28,6 @@ android:persistent="false" android:title="@string/font_size" /> - - @@ -77,6 +72,11 @@ android:title="@string/option_take_photo_video" tools:summary="@string/option_always_ask" /> + + From 460f39176ccc13ac509f72f6df850fc676d85ba6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 25 Jan 2022 14:36:32 +0100 Subject: [PATCH 26/30] Fix compilation issues after merge of develop --- .../detail/timeline/helper/LocationPinProvider.kt | 11 ++++++----- .../features/location/LocationSharingActivity.kt | 14 ++++---------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt index 2820f07b34..fe3a7d9007 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt @@ -25,8 +25,8 @@ import com.bumptech.glide.request.transition.Transition import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.glide.GlideApp +import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.home.AvatarRenderer -import org.billcarsonfr.jsonviewer.Utils import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject import javax.inject.Singleton @@ -35,6 +35,7 @@ import javax.inject.Singleton class LocationPinProvider @Inject constructor( private val context: Context, private val activeSessionHolder: ActiveSessionHolder, + private val dimensionConverter: DimensionConverter, private val avatarRenderer: AvatarRenderer ) { private val cache = mutableMapOf() @@ -50,14 +51,14 @@ class LocationPinProvider @Inject constructor( } activeSessionHolder.getActiveSession().getUser(userId)?.toMatrixItem()?.let { - val size = Utils.dpToPx(44, context) + val size = dimensionConverter.dpToPx(44) avatarRenderer.render(glideRequests, it, object : CustomTarget(size, size) { override fun onResourceReady(resource: Drawable, transition: Transition?) { 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) + val horizontalInset = dimensionConverter.dpToPx(4) + val topInset = dimensionConverter.dpToPx(4) + val bottomInset = dimensionConverter.dpToPx(8) layerDrawable.setLayerInset(1, horizontalInset, topInset, horizontalInset, bottomInset) cache[userId] = layerDrawable diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt index 6a200435dc..67b36b8442 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt @@ -19,10 +19,8 @@ package im.vector.app.features.location import android.content.Context import android.content.Intent import android.os.Parcelable -import com.google.android.material.appbar.MaterialToolbar import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragment -import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivityLocationSharingBinding import kotlinx.parcelize.Parcelize @@ -36,23 +34,19 @@ data class LocationSharingArgs( ) : Parcelable @AndroidEntryPoint -class LocationSharingActivity : VectorBaseActivity(), - ToolbarConfigurable { +class LocationSharingActivity : VectorBaseActivity() { override fun getBinding() = ActivityLocationSharingBinding.inflate(layoutInflater) - override fun configure(toolbar: MaterialToolbar) { - configureToolbar(toolbar) - } - override fun initUiAndData() { val locationSharingArgs: LocationSharingArgs? = intent?.extras?.getParcelable(EXTRA_LOCATION_SHARING_ARGS) if (locationSharingArgs == null) { finish() return } - configure(views.toolbar) - supportActionBar?.title = getString(locationSharingArgs.mode.titleRes) + setupToolbar(views.toolbar) + .setTitle(locationSharingArgs.mode.titleRes) + .allowBack() if (isFirstCreation()) { when (locationSharingArgs.mode) { From b728e062e33716256e78aaa77b3ceeebc74e656b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 25 Jan 2022 15:15:47 +0100 Subject: [PATCH 27/30] Cleanup layout --- vector/src/main/res/layout/activity_location_sharing.xml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vector/src/main/res/layout/activity_location_sharing.xml b/vector/src/main/res/layout/activity_location_sharing.xml index b278bb5a1a..bbb46de8c7 100755 --- a/vector/src/main/res/layout/activity_location_sharing.xml +++ b/vector/src/main/res/layout/activity_location_sharing.xml @@ -1,6 +1,5 @@ @@ -8,8 +7,7 @@ + android:layout_height="wrap_content"> Date: Tue, 25 Jan 2022 16:00:52 +0100 Subject: [PATCH 28/30] ktlint --- .../sdk/internal/session/room/send/LocalEchoEventFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 2582a0a03d..1e46602411 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -66,8 +66,8 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils -import java.util.concurrent.TimeUnit import java.util.UUID +import java.util.concurrent.TimeUnit import javax.inject.Inject /** From 823ece9582cf2f0ae2b0120dcf74e6c627e2cc74 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 25 Jan 2022 17:51:17 +0100 Subject: [PATCH 29/30] Add licence mention as per https://github.com/maplibre/maplibre-gl-native/blob/master/LICENSE.md --- .../src/main/assets/open_source_licenses.html | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/vector/src/main/assets/open_source_licenses.html b/vector/src/main/assets/open_source_licenses.html index 0eefa3b863..2c25606f57 100755 --- a/vector/src/main/assets/open_source_licenses.html +++ b/vector/src/main/assets/open_source_licenses.html @@ -254,6 +254,33 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +
    +
  • + org.maplibre.gl:android-sdk +
    + org.maplibre.gl:android-plugin-annotation-v9 +
    + BSD 2-Clause License + + Copyright (c) 2021 MapLibre contributors + + Copyright (c) 2018-2021 MapTiler.com + + Copyright (c) 2014-2020 Mapbox +
  • +
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
  • textdrawable From f83c2b32bc703a50715278ac8dc27ec5cdfe3bfc Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 25 Jan 2022 17:59:22 +0100 Subject: [PATCH 30/30] Add summary to location sharing setting --- vector/src/main/res/values/strings.xml | 1 + vector/src/main/res/xml/vector_settings_preferences.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 73822f416b..c4f6abd4ac 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3732,6 +3732,7 @@ ${app_name} could not access your location. Please try again later. Open with Enable location sharing + Once enabled you will be able to send your location to any room Render user locations in the timeline Open camera diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml index 03bed63153..db0830bca0 100644 --- a/vector/src/main/res/xml/vector_settings_preferences.xml +++ b/vector/src/main/res/xml/vector_settings_preferences.xml @@ -75,6 +75,7 @@