().apply {
+ listOf("turn:turn.matrix.org:3478?transport=udp", "turn:turn.matrix.org:3478?transport=tcp", "turns:turn.matrix.org:443?transport=tcp").forEach {
+ add(
+ PeerConnection.IceServer.builder(it)
+ .setUsername("xxxxx")
+ .setPassword("xxxxx")
+ .createIceServer()
+ )
+ }
+ }
+
+ peerConnectionManager.createPeerConnection(videoCapturer, iceServers)
+ //peerConnectionManager.startCall()
+ */
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/call/telecom/TelecomUtils.kt b/vector/src/main/java/im/vector/riotx/features/call/telecom/TelecomUtils.kt
new file mode 100644
index 0000000000..61bb8980bf
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/call/telecom/TelecomUtils.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020 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.riotx.features.call.telecom
+
+import android.content.Context
+import android.telephony.TelephonyManager
+
+object TelecomUtils {
+
+ fun isLineBusy(context: Context): Boolean {
+ val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as? TelephonyManager
+ ?: return false
+ return telephonyManager.callState != TelephonyManager.CALL_STATE_IDLE
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/call/telecom/VectorConnectionService.kt b/vector/src/main/java/im/vector/riotx/features/call/telecom/VectorConnectionService.kt
new file mode 100644
index 0000000000..8185c9fc49
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/call/telecom/VectorConnectionService.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2020 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.riotx.features.call.telecom
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.ServiceConnection
+import android.net.Uri
+import android.os.Build
+import android.os.IBinder
+import android.telecom.Connection
+import android.telecom.ConnectionRequest
+import android.telecom.ConnectionService
+import android.telecom.PhoneAccountHandle
+import android.telecom.StatusHints
+import android.telecom.TelecomManager
+import androidx.annotation.RequiresApi
+import im.vector.riotx.core.services.CallService
+
+/**
+ * No active calls in other apps
+ *
+ *To answer incoming calls when there are no active calls in other apps, follow these steps:
+ *
+ *
+ * * Your app receives a new incoming call using its usual mechanisms.
+ * - Use the addNewIncomingCall(PhoneAccountHandle, Bundle) method to inform the telecom subsystem about the new incoming call.
+ * - The telecom subsystem binds to your app's ConnectionService implementation and requests a new instance of the
+ * Connection class representing the new incoming call using the onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest) method.
+ * - The telecom subsystem informs your app that it should show its incoming call user interface using the onShowIncomingCallUi() method.
+ * - Your app shows its incoming UI using a notification with an associated full-screen intent. For more information, see onShowIncomingCallUi().
+ * - Call the setActive() method if the user accepts the incoming call, or setDisconnected(DisconnectCause) specifying REJECTED as
+ * the parameter followed by a call to the destroy() method if the user rejects the incoming call.
+ *
+ */
+@RequiresApi(Build.VERSION_CODES.M) class VectorConnectionService : ConnectionService() {
+
+ /**
+ * The telecom subsystem calls this method in response to your app calling placeCall(Uri, Bundle) to create a new outgoing call
+ */
+ override fun onCreateOutgoingConnection(connectionManagerPhoneAccount: PhoneAccountHandle?, request: ConnectionRequest?): Connection? {
+ val callId = request?.address?.encodedQuery ?: return null
+ val roomId = request.extras.getString("MX_CALL_ROOM_ID") ?: return null
+ return CallConnection(applicationContext, roomId, callId)
+ }
+
+ override fun onCreateIncomingConnection(connectionManagerPhoneAccount: PhoneAccountHandle?, request: ConnectionRequest?): Connection {
+ val roomId = request?.extras?.getString("MX_CALL_ROOM_ID") ?: return super.onCreateIncomingConnection(connectionManagerPhoneAccount, request)
+ val callId = request.extras.getString("MX_CALL_CALL_ID") ?: return super.onCreateIncomingConnection(connectionManagerPhoneAccount, request)
+
+ val connection = CallConnection(applicationContext, roomId, callId)
+ connection.connectionCapabilities = Connection.CAPABILITY_MUTE
+ connection.audioModeIsVoip = true
+ connection.setAddress(Uri.fromParts("tel", "+905000000000", null), TelecomManager.PRESENTATION_ALLOWED)
+ connection.setCallerDisplayName("RiotX Caller", TelecomManager.PRESENTATION_ALLOWED)
+ connection.statusHints = StatusHints("Testing Hint...", null, null)
+
+ bindService(Intent(applicationContext, CallService::class.java), CallServiceConnection(connection), 0)
+ connection.setInitializing()
+ return CallConnection(applicationContext, roomId, callId)
+ }
+
+ inner class CallServiceConnection(private val callConnection: CallConnection) : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
+ val callSrvBinder = binder as CallService.CallServiceBinder
+ callSrvBinder.getCallService().addConnection(callConnection)
+ unbindService(this)
+ }
+
+ override fun onServiceDisconnected(name: ComponentName?) {
+ }
+ }
+
+ companion object {
+ const val TAG = "TComService"
+ }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt
index 4e5d37af6c..c92c28079f 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt
@@ -37,7 +37,12 @@ import im.vector.riotx.core.glide.GlideApp
import im.vector.riotx.core.platform.ToolbarConfigurable
import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.core.platform.VectorBaseFragment
+import im.vector.riotx.core.ui.views.ActiveCallView
+import im.vector.riotx.core.ui.views.ActiveCallViewHolder
import im.vector.riotx.core.ui.views.KeysBackupBanner
+import im.vector.riotx.features.call.SharedActiveCallViewModel
+import im.vector.riotx.features.call.VectorCallActivity
+import im.vector.riotx.features.call.WebRtcPeerConnectionManager
import im.vector.riotx.features.home.room.list.RoomListFragment
import im.vector.riotx.features.home.room.list.RoomListParams
import im.vector.riotx.features.home.room.list.UnreadCounterBadgeView
@@ -46,6 +51,11 @@ import im.vector.riotx.features.popup.VerificationVectorAlert
import im.vector.riotx.features.settings.VectorSettingsActivity.Companion.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS
import im.vector.riotx.features.workers.signout.SignOutViewModel
import kotlinx.android.synthetic.main.fragment_home_detail.*
+import kotlinx.android.synthetic.main.fragment_home_detail.activeCallPiP
+import kotlinx.android.synthetic.main.fragment_home_detail.activeCallPiPWrap
+import kotlinx.android.synthetic.main.fragment_home_detail.activeCallView
+import kotlinx.android.synthetic.main.fragment_home_detail.syncStateView
+import kotlinx.android.synthetic.main.fragment_room_detail.*
import timber.log.Timber
import javax.inject.Inject
@@ -56,8 +66,9 @@ private const val INDEX_ROOMS = 2
class HomeDetailFragment @Inject constructor(
val homeDetailViewModelFactory: HomeDetailViewModel.Factory,
private val avatarRenderer: AvatarRenderer,
- private val alertManager: PopupAlertManager
-) : VectorBaseFragment(), KeysBackupBanner.Delegate {
+ private val alertManager: PopupAlertManager,
+ private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager
+) : VectorBaseFragment(), KeysBackupBanner.Delegate, ActiveCallView.Callback {
private val unreadCounterBadgeViews = arrayListOf()
@@ -65,16 +76,21 @@ class HomeDetailFragment @Inject constructor(
private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel()
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
+ private lateinit var sharedCallActionViewModel: SharedActiveCallViewModel
override fun getLayoutResId() = R.layout.fragment_home_detail
+ private val activeCallViewHolder = ActiveCallViewHolder()
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java)
+ sharedCallActionViewModel = activityViewModelProvider.get(SharedActiveCallViewModel::class.java)
setupBottomNavigationView()
setupToolbar()
setupKeysBackupBanner()
+ setupActiveCallView()
withState(viewModel) {
// Update the navigation view if needed (for when we restore the tabs)
@@ -105,6 +121,13 @@ class HomeDetailFragment @Inject constructor(
}
}
}
+
+ sharedCallActionViewModel
+ .activeCall
+ .observe(viewLifecycleOwner, Observer {
+ activeCallViewHolder.updateCall(it, webRtcPeerConnectionManager)
+ invalidateOptionsMenu()
+ })
}
private fun promptForNewUnknownDevices(uid: String, state: UnknownDevicesState, newest: DeviceInfo) {
@@ -203,6 +226,15 @@ class HomeDetailFragment @Inject constructor(
homeKeysBackupBanner.delegate = this
}
+ private fun setupActiveCallView() {
+ activeCallViewHolder.bind(
+ activeCallPiP,
+ activeCallView,
+ activeCallPiPWrap,
+ this
+ )
+ }
+
private fun setupToolbar() {
val parentActivity = vectorBaseActivity
if (parentActivity is ToolbarConfigurable) {
@@ -283,4 +315,20 @@ class HomeDetailFragment @Inject constructor(
RoomListDisplayMode.ROOMS -> R.id.bottom_action_rooms
else -> R.id.bottom_action_home
}
+
+ override fun onTapToReturnToCall() {
+ sharedCallActionViewModel.activeCall.value?.let { call ->
+ VectorCallActivity.newIntent(
+ context = requireContext(),
+ callId = call.callId,
+ roomId = call.roomId,
+ otherUserId = call.otherUserId,
+ isIncomingCall = !call.isOutgoing,
+ isVideoCall = call.isVideoCall,
+ mode = null
+ ).let {
+ startActivity(it)
+ }
+ }
+ }
}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt
index fba4f9e79e..2cca3c013d 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt
@@ -68,6 +68,8 @@ sealed class RoomDetailAction : VectorViewModelAction {
object ClearSendQueue : RoomDetailAction()
object ResendAll : RoomDetailAction()
+ data class StartCall(val isVideo: Boolean) : RoomDetailAction()
+ object EndCall : RoomDetailAction()
data class AcceptVerificationRequest(val transactionId: String, val otherUserId: String) : RoomDetailAction()
data class DeclineVerificationRequest(val transactionId: String, val otherUserId: String) : RoomDetailAction()
@@ -76,4 +78,5 @@ sealed class RoomDetailAction : VectorViewModelAction {
data class ReRequestKeys(val eventId: String) : RoomDetailAction()
object SelectStickerAttachment : RoomDetailAction()
+ object OpenIntegrationManager: RoomDetailAction()
}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
index 0ce99ce1dd..4995c16bf9 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
@@ -42,6 +42,7 @@ import androidx.core.util.Pair
import androidx.core.view.ViewCompat
import androidx.core.view.forEach
import androidx.core.view.isVisible
+import androidx.lifecycle.Observer
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -100,10 +101,14 @@ import im.vector.riotx.core.glide.GlideApp
import im.vector.riotx.core.intent.getMimeTypeFromUri
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.resources.ColorProvider
+import im.vector.riotx.core.ui.views.ActiveCallView
+import im.vector.riotx.core.ui.views.ActiveCallViewHolder
import im.vector.riotx.core.ui.views.JumpToReadMarkerView
import im.vector.riotx.core.ui.views.NotificationAreaView
import im.vector.riotx.core.utils.Debouncer
import im.vector.riotx.core.utils.KeyboardStateUtils
+import im.vector.riotx.core.utils.PERMISSIONS_FOR_AUDIO_IP_CALL
+import im.vector.riotx.core.utils.PERMISSIONS_FOR_VIDEO_IP_CALL
import im.vector.riotx.core.utils.PERMISSIONS_FOR_WRITING_FILES
import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_DOWNLOAD_FILE
import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_INCOMING_URI
@@ -117,6 +122,8 @@ import im.vector.riotx.core.utils.createJSonViewerStyleProvider
import im.vector.riotx.core.utils.createUIHandler
import im.vector.riotx.core.utils.getColorFromUserId
import im.vector.riotx.core.utils.isValidUrl
+import im.vector.riotx.core.utils.onPermissionResultAudioIpCall
+import im.vector.riotx.core.utils.onPermissionResultVideoIpCall
import im.vector.riotx.core.utils.openUrlInExternalBrowser
import im.vector.riotx.core.utils.saveMedia
import im.vector.riotx.core.utils.shareMedia
@@ -127,6 +134,9 @@ import im.vector.riotx.features.attachments.ContactAttachment
import im.vector.riotx.features.attachments.preview.AttachmentsPreviewActivity
import im.vector.riotx.features.attachments.preview.AttachmentsPreviewArgs
import im.vector.riotx.features.attachments.toGroupedContentAttachmentData
+import im.vector.riotx.features.call.SharedActiveCallViewModel
+import im.vector.riotx.features.call.VectorCallActivity
+import im.vector.riotx.features.call.WebRtcPeerConnectionManager
import im.vector.riotx.features.command.Command
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
import im.vector.riotx.features.crypto.util.toImageRes
@@ -134,7 +144,6 @@ import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.composer.TextComposerView
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
-import im.vector.riotx.features.home.room.detail.sticker.StickerPickerConstants
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotx.features.home.room.detail.timeline.action.EventSharedAction
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
@@ -149,6 +158,7 @@ import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
import im.vector.riotx.features.home.room.detail.widget.RoomWidgetsBannerView
import im.vector.riotx.features.home.room.detail.widget.RoomWidgetsBottomSheet
+import im.vector.riotx.features.home.room.detail.widget.WidgetRequestCodes
import im.vector.riotx.features.html.EventHtmlRenderer
import im.vector.riotx.features.html.PillImageSpan
import im.vector.riotx.features.invite.VectorInviteView
@@ -159,6 +169,7 @@ import im.vector.riotx.features.permalink.NavigationInterceptor
import im.vector.riotx.features.permalink.PermalinkHandler
import im.vector.riotx.features.reactions.EmojiReactionPickerActivity
import im.vector.riotx.features.settings.VectorPreferences
+import im.vector.riotx.features.settings.VectorSettingsActivity
import im.vector.riotx.features.share.SharedData
import im.vector.riotx.features.themes.ThemeUtils
import im.vector.riotx.features.widgets.WidgetActivity
@@ -195,17 +206,22 @@ class RoomDetailFragment @Inject constructor(
val roomDetailViewModelFactory: RoomDetailViewModel.Factory,
private val eventHtmlRenderer: EventHtmlRenderer,
private val vectorPreferences: VectorPreferences,
- private val colorProvider: ColorProvider) :
+ private val colorProvider: ColorProvider,
+ private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager) :
VectorBaseFragment(),
TimelineEventController.Callback,
VectorInviteView.Callback,
JumpToReadMarkerView.Callback,
AttachmentTypeSelectorView.Callback,
AttachmentsHelper.Callback,
- RoomWidgetsBannerView.Callback {
+ RoomWidgetsBannerView.Callback,
+ ActiveCallView.Callback {
companion object {
+ private const val AUDIO_CALL_PERMISSION_REQUEST_CODE = 1
+ private const val VIDEO_CALL_PERMISSION_REQUEST_CODE = 2
+
/**
* Sanitize the display name.
*
@@ -242,6 +258,8 @@ class RoomDetailFragment @Inject constructor(
override fun getMenuRes() = R.menu.menu_timeline
private lateinit var sharedActionViewModel: MessageSharedActionViewModel
+ private lateinit var sharedCallActionViewModel: SharedActiveCallViewModel
+
private lateinit var layoutManager: LinearLayoutManager
private lateinit var jumpToBottomViewVisibilityManager: JumpToBottomViewVisibilityManager
private var modelBuildListener: OnModelBuildFinishedListener? = null
@@ -254,10 +272,12 @@ class RoomDetailFragment @Inject constructor(
private lateinit var attachmentTypeSelector: AttachmentTypeSelectorView
private var lockSendButton = false
+ private val activeCallViewHolder = ActiveCallViewHolder()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
+ sharedCallActionViewModel = activityViewModelProvider.get(SharedActiveCallViewModel::class.java)
attachmentsHelper = AttachmentsHelper(requireContext(), this).register()
keyboardStateUtils = KeyboardStateUtils(requireActivity())
setupToolbar(roomToolbar)
@@ -266,6 +286,7 @@ class RoomDetailFragment @Inject constructor(
setupInviteView()
setupNotificationView()
setupJumpToReadMarkerView()
+ setupActiveCallView()
setupJumpToBottomView()
setupWidgetsBannerView()
@@ -280,6 +301,13 @@ class RoomDetailFragment @Inject constructor(
}
.disposeOnDestroyView()
+ sharedCallActionViewModel
+ .activeCall
+ .observe(viewLifecycleOwner, Observer {
+ activeCallViewHolder.updateCall(it, webRtcPeerConnectionManager)
+ invalidateOptionsMenu()
+ })
+
roomDetailViewModel.selectSubscribe(this, RoomDetailViewState::tombstoneEventHandling, uniqueOnly("tombstoneEventHandling")) {
renderTombstoneEventHandling(it)
}
@@ -302,22 +330,33 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.observeViewEvents {
when (it) {
- is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable)
- is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds)
- is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it)
- is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it)
- is RoomDetailViewEvents.ShowMessage -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG)
- is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it)
- is RoomDetailViewEvents.FileTooBigError -> displayFileTooBigError(it)
- is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it)
- is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it)
- is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it)
- RoomDetailViewEvents.DisplayPromptForIntegrationManager -> displayPromptForIntegrationManager()
- is RoomDetailViewEvents.OpenStickerPicker -> openStickerPicker(it)
+ is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable)
+ is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds)
+ is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it)
+ is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it)
+ is RoomDetailViewEvents.ShowMessage -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG)
+ is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it)
+ is RoomDetailViewEvents.FileTooBigError -> displayFileTooBigError(it)
+ is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it)
+ is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it)
+ is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it)
+ RoomDetailViewEvents.DisplayPromptForIntegrationManager -> displayPromptForIntegrationManager()
+ is RoomDetailViewEvents.OpenStickerPicker -> openStickerPicker(it)
+ is RoomDetailViewEvents.DisplayEnableIntegrationsWarning -> displayDisabledIntegrationDialog()
+ is RoomDetailViewEvents.OpenIntegrationManager -> openIntegrationManager()
}.exhaustive
}
}
+ private fun openIntegrationManager(screen: String? = null) {
+ navigator.openIntegrationManager(
+ fragment = this,
+ roomId = roomDetailArgs.roomId,
+ integId = null,
+ screen = screen
+ )
+ }
+
private fun setupWidgetsBannerView() {
roomWidgetsBannerView.callback = this
}
@@ -334,10 +373,7 @@ class RoomDetailFragment @Inject constructor(
.setView(v)
.setPositiveButton(R.string.yes) { _, _ ->
// Open integration manager, to the sticker installation page
- navigator.openIntegrationManager(
- context = requireContext(),
- roomId = roomDetailArgs.roomId,
- integId = null,
+ openIntegrationManager(
screen = WidgetType.StickerPicker.preferred
)
}
@@ -371,6 +407,7 @@ class RoomDetailFragment @Inject constructor(
override fun onDestroyView() {
timelineEventController.callback = null
timelineEventController.removeModelBuildListener(modelBuildListener)
+ activeCallView.callback = null
modelBuildListener = null
autoCompleter.clear()
debouncer.cancelAll()
@@ -380,6 +417,7 @@ class RoomDetailFragment @Inject constructor(
}
override fun onDestroy() {
+ activeCallViewHolder.unBind(webRtcPeerConnectionManager)
roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState)
super.onDestroy()
}
@@ -409,6 +447,15 @@ class RoomDetailFragment @Inject constructor(
jumpToReadMarkerView.callback = this
}
+ private fun setupActiveCallView() {
+ activeCallViewHolder.bind(
+ activeCallPiP,
+ activeCallView,
+ activeCallPiPWrap,
+ this
+ )
+ }
+
private fun navigateToEvent(action: RoomDetailViewEvents.NavigateToEvent) {
val scrollPosition = timelineEventController.searchPositionOfEvent(action.eventId)
if (scrollPosition == null) {
@@ -469,13 +516,67 @@ class RoomDetailFragment @Inject constructor(
true
}
R.id.open_matrix_apps -> {
- navigator.openIntegrationManager(requireContext(), roomDetailArgs.roomId, null, null)
+ roomDetailViewModel.handle(RoomDetailAction.OpenIntegrationManager)
+ true
+ }
+ R.id.voice_call,
+ R.id.video_call -> {
+ val activeCall = sharedCallActionViewModel.activeCall.value
+ val isVideoCall = item.itemId == R.id.video_call
+ if (activeCall != null) {
+ // resume existing if same room, if not prompt to kill and then restart new call?
+ if (activeCall.roomId == roomDetailArgs.roomId) {
+ onTapToReturnToCall()
+ }
+// else {
+ // TODO might not work well, and should prompt
+// webRtcPeerConnectionManager.endCall()
+// safeStartCall(it, isVideoCall)
+// }
+ } else {
+ safeStartCall(isVideoCall)
+ }
+ true
+ }
+ R.id.hangup_call -> {
+ roomDetailViewModel.handle(RoomDetailAction.EndCall)
true
}
else -> super.onOptionsItemSelected(item)
}
}
+ private fun displayDisabledIntegrationDialog() {
+ AlertDialog.Builder(requireActivity())
+ .setTitle(R.string.disabled_integration_dialog_title)
+ .setMessage(R.string.disabled_integration_dialog_content)
+ .setPositiveButton(R.string.settings) { _, _ ->
+ navigator.openSettings(requireActivity(), VectorSettingsActivity.EXTRA_DIRECT_ACCESS_GENERAL)
+ }
+ .setNegativeButton(R.string.cancel, null)
+ .show()
+ }
+
+ private fun safeStartCall(isVideoCall: Boolean) {
+ val startCallAction = RoomDetailAction.StartCall(isVideoCall)
+ roomDetailViewModel.pendingAction = startCallAction
+ if (isVideoCall) {
+ if (checkPermissions(PERMISSIONS_FOR_VIDEO_IP_CALL,
+ this, VIDEO_CALL_PERMISSION_REQUEST_CODE,
+ R.string.permissions_rationale_msg_camera_and_audio)) {
+ roomDetailViewModel.pendingAction = null
+ roomDetailViewModel.handle(startCallAction)
+ }
+ } else {
+ if (checkPermissions(PERMISSIONS_FOR_AUDIO_IP_CALL,
+ this, AUDIO_CALL_PERMISSION_REQUEST_CODE,
+ R.string.permissions_rationale_msg_record_audio)) {
+ roomDetailViewModel.pendingAction = null
+ roomDetailViewModel.handle(startCallAction)
+ }
+ }
+ }
+
private fun renderRegularMode(text: String) {
autoCompleter.exitSpecialMode()
composerLayout.collapse()
@@ -548,16 +649,16 @@ class RoomDetailFragment @Inject constructor(
val hasBeenHandled = attachmentsHelper.onActivityResult(requestCode, resultCode, data)
if (!hasBeenHandled && resultCode == RESULT_OK && data != null) {
when (requestCode) {
- AttachmentsPreviewActivity.REQUEST_CODE -> {
+ AttachmentsPreviewActivity.REQUEST_CODE -> {
val sendData = AttachmentsPreviewActivity.getOutput(data)
val keepOriginalSize = AttachmentsPreviewActivity.getKeepOriginalSize(data)
roomDetailViewModel.handle(RoomDetailAction.SendMedia(sendData, !keepOriginalSize))
}
- REACTION_SELECT_REQUEST_CODE -> {
+ REACTION_SELECT_REQUEST_CODE -> {
val (eventId, reaction) = EmojiReactionPickerActivity.getOutput(data) ?: return
roomDetailViewModel.handle(RoomDetailAction.SendReaction(eventId, reaction))
}
- StickerPickerConstants.STICKER_PICKER_REQUEST_CODE -> {
+ WidgetRequestCodes.STICKER_PICKER_REQUEST_CODE -> {
val content = WidgetActivity.getOutput(data).toModel() ?: return
roomDetailViewModel.handle(RoomDetailAction.SendSticker(content))
}
@@ -719,6 +820,7 @@ class RoomDetailFragment @Inject constructor(
override fun invalidate() = withState(roomDetailViewModel) { state ->
renderRoomSummary(state)
+ invalidateOptionsMenu()
val summary = state.asyncRoomSummary()
val inviter = state.asyncInviter()
if (summary?.membership == Membership.JOIN) {
@@ -1070,6 +1172,22 @@ class RoomDetailFragment @Inject constructor(
launchAttachmentProcess(pendingType)
}
}
+ AUDIO_CALL_PERMISSION_REQUEST_CODE -> {
+ if (onPermissionResultAudioIpCall(requireContext(), grantResults)) {
+ (roomDetailViewModel.pendingAction as? RoomDetailAction.StartCall)?.let {
+ roomDetailViewModel.pendingAction = null
+ roomDetailViewModel.handle(it)
+ }
+ }
+ }
+ VIDEO_CALL_PERMISSION_REQUEST_CODE -> {
+ if (onPermissionResultVideoIpCall(requireContext(), grantResults)) {
+ (roomDetailViewModel.pendingAction as? RoomDetailAction.StartCall)?.let {
+ roomDetailViewModel.pendingAction = null
+ roomDetailViewModel.handle(it)
+ }
+ }
+ }
}
} else {
// Reset all pending data
@@ -1453,4 +1571,20 @@ class RoomDetailFragment @Inject constructor(
RoomWidgetsBottomSheet.newInstance()
.show(childFragmentManager, "ROOM_WIDGETS_BOTTOM_SHEET")
}
+
+ override fun onTapToReturnToCall() {
+ sharedCallActionViewModel.activeCall.value?.let { call ->
+ VectorCallActivity.newIntent(
+ context = requireContext(),
+ callId = call.callId,
+ roomId = call.roomId,
+ otherUserId = call.otherUserId,
+ isIncomingCall = !call.isOutgoing,
+ isVideoCall = call.isVideoCall,
+ mode = null
+ ).let {
+ startActivity(it)
+ }
+ }
+ }
}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt
index 73ce95eda2..560da2e116 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt
@@ -52,8 +52,12 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
object DisplayPromptForIntegrationManager: RoomDetailViewEvents()
+ object DisplayEnableIntegrationsWarning: RoomDetailViewEvents()
+
data class OpenStickerPicker(val widget: Widget): RoomDetailViewEvents()
+ object OpenIntegrationManager: RoomDetailViewEvents()
+
object MessageSent : SendMessageResult()
data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult()
class SlashCommandError(val command: Command) : SendMessageResult()
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
index cdea939be3..529218216c 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
@@ -30,6 +30,7 @@ import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.MatrixPatterns
import im.vector.matrix.android.api.NoOpMatrixCallback
+import im.vector.matrix.android.api.extensions.orFalse
import im.vector.matrix.android.api.query.QueryStringValue
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.EventType
@@ -68,6 +69,7 @@ import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.resources.UserPreferencesProvider
import im.vector.riotx.core.utils.subscribeLogError
+import im.vector.riotx.features.call.WebRtcPeerConnectionManager
import im.vector.riotx.features.command.CommandParser
import im.vector.riotx.features.command.ParsedCommand
import im.vector.riotx.features.crypto.verification.SupportedVerificationMethodsProvider
@@ -82,7 +84,9 @@ import io.reactivex.Observable
import io.reactivex.functions.BiFunction
import io.reactivex.rxkotlin.subscribeBy
import io.reactivex.schedulers.Schedulers
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer
import timber.log.Timber
@@ -100,7 +104,8 @@ class RoomDetailViewModel @AssistedInject constructor(
private val session: Session,
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
private val stickerPickerActionHandler: StickerPickerActionHandler,
- private val roomSummaryHolder: RoomSummaryHolder
+ private val roomSummaryHolder: RoomSummaryHolder,
+ private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager
) : VectorViewModel(initialState), Timeline.Listener {
private val room = session.getRoom(initialState.roomId)!!
@@ -125,8 +130,7 @@ class RoomDetailViewModel @AssistedInject constructor(
}
private var timelineEvents = PublishRelay.create>()
- var timeline = room.createTimeline(eventId, timelineSettings)
- private set
+ val timeline = room.createTimeline(eventId, timelineSettings)
// Slot to keep a pending action during permission request
var pendingAction: RoomDetailAction? = null
@@ -218,6 +222,8 @@ class RoomDetailViewModel @AssistedInject constructor(
}
}
+ fun getOtherUserIds() = room.roomSummary()?.otherMemberIds
+
override fun handle(action: RoomDetailAction) {
when (action) {
is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action)
@@ -257,13 +263,26 @@ class RoomDetailViewModel @AssistedInject constructor(
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
- }
+ is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
+ is RoomDetailAction.StartCall -> handleStartCall(action)
+ is RoomDetailAction.EndCall -> handleEndCall()
+ }.exhaustive
}
private fun handleSendSticker(action: RoomDetailAction.SendSticker) {
room.sendEvent(EventType.STICKER, action.stickerContent.toContent())
}
+ private fun handleStartCall(action: RoomDetailAction.StartCall) {
+ room.roomSummary()?.otherMemberIds?.firstOrNull()?.let {
+ webRtcPeerConnectionManager.startOutgoingCall(room.roomId, it, action.isVideo)
+ }
+ }
+
+ private fun handleEndCall() {
+ webRtcPeerConnectionManager.endCall()
+ }
+
private fun handleSelectStickerAttachment() {
viewModelScope.launch {
val viewEvent = stickerPickerActionHandler.handle()
@@ -271,6 +290,19 @@ class RoomDetailViewModel @AssistedInject constructor(
}
}
+ private fun handleOpenIntegrationManager() {
+ viewModelScope.launch {
+ val viewEvent = withContext(Dispatchers.Default) {
+ if (isIntegrationEnabled()) {
+ RoomDetailViewEvents.OpenIntegrationManager
+ } else {
+ RoomDetailViewEvents.DisplayEnableIntegrationsWarning
+ }
+ }
+ _viewEvents.post(viewEvent)
+ }
+ }
+
private fun startTrackingUnreadMessages() {
trackUnreadMessages.set(true)
setState { copy(canShowJumpToReadMarker = false) }
@@ -370,13 +402,18 @@ class RoomDetailViewModel @AssistedInject constructor(
}
}
+ private fun isIntegrationEnabled() = session.integrationManagerService().isIntegrationEnabled()
+
fun isMenuItemVisible(@IdRes itemId: Int) = when (itemId) {
R.id.clear_message_queue ->
- /* For now always disable on production, worker cancellation is not working properly */
+ // For now always disable when not in developer mode, worker cancellation is not working properly
timeline.pendingEventCount() > 0 && vectorPreferences.developerMode()
R.id.resend_all -> timeline.failedToDeliverEventCount() > 0
R.id.clear_all -> timeline.failedToDeliverEventCount() > 0
- R.id.open_matrix_apps -> session.integrationManagerService().isIntegrationEnabled()
+ R.id.open_matrix_apps -> true
+ R.id.voice_call,
+ R.id.video_call -> room.canStartCall() && webRtcPeerConnectionManager.currentCall == null
+ R.id.hangup_call -> webRtcPeerConnectionManager.currentCall != null
else -> false
}
@@ -1003,8 +1040,7 @@ class RoomDetailViewModel @AssistedInject constructor(
.unwrap()
.execute { async ->
copy(
- asyncRoomSummary = async,
- typingRoomMembers = typingRoomMembers
+ asyncRoomSummary = async
)
}
}
@@ -1075,6 +1111,7 @@ class RoomDetailViewModel @AssistedInject constructor(
private fun observeSummaryState() {
asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary ->
+
roomSummaryHolder.set(summary)
if (summary.membership == Membership.INVITE) {
summary.inviterId?.let { inviterId ->
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt
index 44e42e761c..b7268e7c61 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt
@@ -57,7 +57,6 @@ data class RoomDetailViewState(
val asyncInviter: Async = Uninitialized,
val asyncRoomSummary: Async = Uninitialized,
val activeRoomWidgets: Async> = Uninitialized,
- val typingRoomMembers: List? = null,
val typingMessage: String? = null,
val sendMode: SendMode = SendMode.REGULAR(""),
val tombstoneEvent: Event? = null,
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt
index 33e18595a0..4616cb4b25 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt
@@ -33,6 +33,7 @@ import com.airbnb.epoxy.EpoxyTouchHelperCallback
import com.airbnb.epoxy.EpoxyViewHolder
import timber.log.Timber
import kotlin.math.abs
+import kotlin.math.min
class RoomMessageTouchHelperCallback(private val context: Context,
@DrawableRes actionIcon: Int,
@@ -92,7 +93,7 @@ class RoomMessageTouchHelperCallback(private val context: Context,
setTouchListener(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
}
val size = triggerDistance
- if (Math.abs(viewHolder.itemView.translationX) < size || dX > this.dX /*going back*/) {
+ if (abs(viewHolder.itemView.translationX) < size || dX > this.dX /*going back*/) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
this.dX = dX
startTracking = true
@@ -127,9 +128,9 @@ class RoomMessageTouchHelperCallback(private val context: Context,
private fun drawReplyButton(canvas: Canvas, itemView: View) {
// Timber.v("drawReplyButton")
- val translationX = Math.abs(itemView.translationX)
+ val translationX = abs(itemView.translationX)
val newTime = System.currentTimeMillis()
- val dt = Math.min(17, newTime - lastReplyButtonAnimationTime)
+ val dt = min(17, newTime - lastReplyButtonAnimationTime)
lastReplyButtonAnimationTime = newTime
val showing = translationX >= minShowDistance
if (showing) {
@@ -163,10 +164,10 @@ class RoomMessageTouchHelperCallback(private val context: Context,
} else {
1.2f - 0.2f * ((replyButtonProgress - 0.8f) / 0.2f)
}
- alpha = Math.min(255f, 255 * (replyButtonProgress / 0.8f)).toInt()
+ alpha = min(255f, 255 * (replyButtonProgress / 0.8f)).toInt()
} else {
scale = replyButtonProgress
- alpha = Math.min(255f, 255 * replyButtonProgress).toInt()
+ alpha = min(255f, 255 * replyButtonProgress).toInt()
}
imageDrawable.alpha = alpha
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/sticker/StickerPickerActionHandler.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/sticker/StickerPickerActionHandler.kt
index 3b939892b5..ebae583d8c 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/sticker/StickerPickerActionHandler.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/sticker/StickerPickerActionHandler.kt
@@ -27,6 +27,10 @@ class StickerPickerActionHandler @Inject constructor(private val session: Sessio
suspend fun handle(): RoomDetailViewEvents = withContext(Dispatchers.Default) {
// Search for the sticker picker widget in the user account
+ val integrationsEnabled = session.integrationManagerService().isIntegrationEnabled()
+ if (!integrationsEnabled) {
+ return@withContext RoomDetailViewEvents.DisplayEnableIntegrationsWarning
+ }
val stickerWidget = session.widgetService().getUserWidgets(WidgetType.StickerPicker.values()).firstOrNull { it.isActive }
if (stickerWidget == null || stickerWidget.computedUrl.isNullOrBlank()) {
RoomDetailViewEvents.DisplayPromptForIntegrationManager
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionState.kt
index c12306c2d0..4eeed1292e 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionState.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionState.kt
@@ -52,7 +52,7 @@ data class MessageActionState(
val actions: List = emptyList(),
val expendedReportContentMenu: Boolean = false,
val actionPermissions: ActionPermissions = ActionPermissions()
- ) : MvRxState {
+) : MvRxState {
constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId, informationData = args.informationData)
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
index 909169e7b0..6c192105d7 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
@@ -45,9 +45,9 @@ import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter
-import im.vector.riotx.features.powerlevel.PowerLevelsObservableFactory
import im.vector.riotx.features.html.EventHtmlRenderer
import im.vector.riotx.features.html.VectorHtmlCompressor
+import im.vector.riotx.features.powerlevel.PowerLevelsObservableFactory
import im.vector.riotx.features.reactions.data.EmojiDataSource
import im.vector.riotx.features.settings.VectorPreferences
@@ -184,6 +184,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
EventType.STATE_ROOM_CANONICAL_ALIAS,
EventType.STATE_ROOM_HISTORY_VISIBILITY,
EventType.CALL_INVITE,
+ EventType.CALL_CANDIDATES,
EventType.CALL_HANGUP,
EventType.CALL_ANSWER -> {
noticeEventFormatter.format(timelineEvent)
@@ -356,6 +357,9 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
private fun canRedact(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean {
// Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
if (event.root.getClearType() != EventType.MESSAGE) return false
+ // Message sent by the current user can always be redacted
+ if (event.root.senderId == session.myUserId) return true
+ // Check permission for messages sent by other users
return actionPermissions.canRedact
}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
index c81a945bc7..462caf8e97 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
@@ -80,12 +80,15 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
EventType.KEY_VERIFICATION_START,
EventType.KEY_VERIFICATION_KEY,
EventType.KEY_VERIFICATION_READY,
- EventType.KEY_VERIFICATION_MAC -> {
+ EventType.KEY_VERIFICATION_MAC,
+ EventType.CALL_CANDIDATES -> {
// TODO These are not filtered out by timeline when encrypted
// For now manually ignore
if (userPreferencesProvider.shouldShowHiddenEvents()) {
noticeItemFactory.create(event, highlight, callback)
- } else null
+ } else {
+ null
+ }
}
EventType.KEY_VERIFICATION_CANCEL,
EventType.KEY_VERIFICATION_DONE -> {
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt
index 1d178054ac..c931c155b1 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt
@@ -29,7 +29,6 @@ import me.gujun.android.span.span
import javax.inject.Inject
class DisplayableEventFormatter @Inject constructor(
-// private val sessionHolder: ActiveSessionHolder,
private val stringProvider: StringProvider,
private val colorProvider: ColorProvider,
private val noticeEventFormatter: NoticeEventFormatter
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt
index 33079a8f33..37debace89 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt
@@ -68,6 +68,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
EventType.STATE_ROOM_POWER_LEVELS -> formatRoomPowerLevels(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
EventType.CALL_INVITE,
+ EventType.CALL_CANDIDATES,
EventType.CALL_HANGUP,
EventType.CALL_ANSWER -> formatCallEvent(type, timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
EventType.MESSAGE,
@@ -237,9 +238,9 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
private fun formatCallEvent(type: String, event: Event, senderName: String?): CharSequence? {
return when (type) {
- EventType.CALL_INVITE -> {
+ EventType.CALL_INVITE -> {
val content = event.getClearContent().toModel() ?: return null
- val isVideoCall = content.offer.sdp == CallInviteContent.Offer.SDP_VIDEO
+ val isVideoCall = content.offer?.sdp == CallInviteContent.Offer.SDP_VIDEO
return if (isVideoCall) {
if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_placed_video_call_by_you)
@@ -254,19 +255,25 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
}
}
}
- EventType.CALL_ANSWER ->
+ EventType.CALL_ANSWER ->
if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_answered_call_by_you)
} else {
sp.getString(R.string.notice_answered_call, senderName)
}
- EventType.CALL_HANGUP ->
+ EventType.CALL_HANGUP ->
if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_ended_call_by_you)
} else {
sp.getString(R.string.notice_ended_call, senderName)
}
- else -> null
+ EventType.CALL_CANDIDATES ->
+ if (event.isSentByCurrentUser()) {
+ sp.getString(R.string.notice_call_candidates_by_you)
+ } else {
+ sp.getString(R.string.notice_call_candidates, senderName)
+ }
+ else -> null
}
}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/PollResultLineView.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/PollResultLineView.kt
index a0ad3466f7..c52b863658 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/PollResultLineView.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/PollResultLineView.kt
@@ -64,8 +64,8 @@ class PollResultLineView @JvmOverloads constructor(
set(value) {
field = value
// Text in main color
- labelTextView.setTypeface(labelTextView.getTypeface(), if (value) Typeface.BOLD else Typeface.NORMAL)
- percentTextView.setTypeface(percentTextView.getTypeface(), if (value) Typeface.BOLD else Typeface.NORMAL)
+ labelTextView.setTypeface(labelTextView.typeface, if (value) Typeface.BOLD else Typeface.NORMAL)
+ percentTextView.setTypeface(percentTextView.typeface, if (value) Typeface.BOLD else Typeface.NORMAL)
}
init {
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/sticker/StickerPickerConstants.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/widget/WidgetRequestCodes.kt
similarity index 82%
rename from vector/src/main/java/im/vector/riotx/features/home/room/detail/sticker/StickerPickerConstants.kt
rename to vector/src/main/java/im/vector/riotx/features/home/room/detail/widget/WidgetRequestCodes.kt
index 8068eafc85..6fdfa598e4 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/sticker/StickerPickerConstants.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/widget/WidgetRequestCodes.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package im.vector.riotx.features.home.room.detail.sticker
+package im.vector.riotx.features.home.room.detail.widget
-object StickerPickerConstants {
+object WidgetRequestCodes {
const val STICKER_PICKER_REQUEST_CODE = 16000
+ const val INTEGRATION_MANAGER_REQUEST_CODE = 16001
}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomCategoryItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomCategoryItem.kt
index 0c4ae4d61d..c3b1dc27eb 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomCategoryItem.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomCategoryItem.kt
@@ -44,7 +44,7 @@ abstract class RoomCategoryItem : VectorEpoxyModel() {
DrawableCompat.setTint(it, tintColor)
}
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted))
- holder.titleView.setCompoundDrawablesWithIntrinsicBounds(expandedArrowDrawable, null, null, null)
+ holder.titleView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null)
holder.titleView.text = title
holder.rootView.setOnClickListener { listener?.invoke() }
}
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt
index c9724bf971..b06cb8a4bb 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt
@@ -141,7 +141,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
val showHighlighted = summaries.any { it.highlightCount > 0 }
roomCategoryItem {
id(titleRes)
- title(stringProvider.getString(titleRes).toUpperCase())
+ title(stringProvider.getString(titleRes))
expanded(isExpanded)
unreadNotificationCount(unreadCount)
showHighlighted(showHighlighted)
diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt
index f15cb801ed..abdea9698f 100644
--- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt
@@ -48,6 +48,7 @@ import im.vector.riotx.core.extensions.configureAndStart
import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider
+import im.vector.riotx.features.call.WebRtcPeerConnectionManager
import im.vector.riotx.features.notifications.PushRuleTriggerListener
import im.vector.riotx.features.session.SessionListener
import im.vector.riotx.features.signout.soft.SoftLogoutActivity
@@ -66,7 +67,8 @@ class LoginViewModel @AssistedInject constructor(
private val homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory,
private val sessionListener: SessionListener,
private val reAuthHelper: ReAuthHelper,
- private val stringProvider: StringProvider)
+ private val stringProvider: StringProvider,
+ private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager)
: VectorViewModel(initialState) {
@AssistedInject.Factory
@@ -613,6 +615,7 @@ class LoginViewModel @AssistedInject constructor(
private fun onSessionCreated(session: Session) {
activeSessionHolder.setActiveSession(session)
session.configureAndStart(applicationContext, pushRuleTriggerListener, sessionListener)
+ session.callSignalingService().addCallListener(webRtcPeerConnectionManager)
setState {
copy(
asyncLoginAction = Success(Unit)
diff --git a/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt
index 88f4fc2f5f..49abc9ba81 100755
--- a/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt
@@ -21,17 +21,17 @@ import android.os.Parcelable
import android.view.View
import butterknife.OnClick
import com.airbnb.mvrx.args
+import im.vector.matrix.android.internal.auth.registration.LocalizedFlowDataLoginTerms
import im.vector.riotx.R
import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.extensions.toReducedUrl
-import im.vector.riotx.core.utils.openUrlInExternalBrowser
+import im.vector.riotx.core.utils.openUrlInChromeCustomTab
import im.vector.riotx.features.login.AbstractLoginFragment
import im.vector.riotx.features.login.LoginAction
import im.vector.riotx.features.login.LoginViewState
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_login_terms.*
-import im.vector.matrix.android.internal.auth.registration.LocalizedFlowDataLoginTerms
import javax.inject.Inject
@Parcelize
@@ -95,7 +95,7 @@ class LoginTermsFragment @Inject constructor(
localizedFlowDataLoginTerms.localizedUrl
?.takeIf { it.isNotBlank() }
?.let {
- openUrlInExternalBrowser(requireContext(), it)
+ openUrlInChromeCustomTab(requireContext(), null, it)
}
}
diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt
index 6a0094520a..a909e5becf 100644
--- a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt
+++ b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt
@@ -30,8 +30,8 @@ import androidx.fragment.app.Fragment
import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
import im.vector.matrix.android.api.session.terms.TermsService
-import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.session.widgets.model.Widget
+import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.error.fatalError
@@ -46,7 +46,7 @@ import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
import im.vector.riotx.features.debug.DebugMenuActivity
import im.vector.riotx.features.home.room.detail.RoomDetailActivity
import im.vector.riotx.features.home.room.detail.RoomDetailArgs
-import im.vector.riotx.features.home.room.detail.sticker.StickerPickerConstants
+import im.vector.riotx.features.home.room.detail.widget.WidgetRequestCodes
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
import im.vector.riotx.features.invite.InviteUsersToRoomActivity
import im.vector.riotx.features.media.BigImageViewerActivity
@@ -230,12 +230,13 @@ class DefaultNavigator @Inject constructor(
override fun openStickerPicker(fragment: Fragment, roomId: String, widget: Widget, requestCode: Int) {
val widgetArgs = widgetArgsBuilder.buildStickerPickerArgs(roomId, widget)
val intent = WidgetActivity.newIntent(fragment.requireContext(), widgetArgs)
- fragment.startActivityForResult(intent, StickerPickerConstants.STICKER_PICKER_REQUEST_CODE)
+ fragment.startActivityForResult(intent, WidgetRequestCodes.STICKER_PICKER_REQUEST_CODE)
}
- override fun openIntegrationManager(context: Context, roomId: String, integId: String?, screen: String?) {
+ override fun openIntegrationManager(fragment: Fragment, roomId: String, integId: String?, screen: String?) {
val widgetArgs = widgetArgsBuilder.buildIntegrationManagerArgs(roomId, integId, screen)
- context.startActivity(WidgetActivity.newIntent(context, widgetArgs))
+ val intent = WidgetActivity.newIntent(fragment.requireContext(), widgetArgs)
+ fragment.startActivityForResult(intent, WidgetRequestCodes.INTEGRATION_MANAGER_REQUEST_CODE)
}
override fun openRoomWidget(context: Context, roomId: String, widget: Widget) {
diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt
index 35ace87b6b..916a46c041 100644
--- a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt
+++ b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt
@@ -25,7 +25,7 @@ import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
import im.vector.matrix.android.api.session.terms.TermsService
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.session.widgets.model.Widget
-import im.vector.riotx.features.home.room.detail.sticker.StickerPickerConstants
+import im.vector.riotx.features.home.room.detail.widget.WidgetRequestCodes
import im.vector.riotx.features.media.ImageContentRenderer
import im.vector.riotx.features.media.VideoContentRenderer
import im.vector.riotx.features.settings.VectorSettingsActivity
@@ -85,9 +85,9 @@ interface Navigator {
fun openStickerPicker(fragment: Fragment,
roomId: String,
widget: Widget,
- requestCode: Int = StickerPickerConstants.STICKER_PICKER_REQUEST_CODE)
+ requestCode: Int = WidgetRequestCodes.STICKER_PICKER_REQUEST_CODE)
- fun openIntegrationManager(context: Context, roomId: String, integId: String?, screen: String?)
+ fun openIntegrationManager(fragment: Fragment, roomId: String, integId: String?, screen: String?)
fun openRoomWidget(context: Context, roomId: String, widget: Widget)
diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationUtils.kt
index 178235ab5f..9dc518bbc9 100755
--- a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationUtils.kt
+++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationUtils.kt
@@ -35,11 +35,14 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.app.RemoteInput
import androidx.core.app.TaskStackBuilder
import androidx.core.content.ContextCompat
+import androidx.core.graphics.drawable.IconCompat
import androidx.fragment.app.Fragment
import im.vector.riotx.BuildConfig
import im.vector.riotx.R
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.utils.startNotificationChannelSettingsIntent
+import im.vector.riotx.features.call.VectorCallActivity
+import im.vector.riotx.features.call.service.CallHeadsUpActionReceiver
import im.vector.riotx.features.home.HomeActivity
import im.vector.riotx.features.home.room.detail.RoomDetailActivity
import im.vector.riotx.features.home.room.detail.RoomDetailArgs
@@ -263,13 +266,13 @@ class NotificationUtils @Inject constructor(private val context: Context,
*/
@SuppressLint("NewApi")
fun buildIncomingCallNotification(isVideo: Boolean,
- roomName: String,
- matrixId: String,
+ otherUserId: String,
+ roomId: String,
callId: String): Notification {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
val builder = NotificationCompat.Builder(context, CALL_NOTIFICATION_CHANNEL_ID)
- .setContentTitle(ensureTitleNotEmpty(roomName))
+ .setContentTitle(ensureTitleNotEmpty(otherUserId))
.apply {
if (isVideo) {
setContentText(stringProvider.getString(R.string.incoming_video_call))
@@ -280,28 +283,124 @@ class NotificationUtils @Inject constructor(private val context: Context,
.setSmallIcon(R.drawable.incoming_call_notification_transparent)
.setCategory(NotificationCompat.CATEGORY_CALL)
.setLights(accentColor, 500, 500)
+ .setOngoing(true)
// Compat: Display the incoming call notification on the lock screen
- builder.priority = NotificationCompat.PRIORITY_MAX
+ builder.priority = NotificationCompat.PRIORITY_HIGH
- // clear the activity stack to home activity
- val intent = Intent(context, HomeActivity::class.java)
- .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- // TODO .putExtra(VectorHomeActivity.EXTRA_CALL_SESSION_ID, matrixId)
- // TODO .putExtra(VectorHomeActivity.EXTRA_CALL_ID, callId)
-
- // Recreate the back stack
- val stackBuilder = TaskStackBuilder.create(context)
- .addParentStack(HomeActivity::class.java)
- .addNextIntent(intent)
-
- // android 4.3 issue
- // use a generator for the private requestCode.
- // When using 0, the intent is not created/launched when the user taps on the notification.
//
- val pendingIntent = stackBuilder.getPendingIntent(Random.nextInt(1000), PendingIntent.FLAG_UPDATE_CURRENT)
+ val requestId = Random.nextInt(1000)
+// val pendingIntent = stackBuilder.getPendingIntent(requestId, PendingIntent.FLAG_UPDATE_CURRENT)
- builder.setContentIntent(pendingIntent)
+ val contentIntent = VectorCallActivity.newIntent(
+ context = context,
+ callId = callId,
+ roomId = roomId,
+ otherUserId = otherUserId,
+ isIncomingCall = true,
+ isVideoCall = isVideo,
+ mode = VectorCallActivity.INCOMING_RINGING
+ ).apply {
+ flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
+ data = Uri.parse("foobar://$callId")
+ }
+ val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0)
+
+ val answerCallPendingIntent = TaskStackBuilder.create(context)
+ .addNextIntentWithParentStack(Intent(context, HomeActivity::class.java))
+ .addNextIntent(VectorCallActivity.newIntent(
+ context = context,
+ callId = callId,
+ roomId = roomId,
+ otherUserId = otherUserId,
+ isIncomingCall = true,
+ isVideoCall = isVideo,
+ mode = VectorCallActivity.INCOMING_ACCEPT)
+ )
+ .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
+
+ val rejectCallActionReceiver = Intent(context, CallHeadsUpActionReceiver::class.java).apply {
+ putExtra(CallHeadsUpActionReceiver.EXTRA_CALL_ACTION_KEY, CallHeadsUpActionReceiver.CALL_ACTION_REJECT)
+ }
+ // val answerCallPendingIntent = PendingIntent.getBroadcast(context, requestId, answerCallActionReceiver, PendingIntent.FLAG_UPDATE_CURRENT)
+ val rejectCallPendingIntent = PendingIntent.getBroadcast(
+ context,
+ requestId + 1,
+ rejectCallActionReceiver,
+ PendingIntent.FLAG_UPDATE_CURRENT
+ )
+
+ builder.addAction(
+ NotificationCompat.Action(
+ R.drawable.ic_call,
+ // IconCompat.createWithResource(applicationContext, R.drawable.ic_call)
+ // .setTint(ContextCompat.getColor(applicationContext, R.color.riotx_positive_accent)),
+ context.getString(R.string.call_notification_answer),
+ answerCallPendingIntent
+ )
+ )
+
+ builder.addAction(
+ NotificationCompat.Action(
+ IconCompat.createWithResource(context, R.drawable.ic_call_end).setTint(ContextCompat.getColor(context, R.color.riotx_notice)),
+ context.getString(R.string.call_notification_reject),
+ rejectCallPendingIntent)
+ )
+
+ builder.setFullScreenIntent(contentPendingIntent, true)
+
+ return builder.build()
+ }
+
+ fun buildOutgoingRingingCallNotification(isVideo: Boolean,
+ otherUserId: String,
+ roomId: String,
+ callId: String): Notification {
+ val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
+
+ val builder = NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID)
+ .setContentTitle(ensureTitleNotEmpty(otherUserId))
+ .apply {
+ setContentText(stringProvider.getString(R.string.call_ring))
+ }
+ .setSmallIcon(R.drawable.incoming_call_notification_transparent)
+ .setCategory(NotificationCompat.CATEGORY_CALL)
+ .setLights(accentColor, 500, 500)
+ .setOngoing(true)
+
+ val requestId = Random.nextInt(1000)
+
+ val contentIntent = VectorCallActivity.newIntent(
+ context = context,
+ callId = callId,
+ roomId = roomId,
+ otherUserId = otherUserId,
+ isIncomingCall = true,
+ isVideoCall = isVideo,
+ mode = null).apply {
+ flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
+ data = Uri.parse("foobar://$callId")
+ }
+ val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0)
+
+ val rejectCallActionReceiver = Intent(context, CallHeadsUpActionReceiver::class.java).apply {
+ putExtra(CallHeadsUpActionReceiver.EXTRA_CALL_ACTION_KEY, CallHeadsUpActionReceiver.CALL_ACTION_REJECT)
+ }
+
+ val rejectCallPendingIntent = PendingIntent.getBroadcast(
+ context,
+ requestId + 1,
+ rejectCallActionReceiver,
+ PendingIntent.FLAG_UPDATE_CURRENT
+ )
+
+ builder.addAction(
+ NotificationCompat.Action(
+ IconCompat.createWithResource(context, R.drawable.ic_call_end).setTint(ContextCompat.getColor(context, R.color.riotx_notice)),
+ context.getString(R.string.call_notification_hangup),
+ rejectCallPendingIntent)
+ )
+ builder.setContentIntent(contentPendingIntent)
return builder.build()
}
@@ -321,8 +420,9 @@ class NotificationUtils @Inject constructor(private val context: Context,
roomName: String,
roomId: String,
matrixId: String,
- callId: String): Notification {
- val builder = NotificationCompat.Builder(context, CALL_NOTIFICATION_CHANNEL_ID)
+ callId: String,
+ fromBg: Boolean = false): Notification {
+ val builder = NotificationCompat.Builder(context, if (fromBg) CALL_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID)
.setContentTitle(ensureTitleNotEmpty(roomName))
.apply {
if (isVideo) {
@@ -334,31 +434,37 @@ class NotificationUtils @Inject constructor(private val context: Context,
.setSmallIcon(R.drawable.incoming_call_notification_transparent)
.setCategory(NotificationCompat.CATEGORY_CALL)
- // Display the pending call notification on the lock screen
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- builder.priority = NotificationCompat.PRIORITY_MAX
+ if (fromBg) {
+ builder.priority = NotificationCompat.PRIORITY_LOW
+ builder.setOngoing(true)
}
- /* TODO
- // Build the pending intent for when the notification is clicked
- val roomIntent = Intent(context, VectorRoomActivity::class.java)
- .putExtra(VectorRoomActivity.EXTRA_ROOM_ID, roomId)
- .putExtra(VectorRoomActivity.EXTRA_MATRIX_ID, matrixId)
- .putExtra(VectorRoomActivity.EXTRA_START_CALL_ID, callId)
+ val rejectCallActionReceiver = Intent(context, CallHeadsUpActionReceiver::class.java).apply {
+ data = Uri.parse("mxcall://end?$callId")
+ putExtra(CallHeadsUpActionReceiver.EXTRA_CALL_ACTION_KEY, CallHeadsUpActionReceiver.CALL_ACTION_REJECT)
+ }
- // Recreate the back stack
- val stackBuilder = TaskStackBuilder.create(context)
- .addParentStack(VectorRoomActivity::class.java)
- .addNextIntent(roomIntent)
+ val rejectCallPendingIntent = PendingIntent.getBroadcast(
+ context,
+ System.currentTimeMillis().toInt(),
+ rejectCallActionReceiver,
+ PendingIntent.FLAG_UPDATE_CURRENT
+ )
- // android 4.3 issue
- // use a generator for the private requestCode.
- // When using 0, the intent is not created/launched when the user taps on the notification.
- //
- val pendingIntent = stackBuilder.getPendingIntent(Random().nextInt(1000), PendingIntent.FLAG_UPDATE_CURRENT)
+ builder.addAction(
+ NotificationCompat.Action(
+ IconCompat.createWithResource(context, R.drawable.ic_call_end).setTint(ContextCompat.getColor(context, R.color.riotx_notice)),
+ context.getString(R.string.call_notification_hangup),
+ rejectCallPendingIntent)
+ )
- builder.setContentIntent(pendingIntent)
- */
+ val contentPendingIntent = TaskStackBuilder.create(context)
+ .addNextIntentWithParentStack(Intent(context, HomeActivity::class.java))
+ // TODO other userId
+ .addNextIntent(VectorCallActivity.newIntent(context, callId, roomId, "otherUserId", true, isVideo, null))
+ .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
+
+ builder.setContentIntent(contentPendingIntent)
return builder.build()
}
diff --git a/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt b/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt
index 1876d83617..78a0cece41 100644
--- a/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt
+++ b/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt
@@ -15,6 +15,7 @@
*/
package im.vector.riotx.features.popup
+import android.annotation.SuppressLint
import android.app.Activity
import android.os.Build
import android.os.Handler
@@ -26,6 +27,7 @@ import com.tapadoo.alerter.OnHideAlertListener
import dagger.Lazy
import im.vector.riotx.R
import im.vector.riotx.features.home.AvatarRenderer
+import im.vector.riotx.features.themes.ThemeUtils
import timber.log.Timber
import java.lang.ref.WeakReference
import javax.inject.Inject
@@ -139,24 +141,32 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy= Build.VERSION_CODES.M && view != null) {
- var flags = view.systemUiVisibility
- flags = flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
- view.systemUiVisibility = flags
- }
+ weakCurrentActivity?.get()
+ ?.takeIf { Build.VERSION.SDK_INT >= Build.VERSION_CODES.M }
+ // Do not change anything on Dark themes
+ ?.takeIf { ThemeUtils.isLightTheme(it) }
+ ?.let { it.window?.decorView }
+ ?.let { view ->
+ var flags = view.systemUiVisibility
+ flags = flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
+ view.systemUiVisibility = flags
+ }
}
+ @SuppressLint("InlinedApi")
private fun setLightStatusBar() {
- val view = weakCurrentActivity?.get()?.window?.decorView
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && view != null) {
- var flags = view.systemUiVisibility
- flags = flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
- view.systemUiVisibility = flags
- }
+ weakCurrentActivity?.get()
+ ?.takeIf { Build.VERSION.SDK_INT >= Build.VERSION_CODES.M }
+ // Do not change anything on Dark themes
+ ?.takeIf { ThemeUtils.isLightTheme(it) }
+ ?.let { it.window?.decorView }
+ ?.let { view ->
+ var flags = view.systemUiVisibility
+ flags = flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
+ view.systemUiVisibility = flags
+ }
}
private fun showAlert(alert: VectorAlert, activity: Activity, animate: Boolean = true) {
diff --git a/vector/src/main/java/im/vector/riotx/features/rageshake/VectorFileLogger.kt b/vector/src/main/java/im/vector/riotx/features/rageshake/VectorFileLogger.kt
index bcc48fb96c..d4ad4efb6f 100644
--- a/vector/src/main/java/im/vector/riotx/features/rageshake/VectorFileLogger.kt
+++ b/vector/src/main/java/im/vector/riotx/features/rageshake/VectorFileLogger.kt
@@ -39,7 +39,7 @@ private const val SIZE_20MB = 20 * 1024 * 1024
private const val SIZE_50MB = 50 * 1024 * 1024
@Singleton
-class VectorFileLogger @Inject constructor(val context: Context, private val vectorPreferences: VectorPreferences) : Timber.DebugTree() {
+class VectorFileLogger @Inject constructor(val context: Context, private val vectorPreferences: VectorPreferences) : Timber.Tree() {
private val maxLogSizeByte: Int
private val logRotationCount: Int
diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/widget/DotsView.kt b/vector/src/main/java/im/vector/riotx/features/reactions/widget/DotsView.kt
index 292b17ce9f..a0dc4a4ae5 100644
--- a/vector/src/main/java/im/vector/riotx/features/reactions/widget/DotsView.kt
+++ b/vector/src/main/java/im/vector/riotx/features/reactions/widget/DotsView.kt
@@ -22,6 +22,8 @@ import android.graphics.Paint
import android.util.AttributeSet
import android.util.Property
import android.view.View
+import kotlin.math.cos
+import kotlin.math.sin
/**
* This view will draw dots floating around the center of it's view
@@ -84,16 +86,16 @@ class DotsView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
private fun drawOuterDotsFrame(canvas: Canvas) {
for (i in 0 until DOTS_COUNT) {
- val cX = (centerX + currentRadius1 * Math.cos(i.toDouble() * OUTER_DOTS_POSITION_ANGLE.toDouble() * Math.PI / 180)).toFloat()
- val cY = (centerY + currentRadius1 * Math.sin(i.toDouble() * OUTER_DOTS_POSITION_ANGLE.toDouble() * Math.PI / 180)).toFloat()
+ val cX = (centerX + currentRadius1 * cos(i.toDouble() * OUTER_DOTS_POSITION_ANGLE.toDouble() * Math.PI / 180)).toFloat()
+ val cY = (centerY + currentRadius1 * sin(i.toDouble() * OUTER_DOTS_POSITION_ANGLE.toDouble() * Math.PI / 180)).toFloat()
canvas.drawCircle(cX, cY, currentDotSize1, circlePaints[i % circlePaints.size])
}
}
private fun drawInnerDotsFrame(canvas: Canvas) {
for (i in 0 until DOTS_COUNT) {
- val cX = (centerX + currentRadius2 * Math.cos((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180)).toFloat()
- val cY = (centerY + currentRadius2 * Math.sin((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180)).toFloat()
+ val cX = (centerX + currentRadius2 * cos((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180)).toFloat()
+ val cY = (centerY + currentRadius2 * sin((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180)).toFloat()
canvas.drawCircle(cX, cY, currentDotSize2, circlePaints[(i + 1) % circlePaints.size])
}
}
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt
index beae0a623e..5f245c883d 100755
--- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt
@@ -74,7 +74,6 @@ class VectorPreferences @Inject constructor(private val context: Context) {
const val SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY = "SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY"
// user
- const val SETTINGS_DISPLAY_NAME_PREFERENCE_KEY = "SETTINGS_DISPLAY_NAME_PREFERENCE_KEY"
const val SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY = "SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY"
// contacts
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt
index 0c73c0f5d3..bbe6358bd9 100755
--- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt
@@ -59,6 +59,8 @@ class VectorSettingsActivity : VectorBaseActivity(),
if (isFirstCreation()) {
// display the fragment
when (intent.getIntExtra(EXTRA_DIRECT_ACCESS, EXTRA_DIRECT_ACCESS_ROOT)) {
+ EXTRA_DIRECT_ACCESS_GENERAL ->
+ replaceFragment(R.id.vector_settings_page, VectorSettingsGeneralFragment::class.java, null, FRAGMENT_TAG)
EXTRA_DIRECT_ACCESS_ADVANCED_SETTINGS ->
replaceFragment(R.id.vector_settings_page, VectorSettingsAdvancedSettingsFragment::class.java, null, FRAGMENT_TAG)
EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY ->
@@ -137,6 +139,7 @@ class VectorSettingsActivity : VectorBaseActivity(),
const val EXTRA_DIRECT_ACCESS_ADVANCED_SETTINGS = 1
const val EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY = 2
const val EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS = 3
+ const val EXTRA_DIRECT_ACCESS_GENERAL = 4
private const val FRAGMENT_TAG = "VectorSettingsPreferencesFragment"
}
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt
index 926d285f7b..6d73abf873 100644
--- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt
@@ -79,7 +79,7 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
findPreference(VectorPreferences.SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY)!!
}
private val mDisplayNamePreference by lazy {
- findPreference(VectorPreferences.SETTINGS_DISPLAY_NAME_PREFERENCE_KEY)!!
+ findPreference("SETTINGS_DISPLAY_NAME_PREFERENCE_KEY")!!
}
private val mPasswordPreference by lazy {
findPreference(VectorPreferences.SETTINGS_CHANGE_PASSWORD_PREFERENCE_KEY)!!
@@ -122,7 +122,9 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
it.summary = session.getUser(session.myUserId)?.displayName ?: ""
it.text = it.summary.toString()
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
- onDisplayNameClick(newValue?.let { (it as String).trim() })
+ newValue
+ ?.let { value -> (value as? String)?.trim() }
+ ?.let { value -> onDisplayNameChanged(value) }
false
}
}
@@ -857,45 +859,25 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
/**
* Update the displayname.
*/
- private fun onDisplayNameClick(value: String?) {
- notImplemented()
- /* TODO
- if (!TextUtils.equals(session.myUser.displayname, value)) {
+ private fun onDisplayNameChanged(value: String) {
+ val currentDisplayName = session.getUser(session.myUserId)?.displayName ?: ""
+ if (currentDisplayName != value) {
displayLoadingView()
- session.myUser.updateDisplayName(value, object : MatrixCallback {
- override fun onSuccess(info: Void?) {
+ session.setDisplayName(session.myUserId, value, object : MatrixCallback {
+ override fun onSuccess(data: Unit) {
+ if (!isAdded) return
// refresh the settings value
- PreferenceManager.getDefaultSharedPreferences(activity).edit {
- putString(VectorPreferences.SETTINGS_DISPLAY_NAME_PREFERENCE_KEY, value)
- }
-
+ mDisplayNamePreference.summary = value
onCommonDone(null)
-
- refreshDisplay()
}
- override fun onNetworkError(e: Exception) {
- onCommonDone(e.localizedMessage)
- }
-
- override fun onMatrixError(e: MatrixError) {
- if (MatrixError.M_CONSENT_NOT_GIVEN == e.errcode) {
- activity?.runOnUiThread {
- hideLoadingView()
- (activity as VectorAppCompatActivity).consentNotGivenHelper.displayDialog(e)
- }
- } else {
- onCommonDone(e.localizedMessage)
- }
- }
-
- override fun onUnexpectedError(e: Exception) {
- onCommonDone(e.localizedMessage)
+ override fun onFailure(failure: Throwable) {
+ if (!isAdded) return
+ onCommonDone(failure.localizedMessage)
}
})
}
- */
}
companion object {
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt
index 76065b63ea..dc8c17b08b 100644
--- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt
@@ -337,7 +337,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
* @param aMyDeviceInfo the device info
*/
private fun refreshCryptographyPreference(devices: List) {
- showDeviceListPref.isEnabled = devices.size > 0
+ showDeviceListPref.isEnabled = devices.isNotEmpty()
showDeviceListPref.summary = resources.getQuantityString(R.plurals.settings_active_sessions_count, devices.size, devices.size)
// val userId = session.myUserId
// val deviceId = session.sessionParams.deviceId
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsEpoxyController.kt
index 8760f9ebb2..722115adf3 100644
--- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsEpoxyController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsEpoxyController.kt
@@ -179,7 +179,7 @@ class GossipingEventsEpoxyController @Inject constructor(
}
private fun buildOutgoing(data: KeyRequestListViewState?) {
- data?.outgoingRoomKeyRequest?.let { async ->
+ data?.outgoingRoomKeyRequests?.let { async ->
when (async) {
is Uninitialized,
is Loading -> {
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt
index b5e1303d89..7721b67118 100644
--- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt
@@ -107,7 +107,7 @@ class KeyRequestEpoxyController @Inject constructor(
}
private fun buildOutgoing(data: KeyRequestListViewState?) {
- data?.outgoingRoomKeyRequest?.let { async ->
+ data?.outgoingRoomKeyRequests?.let { async ->
when (async) {
is Uninitialized,
is Loading -> {
diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListViewModel.kt
index 06d9ffcf7d..db4b4f7d60 100644
--- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListViewModel.kt
@@ -36,7 +36,7 @@ import kotlinx.coroutines.launch
data class KeyRequestListViewState(
val incomingRequests: Async> = Uninitialized,
- val outgoingRoomKeyRequest: Async> = Uninitialized
+ val outgoingRoomKeyRequests: Async> = Uninitialized
) : MvRxState
class KeyRequestListViewModel @AssistedInject constructor(@Assisted initialState: KeyRequestListViewState,
@@ -49,14 +49,14 @@ class KeyRequestListViewModel @AssistedInject constructor(@Assisted initialState
fun refresh() {
viewModelScope.launch {
- session.cryptoService().getOutgoingRoomKeyRequest().let {
+ session.cryptoService().getOutgoingRoomKeyRequests().let {
setState {
copy(
- outgoingRoomKeyRequest = Success(it)
+ outgoingRoomKeyRequests = Success(it)
)
}
}
- session.cryptoService().getIncomingRoomKeyRequest().let {
+ session.cryptoService().getIncomingRoomKeyRequests().let {
setState {
copy(
incomingRequests = Success(it)
diff --git a/vector/src/main/java/im/vector/riotx/features/terms/ReviewTermsFragment.kt b/vector/src/main/java/im/vector/riotx/features/terms/ReviewTermsFragment.kt
index ecf5818300..0adfa049fe 100644
--- a/vector/src/main/java/im/vector/riotx/features/terms/ReviewTermsFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/terms/ReviewTermsFragment.kt
@@ -30,7 +30,7 @@ import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.core.platform.VectorBaseFragment
-import im.vector.riotx.core.utils.openUrlInExternalBrowser
+import im.vector.riotx.core.utils.openUrlInChromeCustomTab
import kotlinx.android.synthetic.main.fragment_review_terms.*
import javax.inject.Inject
@@ -106,6 +106,6 @@ class ReviewTermsFragment @Inject constructor(
}
override fun review(term: Term) {
- openUrlInExternalBrowser(requireContext(), term.url)
+ openUrlInChromeCustomTab(requireContext(), null, term.url)
}
}
diff --git a/vector/src/main/java/im/vector/riotx/features/terms/ReviewTermsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/terms/ReviewTermsViewModel.kt
index 69197b7b59..e19b8e7675 100644
--- a/vector/src/main/java/im/vector/riotx/features/terms/ReviewTermsViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/terms/ReviewTermsViewModel.kt
@@ -139,7 +139,7 @@ class ReviewTermsViewModel @AssistedInject constructor(
)
}
} catch (failure: Throwable) {
- Timber.e(failure, "Failed to agree to terms")
+ Timber.e(failure, "Failed to load terms")
setState {
copy(
termsList = Uninitialized
diff --git a/vector/src/main/java/im/vector/riotx/features/themes/ThemeUtils.kt b/vector/src/main/java/im/vector/riotx/features/themes/ThemeUtils.kt
index 45e64465d6..4878134375 100644
--- a/vector/src/main/java/im/vector/riotx/features/themes/ThemeUtils.kt
+++ b/vector/src/main/java/im/vector/riotx/features/themes/ThemeUtils.kt
@@ -44,6 +44,17 @@ object ThemeUtils {
private val mColorByAttr = HashMap()
+ /**
+ * @return true if current theme is Light or Status
+ */
+ fun isLightTheme(context: Context): Boolean {
+ return when (getApplicationTheme(context)) {
+ THEME_LIGHT_VALUE,
+ THEME_STATUS_VALUE -> true
+ else -> false
+ }
+ }
+
/**
* Provides the selected application theme
*
diff --git a/vector/src/main/java/im/vector/riotx/features/widgets/WidgetFragment.kt b/vector/src/main/java/im/vector/riotx/features/widgets/WidgetFragment.kt
index 14b25d0439..b162149ffe 100644
--- a/vector/src/main/java/im/vector/riotx/features/widgets/WidgetFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/widgets/WidgetFragment.kt
@@ -40,6 +40,7 @@ import im.vector.riotx.R
import im.vector.riotx.core.platform.OnBackPressed
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.utils.openUrlInExternalBrowser
+import im.vector.riotx.features.home.room.detail.widget.WidgetRequestCodes
import im.vector.riotx.features.terms.ReviewTermsActivity
import im.vector.riotx.features.webview.WebViewEventListener
import im.vector.riotx.features.widgets.webview.clearAfterWidget
@@ -77,7 +78,7 @@ class WidgetFragment @Inject constructor() : VectorBaseFragment(), WebViewEventL
Timber.v("Observed view events: $it")
when (it) {
is WidgetViewEvents.DisplayTerms -> displayTerms(it)
- is WidgetViewEvents.LoadFormattedURL -> loadFormattedUrl(it)
+ is WidgetViewEvents.OnURLFormatted -> loadFormattedUrl(it)
is WidgetViewEvents.DisplayIntegrationManager -> displayIntegrationManager(it)
is WidgetViewEvents.Failure -> displayErrorDialog(it.throwable)
}
@@ -86,11 +87,17 @@ class WidgetFragment @Inject constructor() : VectorBaseFragment(), WebViewEventL
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- if (requestCode == ReviewTermsActivity.TERMS_REQUEST_CODE) {
- if (resultCode == Activity.RESULT_OK) {
- viewModel.handle(WidgetAction.OnTermsReviewed)
- } else {
- vectorBaseActivity.finish()
+ when (requestCode) {
+ ReviewTermsActivity.TERMS_REQUEST_CODE -> {
+ Timber.v("On terms results")
+ if (resultCode == Activity.RESULT_OK) {
+ viewModel.handle(WidgetAction.OnTermsReviewed)
+ } else {
+ vectorBaseActivity.finish()
+ }
+ }
+ WidgetRequestCodes.INTEGRATION_MANAGER_REQUEST_CODE -> {
+ viewModel.handle(WidgetAction.LoadFormattedUrl)
}
}
}
@@ -139,7 +146,7 @@ class WidgetFragment @Inject constructor() : VectorBaseFragment(), WebViewEventL
override fun onOptionsItemSelected(item: MenuItem): Boolean = withState(viewModel) { state ->
when (item.itemId) {
R.id.action_edit -> {
- navigator.openIntegrationManager(requireContext(), state.roomId, state.widgetId, state.widgetKind.screenId)
+ navigator.openIntegrationManager(this, state.roomId, state.widgetId, state.widgetKind.screenId)
return@withState true
}
R.id.action_delete -> {
@@ -261,9 +268,9 @@ class WidgetFragment @Inject constructor() : VectorBaseFragment(), WebViewEventL
)
}
- private fun loadFormattedUrl(loadFormattedUrl: WidgetViewEvents.LoadFormattedURL) {
+ private fun loadFormattedUrl(event: WidgetViewEvents.OnURLFormatted) {
widgetWebView.clearHistory()
- widgetWebView.loadUrl(loadFormattedUrl.formattedURL)
+ widgetWebView.loadUrl(event.formattedURL)
}
private fun setStateError(message: String?) {
@@ -280,7 +287,7 @@ class WidgetFragment @Inject constructor() : VectorBaseFragment(), WebViewEventL
private fun displayIntegrationManager(event: WidgetViewEvents.DisplayIntegrationManager) {
navigator.openIntegrationManager(
- context = vectorBaseActivity,
+ fragment = this,
roomId = fragmentArgs.roomId,
integId = event.integId,
screen = event.integType
diff --git a/vector/src/main/java/im/vector/riotx/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/riotx/features/widgets/WidgetPostAPIHandler.kt
index 351a15aad1..7115a2ea62 100644
--- a/vector/src/main/java/im/vector/riotx/features/widgets/WidgetPostAPIHandler.kt
+++ b/vector/src/main/java/im/vector/riotx/features/widgets/WidgetPostAPIHandler.kt
@@ -39,13 +39,12 @@ import java.util.ArrayList
import java.util.HashMap
class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roomId: String,
- @Assisted private val navigationCallback: NavigationCallback,
private val stringProvider: StringProvider,
private val session: Session) : WidgetPostAPIMediator.Handler {
@AssistedInject.Factory
interface Factory {
- fun create(roomId: String, navigationCallback: NavigationCallback): WidgetPostAPIHandler
+ fun create(roomId: String): WidgetPostAPIHandler
}
interface NavigationCallback {
@@ -54,31 +53,31 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
fun openIntegrationManager(integId: String?, integType: String?)
}
- private val widgetPostAPIMediator = session.widgetService().getWidgetPostAPIMediator()
private val room = session.getRoom(roomId)!!
+ var navigationCallback: NavigationCallback? = null
- override fun handleWidgetRequest(eventData: JsonDict): Boolean {
+ override fun handleWidgetRequest(mediator: WidgetPostAPIMediator, eventData: JsonDict): Boolean {
return when (eventData["action"] as String?) {
"integration_manager_open" -> handleIntegrationManagerOpenAction(eventData).run { true }
- "bot_options" -> getBotOptions(eventData).run { true }
- "can_send_event" -> canSendEvent(eventData).run { true }
+ "bot_options" -> getBotOptions(mediator, eventData).run { true }
+ "can_send_event" -> canSendEvent(mediator, eventData).run { true }
"close_scalar" -> handleCloseScalar().run { true }
- "get_membership_count" -> getMembershipCount(eventData).run { true }
- "get_widgets" -> getWidgets(eventData).run { true }
- "invite" -> inviteUser(eventData).run { true }
- "join_rules_state" -> getJoinRules(eventData).run { true }
- "membership_state" -> getMembershipState(eventData).run { true }
- "set_bot_options" -> setBotOptions(eventData).run { true }
- "set_bot_power" -> setBotPower(eventData).run { true }
- "set_plumbing_state" -> setPlumbingState(eventData).run { true }
- "set_widget" -> setWidget(eventData).run { true }
- "m.sticker" -> pickStickerData(eventData).run { true }
+ "get_membership_count" -> getMembershipCount(mediator, eventData).run { true }
+ "get_widgets" -> getWidgets(mediator, eventData).run { true }
+ "invite" -> inviteUser(mediator, eventData).run { true }
+ "join_rules_state" -> getJoinRules(mediator, eventData).run { true }
+ "membership_state" -> getMembershipState(mediator, eventData).run { true }
+ "set_bot_options" -> setBotOptions(mediator, eventData).run { true }
+ "set_bot_power" -> setBotPower(mediator, eventData).run { true }
+ "set_plumbing_state" -> setPlumbingState(mediator, eventData).run { true }
+ "set_widget" -> setWidget(mediator, eventData).run { true }
+ "m.sticker" -> pickStickerData(mediator, eventData).run { true }
else -> false
}
}
private fun handleCloseScalar() {
- navigationCallback.close()
+ navigationCallback?.close()
}
private fun handleIntegrationManagerOpenAction(eventData: JsonDict) {
@@ -101,7 +100,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
// Add "type_" as a prefix
integType?.let { integType = "type_$integType" }
}
- navigationCallback.openIntegrationManager(integId, integType)
+ navigationCallback?.openIntegrationManager(integId, integType)
}
/**
@@ -109,8 +108,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @param eventData the modular data
*/
- private fun getBotOptions(eventData: JsonDict) {
- if (checkRoomId(eventData) || checkUserId(eventData)) {
+ private fun getBotOptions(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
+ if (checkRoomId(widgetPostAPIMediator, eventData) || checkUserId(widgetPostAPIMediator, eventData)) {
return
}
val userId = eventData["user_id"] as String
@@ -134,8 +133,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
}
}
- private fun canSendEvent(eventData: JsonDict) {
- if (checkRoomId(eventData)) {
+ private fun canSendEvent(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
+ if (checkRoomId(widgetPostAPIMediator, eventData)) {
return
}
Timber.d("Received request canSendEvent in room $roomId")
@@ -170,8 +169,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @param eventData the modular data
*/
- private fun getMembershipState(eventData: JsonDict) {
- if (checkRoomId(eventData) || checkUserId(eventData)) {
+ private fun getMembershipState(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
+ if (checkRoomId(widgetPostAPIMediator, eventData) || checkUserId(widgetPostAPIMediator, eventData)) {
return
}
val userId = eventData["user_id"] as String
@@ -189,8 +188,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @param eventData the modular data
*/
- private fun getJoinRules(eventData: JsonDict) {
- if (checkRoomId(eventData)) {
+ private fun getJoinRules(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
+ if (checkRoomId(widgetPostAPIMediator, eventData)) {
return
}
Timber.d("Received request join rules in room $roomId")
@@ -207,8 +206,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @param eventData the modular data
*/
- private fun getWidgets(eventData: JsonDict) {
- if (checkRoomId(eventData)) {
+ private fun getWidgets(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
+ if (checkRoomId(widgetPostAPIMediator, eventData)) {
return
}
Timber.d("Received request to get widget in room $roomId")
@@ -227,12 +226,12 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @param eventData the modular data
*/
- private fun setWidget(eventData: JsonDict) {
+ private fun setWidget(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
val userWidget = eventData["userWidget"] as Boolean?
if (userWidget == true) {
Timber.d("Received request to set widget for user")
} else {
- if (checkRoomId(eventData)) {
+ if (checkRoomId(widgetPostAPIMediator, eventData)) {
return
}
Timber.d("Received request to set widget in room $roomId")
@@ -283,14 +282,14 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
session.updateAccountData(
type = UserAccountData.TYPE_WIDGETS,
content = addUserWidgetBody,
- callback = createWidgetAPICallback(eventData)
+ callback = createWidgetAPICallback(widgetPostAPIMediator, eventData)
)
} else {
session.widgetService().createRoomWidget(
roomId = roomId,
widgetId = widgetId,
content = widgetEventContent,
- callback = createWidgetAPICallback(eventData)
+ callback = createWidgetAPICallback(widgetPostAPIMediator, eventData)
)
}
}
@@ -300,8 +299,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @param eventData the modular data
*/
- private fun setPlumbingState(eventData: JsonDict) {
- if (checkRoomId(eventData)) {
+ private fun setPlumbingState(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
+ if (checkRoomId(widgetPostAPIMediator, eventData)) {
return
}
val description = "Received request to set plumbing state to status " + eventData["status"] + " in room " + roomId + " requested"
@@ -315,7 +314,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
eventType = EventType.PLUMBING,
stateKey = null,
body = params,
- callback = createWidgetAPICallback(eventData)
+ callback = createWidgetAPICallback(widgetPostAPIMediator, eventData)
)
}
@@ -325,8 +324,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
* @param eventData the modular data
*/
@Suppress("UNCHECKED_CAST")
- private fun setBotOptions(eventData: JsonDict) {
- if (checkRoomId(eventData) || checkUserId(eventData)) {
+ private fun setBotOptions(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
+ if (checkRoomId(widgetPostAPIMediator, eventData) || checkUserId(widgetPostAPIMediator, eventData)) {
return
}
val userId = eventData["user_id"] as String
@@ -338,7 +337,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
eventType = EventType.BOT_OPTIONS,
stateKey = stateKey,
body = content,
- callback = createWidgetAPICallback(eventData)
+ callback = createWidgetAPICallback(widgetPostAPIMediator, eventData)
)
}
@@ -347,8 +346,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @param eventData the modular data
*/
- private fun setBotPower(eventData: JsonDict) {
- if (checkRoomId(eventData) || checkUserId(eventData)) {
+ private fun setBotPower(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
+ if (checkRoomId(widgetPostAPIMediator, eventData) || checkUserId(widgetPostAPIMediator, eventData)) {
return
}
val userId = eventData["user_id"] as String
@@ -369,8 +368,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @param eventData the modular data
*/
- private fun inviteUser(eventData: JsonDict) {
- if (checkRoomId(eventData) || checkUserId(eventData)) {
+ private fun inviteUser(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
+ if (checkRoomId(widgetPostAPIMediator, eventData) || checkUserId(widgetPostAPIMediator, eventData)) {
return
}
val userId = eventData["user_id"] as String
@@ -380,7 +379,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
if (member != null && member.membership == Membership.JOIN) {
widgetPostAPIMediator.sendSuccess(eventData)
} else {
- room.invite(userId = userId, callback = createWidgetAPICallback(eventData))
+ room.invite(userId = userId, callback = createWidgetAPICallback(widgetPostAPIMediator, eventData))
}
}
@@ -389,8 +388,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @param eventData the modular data
*/
- private fun getMembershipCount(eventData: JsonDict) {
- if (checkRoomId(eventData)) {
+ private fun getMembershipCount(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
+ if (checkRoomId(widgetPostAPIMediator, eventData)) {
return
}
val numberOfJoinedMembers = room.getNumberOfJoinedMembers()
@@ -398,7 +397,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
}
@Suppress("UNCHECKED_CAST")
- private fun pickStickerData(eventData: JsonDict) {
+ private fun pickStickerData(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
Timber.d("Received request send sticker")
val data = eventData["data"]
if (data == null) {
@@ -411,7 +410,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
return
}
widgetPostAPIMediator.sendSuccess(eventData)
- navigationCallback.closeWithResult(content)
+ navigationCallback?.closeWithResult(content)
}
/**
@@ -420,7 +419,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @return true in case of error
*/
- private fun checkRoomId(eventData: JsonDict): Boolean {
+ private fun checkRoomId(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict): Boolean {
val roomIdInEvent = eventData["room_id"] as String?
// Check if param is present
if (null == roomIdInEvent) {
@@ -443,7 +442,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
*
* @return true in case of error
*/
- private fun checkUserId(eventData: JsonDict): Boolean {
+ private fun checkUserId(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict): Boolean {
val userIdInEvent = eventData["user_id"] as String?
// Check if param is present
if (null == userIdInEvent) {
@@ -454,7 +453,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
return false
}
- private fun createWidgetAPICallback(eventData: JsonDict): WidgetAPICallback {
+ private fun createWidgetAPICallback(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict): WidgetAPICallback {
return WidgetAPICallback(widgetPostAPIMediator, eventData, stringProvider)
}
}
diff --git a/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewEvents.kt
index 7750f2dd68..5b40e0441d 100644
--- a/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewEvents.kt
+++ b/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewEvents.kt
@@ -23,6 +23,6 @@ sealed class WidgetViewEvents : VectorViewEvents {
data class Failure(val throwable: Throwable): WidgetViewEvents()
data class Close(val content: Content? = null) : WidgetViewEvents()
data class DisplayIntegrationManager(val integId: String?, val integType: String?) : WidgetViewEvents()
- data class LoadFormattedURL(val formattedURL: String) : WidgetViewEvents()
+ data class OnURLFormatted(val formattedURL: String) : WidgetViewEvents()
data class DisplayTerms(val url: String, val token: String) : WidgetViewEvents()
}
diff --git a/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewModel.kt
index d81e1efea4..d516137bc5 100644
--- a/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewModel.kt
@@ -76,13 +76,22 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi
private val integrationManagerService = session.integrationManagerService()
private val widgetURLFormatter = widgetService.getWidgetURLFormatter()
private val postAPIMediator = widgetService.getWidgetPostAPIMediator()
+ private var widgetPostAPIHandler: WidgetPostAPIHandler? = null
+
+ // Flag to avoid infinite loop
+ private var canRefreshToken = true
init {
integrationManagerService.addListener(this)
if (initialState.widgetKind.isAdmin()) {
- val widgetPostAPIHandler = widgetPostAPIHandlerFactory.create(initialState.roomId, this)
+ widgetPostAPIHandler = widgetPostAPIHandlerFactory.create(initialState.roomId).apply {
+ navigationCallback = this@WidgetViewModel
+ }
postAPIMediator.setHandler(widgetPostAPIHandler)
}
+ if (!integrationManagerService.isIntegrationEnabled()) {
+ _viewEvents.post(WidgetViewEvents.Close(null))
+ }
setupName()
refreshPermissionStatus()
observePowerLevel()
@@ -139,10 +148,10 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi
is WidgetAction.OnWebViewLoadingError -> handleWebViewLoadingError(action)
is WidgetAction.OnWebViewLoadingSuccess -> handleWebViewLoadingSuccess(action)
is WidgetAction.OnWebViewStartedToLoad -> handleWebViewStartLoading()
- WidgetAction.LoadFormattedUrl -> loadFormattedUrl()
+ WidgetAction.LoadFormattedUrl -> loadFormattedUrl(forceFetchToken = false)
WidgetAction.DeleteWidget -> handleDeleteWidget()
WidgetAction.RevokeWidget -> handleRevokeWidget()
- WidgetAction.OnTermsReviewed -> refreshPermissionStatus()
+ WidgetAction.OnTermsReviewed -> loadFormattedUrl(forceFetchToken = false)
}
}
@@ -224,10 +233,10 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi
)
setState { copy(formattedURL = Success(formattedUrl)) }
Timber.v("Post load formatted url event: $formattedUrl")
- _viewEvents.post(WidgetViewEvents.LoadFormattedURL(formattedUrl))
+ _viewEvents.post(WidgetViewEvents.OnURLFormatted(formattedUrl))
} catch (failure: Throwable) {
if (failure is WidgetManagementFailure.TermsNotSignedException) {
- _viewEvents.post(WidgetViewEvents.DisplayTerms(failure.baseUrl, failure.token))
+ _viewEvents.post(WidgetViewEvents.DisplayTerms(initialState.baseUrl, failure.token))
}
setState { copy(formattedURL = Fail(failure)) }
}
@@ -251,7 +260,8 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi
}
if (action.isHttpError) {
// In case of 403, try to refresh the scalar token
- if (it.formattedURL is Success && action.errorCode == HttpsURLConnection.HTTP_FORBIDDEN) {
+ if (it.formattedURL is Success && action.errorCode == HttpsURLConnection.HTTP_FORBIDDEN && canRefreshToken) {
+ canRefreshToken = false
loadFormattedUrl(true)
}
} else {
@@ -261,17 +271,24 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi
override fun onCleared() {
integrationManagerService.removeListener(this)
+ widgetPostAPIHandler?.navigationCallback = null
postAPIMediator.setHandler(null)
super.onCleared()
}
-// IntegrationManagerService.Listener
+ // IntegrationManagerService.Listener
override fun onWidgetPermissionsChanged(widgets: Map) {
refreshPermissionStatus()
}
-// WidgetPostAPIHandler.NavigationCallback
+ override fun onIsEnabledChanged(enabled: Boolean) {
+ if (!enabled) {
+ _viewEvents.post(WidgetViewEvents.Close(null))
+ }
+ }
+
+ // WidgetPostAPIHandler.NavigationCallback
override fun close() {
_viewEvents.post(WidgetViewEvents.Close(null))
diff --git a/vector/src/main/java/im/vector/riotx/features/widgets/webview/WebviewPermissionUtils.kt b/vector/src/main/java/im/vector/riotx/features/widgets/webview/WebviewPermissionUtils.kt
index 32f6a906e2..fa0f4e0a97 100644
--- a/vector/src/main/java/im/vector/riotx/features/widgets/webview/WebviewPermissionUtils.kt
+++ b/vector/src/main/java/im/vector/riotx/features/widgets/webview/WebviewPermissionUtils.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package im.vector.fragments.roomwidgets
+package im.vector.riotx.features.widgets.webview
import android.annotation.SuppressLint
import android.content.Context
diff --git a/vector/src/main/java/im/vector/riotx/features/widgets/webview/WidgetWebView.kt b/vector/src/main/java/im/vector/riotx/features/widgets/webview/WidgetWebView.kt
index 68cbe76531..6b5ade06c4 100644
--- a/vector/src/main/java/im/vector/riotx/features/widgets/webview/WidgetWebView.kt
+++ b/vector/src/main/java/im/vector/riotx/features/widgets/webview/WidgetWebView.kt
@@ -24,7 +24,6 @@ import android.webkit.PermissionRequest
import android.webkit.WebChromeClient
import android.webkit.WebSettings
import android.webkit.WebView
-import im.vector.fragments.roomwidgets.WebviewPermissionUtils
import im.vector.riotx.R
import im.vector.riotx.features.themes.ThemeUtils
import im.vector.riotx.features.webview.VectorWebViewClient
@@ -81,22 +80,10 @@ fun WebView.clearAfterWidget() {
webChromeClient = null
webViewClient = null
clearHistory()
-
// NOTE: clears RAM cache, if you pass true, it will also clear the disk cache.
clearCache(true)
-
// Loading a blank page is optional, but will ensure that the WebView isn't doing anything when you destroy it.
loadUrl("about:blank")
-
- onPause()
removeAllViews()
-
- // NOTE: This pauses JavaScript execution for ALL WebViews,
- // do not use if you have other WebViews still alive.
- // If you create another WebView after calling this,
- // make sure to call mWebView.resumeTimers().
- pauseTimers()
-
- // NOTE: This can occasionally cause a segfault below API 17 (4.2)
destroy()
}
diff --git a/vector/src/main/res/drawable/bg_login_server.xml b/vector/src/main/res/drawable/bg_login_server.xml
index 5aecd26292..a2ca14f02e 100644
--- a/vector/src/main/res/drawable/bg_login_server.xml
+++ b/vector/src/main/res/drawable/bg_login_server.xml
@@ -7,6 +7,6 @@
android:width="1.2dp"
android:color="#E7E7E7" />
-
+
\ No newline at end of file
diff --git a/vector/src/main/res/drawable/bg_login_server_checked.xml b/vector/src/main/res/drawable/bg_login_server_checked.xml
index 1aea622462..f120e62884 100644
--- a/vector/src/main/res/drawable/bg_login_server_checked.xml
+++ b/vector/src/main/res/drawable/bg_login_server_checked.xml
@@ -7,6 +7,6 @@
android:width="1.2dp"
android:color="@color/riotx_accent" />
-
+
\ No newline at end of file
diff --git a/vector/src/main/res/drawable/ic_airplane_16dp.xml b/vector/src/main/res/drawable/ic_airplane_16dp.xml
new file mode 100644
index 0000000000..fa2387ebf0
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_airplane_16dp.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/vector/src/main/res/drawable/ic_call.xml b/vector/src/main/res/drawable/ic_call.xml
new file mode 100644
index 0000000000..430c438577
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_call.xml
@@ -0,0 +1,14 @@
+
+
+
diff --git a/vector/src/main/res/drawable/ic_call_end.xml b/vector/src/main/res/drawable/ic_call_end.xml
new file mode 100644
index 0000000000..07f7e01351
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_call_end.xml
@@ -0,0 +1,14 @@
+
+
+
diff --git a/vector/src/main/res/drawable/ic_call_flip_camera_active.xml b/vector/src/main/res/drawable/ic_call_flip_camera_active.xml
new file mode 100644
index 0000000000..25590cc753
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_call_flip_camera_active.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/vector/src/main/res/drawable/ic_call_flip_camera_default.xml b/vector/src/main/res/drawable/ic_call_flip_camera_default.xml
new file mode 100644
index 0000000000..75ad0133f8
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_call_flip_camera_default.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/vector/src/main/res/drawable/ic_call_mute_active.xml b/vector/src/main/res/drawable/ic_call_mute_active.xml
new file mode 100644
index 0000000000..757f9cfa17
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_call_mute_active.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/vector/src/main/res/drawable/ic_call_mute_default.xml b/vector/src/main/res/drawable/ic_call_mute_default.xml
new file mode 100644
index 0000000000..37a0c83fec
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_call_mute_default.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/vector/src/main/res/drawable/ic_call_speaker_active.xml b/vector/src/main/res/drawable/ic_call_speaker_active.xml
new file mode 100644
index 0000000000..97035b1915
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_call_speaker_active.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/vector/src/main/res/drawable/ic_call_speaker_default.xml b/vector/src/main/res/drawable/ic_call_speaker_default.xml
new file mode 100644
index 0000000000..2fc06a5795
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_call_speaker_default.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/vector/src/main/res/drawable/ic_call_videocam_off_active.xml b/vector/src/main/res/drawable/ic_call_videocam_off_active.xml
new file mode 100644
index 0000000000..106317ed56
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_call_videocam_off_active.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/vector/src/main/res/drawable/ic_call_videocam_off_default.xml b/vector/src/main/res/drawable/ic_call_videocam_off_default.xml
new file mode 100644
index 0000000000..0b3d9baf04
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_call_videocam_off_default.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/vector/src/main/res/drawable/ic_hd.xml b/vector/src/main/res/drawable/ic_hd.xml
new file mode 100644
index 0000000000..3335724529
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_hd.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/vector/src/main/res/drawable/ic_hd_disabled.xml b/vector/src/main/res/drawable/ic_hd_disabled.xml
new file mode 100644
index 0000000000..6396b7bc7e
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_hd_disabled.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/vector/src/main/res/drawable/ic_microphone_off.xml b/vector/src/main/res/drawable/ic_microphone_off.xml
new file mode 100644
index 0000000000..92d5044902
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_microphone_off.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/drawable/ic_microphone_on.xml b/vector/src/main/res/drawable/ic_microphone_on.xml
new file mode 100644
index 0000000000..aaa9987860
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_microphone_on.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/vector/src/main/res/drawable/ic_more_vertical.xml b/vector/src/main/res/drawable/ic_more_vertical.xml
new file mode 100644
index 0000000000..9289a8cacb
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_more_vertical.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
diff --git a/vector/src/main/res/drawable/ic_phone.xml b/vector/src/main/res/drawable/ic_phone.xml
new file mode 100644
index 0000000000..430c438577
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_phone.xml
@@ -0,0 +1,14 @@
+
+
+
diff --git a/vector/src/main/res/drawable/ic_video.xml b/vector/src/main/res/drawable/ic_video.xml
new file mode 100644
index 0000000000..f9c57db65e
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_video.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/vector/src/main/res/drawable/ic_video_flip.xml b/vector/src/main/res/drawable/ic_video_flip.xml
new file mode 100644
index 0000000000..0cc540b9fb
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_video_flip.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/drawable/ic_video_off.xml b/vector/src/main/res/drawable/ic_video_off.xml
new file mode 100644
index 0000000000..34abdb5b51
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_video_off.xml
@@ -0,0 +1,20 @@
+
+
+
+
diff --git a/vector/src/main/res/drawable/ic_videocam.xml b/vector/src/main/res/drawable/ic_videocam.xml
new file mode 100644
index 0000000000..b7a50f9a57
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_videocam.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/vector/src/main/res/drawable/oval_destructive.xml b/vector/src/main/res/drawable/oval_destructive.xml
new file mode 100644
index 0000000000..045a50456d
--- /dev/null
+++ b/vector/src/main/res/drawable/oval_destructive.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/drawable/oval_positive.xml b/vector/src/main/res/drawable/oval_positive.xml
new file mode 100644
index 0000000000..d2e17d746b
--- /dev/null
+++ b/vector/src/main/res/drawable/oval_positive.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/activity_call.xml b/vector/src/main/res/layout/activity_call.xml
new file mode 100644
index 0000000000..39d0bef790
--- /dev/null
+++ b/vector/src/main/res/layout/activity_call.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/bottom_sheet_call_controls.xml b/vector/src/main/res/layout/bottom_sheet_call_controls.xml
new file mode 100644
index 0000000000..04cb2af20d
--- /dev/null
+++ b/vector/src/main/res/layout/bottom_sheet_call_controls.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/fragment_home_detail.xml b/vector/src/main/res/layout/fragment_home_detail.xml
index ae2e251d97..f90422dff9 100644
--- a/vector/src/main/res/layout/fragment_home_detail.xml
+++ b/vector/src/main/res/layout/fragment_home_detail.xml
@@ -63,13 +63,42 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/syncStateView" />
+
+
+ app:layout_constraintTop_toBottomOf="@+id/activeCallView" />
+
+
+
+
+
+
+ android:src="@drawable/ic_logo_modular"
+ android:tint="?riotx_text_primary" />
-
@@ -97,6 +96,14 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomToolbar" />
+
+
-
+ app:layout_constraintTop_toBottomOf="@id/activeCallView">
+
+
+
+
+
-
diff --git a/vector/src/main/res/layout/item_room_category.xml b/vector/src/main/res/layout/item_room_category.xml
index 38c315aff5..519fbb7131 100644
--- a/vector/src/main/res/layout/item_room_category.xml
+++ b/vector/src/main/res/layout/item_room_category.xml
@@ -4,33 +4,32 @@
android:id="@+id/roomCategoryRootView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="?riotx_header_panel_background"
+ android:background="?riotx_background"
android:clickable="true"
android:focusable="true"
+ android:foreground="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
- android:paddingStart="@dimen/layout_horizontal_margin"
- android:paddingLeft="@dimen/layout_horizontal_margin"
- android:paddingTop="@dimen/layout_vertical_margin"
+ android:paddingStart="8dp"
+ android:paddingLeft="8dp"
+ android:paddingTop="12dp"
android:paddingEnd="@dimen/layout_horizontal_margin"
- android:foreground="?attr/selectableItemBackground"
android:paddingRight="@dimen/layout_horizontal_margin"
- android:paddingBottom="8dp">
+ android:paddingBottom="4dp">
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/view_call_controls.xml b/vector/src/main/res/layout/view_call_controls.xml
new file mode 100644
index 0000000000..94757c2c72
--- /dev/null
+++ b/vector/src/main/res/layout/view_call_controls.xml
@@ -0,0 +1,205 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/view_sync_state.xml b/vector/src/main/res/layout/view_sync_state.xml
index 0e7ddabc21..49b3bb3857 100644
--- a/vector/src/main/res/layout/view_sync_state.xml
+++ b/vector/src/main/res/layout/view_sync_state.xml
@@ -1,5 +1,6 @@
-
+ tools:visibility="visible">
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/menu/menu_timeline.xml b/vector/src/main/res/menu/menu_timeline.xml
index a4adb203f2..d4eb923d50 100644
--- a/vector/src/main/res/menu/menu_timeline.xml
+++ b/vector/src/main/res/menu/menu_timeline.xml
@@ -3,11 +3,38 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
+
+
+
+
+
+
- #FFF8E3
#22262E
-
\ 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 19afc120cb..9b03ef1ee5 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -211,6 +211,20 @@
Try using %s
Do not ask me again
+ RiotX Call Failed
+ Failed to establish real time connection.\nPlease ask the administrator of your homeserver to configure a TURN server in order for calls to work reliably.
+
+ Select Sound Device
+ Phone
+ Speaker
+ Headset
+ Wireless Headset
+ Switch Camera
+ Front
+ Back
+ Turn HD off
+ Turn HD on
+
Send files
Send sticker
Take photo or video
@@ -362,6 +376,8 @@
Incoming Voice Call
Call In Progressβ¦
Video Call In Progressβ¦
+ Active Call (%s)
+ Return to call
The remote side failed to pick up.
Media Connection Failed
@@ -461,7 +477,7 @@
ADMIN TOOLS
CALL
- DIRECT CHATS
+ Direct Messages
SESSIONS
Invite
@@ -862,6 +878,9 @@
Allow integrations
Integration Manager
+ Integrations are disabled
+ "Enable 'Allow integrations' in Settings to do this."
+
User interface
Language
Choose language
@@ -1162,6 +1181,7 @@
Read DRM protected Media
+
Unable to create widget.
Failed to send request.
Power level must be positive integer.
@@ -2452,5 +2472,10 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
Enter the URL of an identity server
Submit
Set role
+ Open chat
+ Mute the microphone
+ Unmute the microphone
+ Stop the camera
+ Start the camera
diff --git a/vector/src/main/res/xml/vector_settings_general.xml b/vector/src/main/res/xml/vector_settings_general.xml
index 846380465e..f79fbc71e2 100644
--- a/vector/src/main/res/xml/vector_settings_general.xml
+++ b/vector/src/main/res/xml/vector_settings_general.xml
@@ -14,6 +14,7 @@
@@ -68,6 +69,26 @@
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-