From 8eeae51cc613fb9e257dc3aed499efa3b246cbfe Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 25 May 2021 15:21:54 +0200 Subject: [PATCH 01/34] Call transfer: prepare code for consult feature --- .../sdk/api/session/call/CallIdGenerator.kt | 23 ++++++++++++++ .../android/sdk/api/session/call/MxCall.kt | 5 ++- .../room/model/call/CallHangupContent.kt | 3 ++ .../room/model/call/CallReplacesContent.kt | 2 +- .../internal/session/call/MxCallFactory.kt | 3 +- .../internal/session/call/model/MxCallImpl.kt | 13 +++++--- .../call/transfer/CallTransferViewModel.kt | 6 ++-- .../app/features/call/webrtc/WebRtcCall.kt | 31 ++++++++++++++++++- 8 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallIdGenerator.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallIdGenerator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallIdGenerator.kt new file mode 100644 index 0000000000..43e6872525 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallIdGenerator.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.call + +import java.util.UUID + +object CallIdGenerator { + fun generate() = UUID.randomUUID().toString() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt index 7533619eb0..a8a3cf58aa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt @@ -92,7 +92,10 @@ interface MxCall : MxCallDetail { /** * Send a m.call.replaces event to initiate call transfer. */ - suspend fun transfer(targetUserId: String, targetRoomId: String?) + suspend fun transfer(targetUserId: String, + targetRoomId: String?, + createCallId: String?, + awaitCallId: String?) fun addListener(listener: StateListener) fun removeListener(listener: StateListener) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt index 0acc409053..7edf2dfdc8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt @@ -56,6 +56,9 @@ data class CallHangupContent( @Json(name = "user_hangup") USER_HANGUP, + @Json(name = "replaced") + REPLACED, + @Json(name = "user_media_failed") USER_MEDIA_FAILED, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt index 97a3b8c7a7..8746bffda1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt @@ -42,7 +42,7 @@ data class CallReplacesContent( * (possibly waiting for user confirmation) and then continues the transfer in this room. * If absent, the transferee contacts the Matrix User ID given in the target_user field in a room of its choosing. */ - @Json(name = "target_room") val targerRoomId: String? = null, + @Json(name = "target_room") val targetRoomId: String? = null, /** * An object giving information about the transfer target */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt index b14cdca63c..b6aed98504 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.call import org.matrix.android.sdk.api.MatrixConfiguration +import org.matrix.android.sdk.api.session.call.CallIdGenerator import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent @@ -63,7 +64,7 @@ internal class MxCallFactory @Inject constructor( fun createOutgoingCall(roomId: String, opponentUserId: String, isVideoCall: Boolean): MxCall { return MxCallImpl( - callId = UUID.randomUUID().toString(), + callId = CallIdGenerator.generate(), isOutgoing = true, roomId = roomId, userId = userId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt index 88fba0ea85..6db2989a2e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.call.model import org.matrix.android.sdk.api.MatrixConfiguration +import org.matrix.android.sdk.api.session.call.CallIdGenerator import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.events.model.Content @@ -202,7 +203,10 @@ internal class MxCallImpl( .also { eventSenderProcessor.postEvent(it) } } - override suspend fun transfer(targetUserId: String, targetRoomId: String?) { + override suspend fun transfer(targetUserId: String, + targetRoomId: String?, + createCallId: String?, + awaitCallId: String?) { val profileInfoParams = GetProfileInfoTask.Params(targetUserId) val profileInfo = try { getProfileInfoTask.execute(profileInfoParams) @@ -213,15 +217,16 @@ internal class MxCallImpl( CallReplacesContent( callId = callId, partyId = ourPartyId, - replacementId = UUID.randomUUID().toString(), + replacementId = CallIdGenerator.generate(), version = MxCall.VOIP_PROTO_VERSION.toString(), targetUser = CallReplacesContent.TargetUser( id = targetUserId, displayName = profileInfo?.get(ProfileService.DISPLAY_NAME_KEY) as? String, avatarUrl = profileInfo?.get(ProfileService.AVATAR_URL_KEY) as? String ), - targerRoomId = targetRoomId, - createCall = UUID.randomUUID().toString() + targetRoomId = targetRoomId, + awaitCall = awaitCallId, + createCall = createCallId ) .let { createEventAndLocalEcho(type = EventType.CALL_REPLACES, roomId = roomId, content = it.toContent()) } .also { eventSenderProcessor.postEvent(it) } diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt index 5f661faf80..b2371fcdec 100644 --- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt @@ -75,7 +75,7 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: override fun handle(action: CallTransferAction) { when (action) { - is CallTransferAction.ConnectWithUserId -> connectWithUserId(action) + is CallTransferAction.ConnectWithUserId -> connectWithUserId(action) is CallTransferAction.ConnectWithPhoneNumber -> connectWithPhoneNumber(action) }.exhaustive } @@ -84,7 +84,7 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: viewModelScope.launch { try { _viewEvents.post(CallTransferViewEvents.Loading) - call?.mxCall?.transfer(action.selectedUserId, null) + call?.transferToUser(action.selectedUserId, null) _viewEvents.post(CallTransferViewEvents.Dismiss) } catch (failure: Throwable) { _viewEvents.post(CallTransferViewEvents.FailToTransfer) @@ -97,7 +97,7 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: try { _viewEvents.post(CallTransferViewEvents.Loading) val result = dialPadLookup.lookupPhoneNumber(action.phoneNumber) - call?.mxCall?.transfer(result.userId, result.roomId) + call?.transferToUser(result.userId, result.roomId) _viewEvents.post(CallTransferViewEvents.Dismiss) } catch (failure: Throwable) { _viewEvents.post(CallTransferViewEvents.FailToTransfer) diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt index a3a1a29c4b..4aed01965b 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt @@ -45,6 +45,7 @@ import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.call.CallIdGenerator import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.call.MxPeerConnectionState @@ -268,7 +269,7 @@ class WebRtcCall(val mxCall: MxCall, sessionScope?.launch(dispatcher) { when (mode) { - VectorCallActivity.INCOMING_ACCEPT -> { + VectorCallActivity.INCOMING_ACCEPT -> { internalAcceptIncomingCall() } VectorCallActivity.INCOMING_RINGING -> { @@ -286,6 +287,34 @@ class WebRtcCall(val mxCall: MxCall, } } + suspend fun transferToUser(targetUserId: String, targetRoomId: String?) { + mxCall.transfer( + targetUserId = targetUserId, + targetRoomId = targetRoomId, + createCallId = CallIdGenerator.generate(), + awaitCallId = null + ) + endCall(true, CallHangupContent.Reason.REPLACED) + } + + suspend fun transferToCall(transferTargetCall: WebRtcCall) { + val newCallId = CallIdGenerator.generate() + transferTargetCall.mxCall.transfer( + targetUserId = this.mxCall.opponentUserId, + targetRoomId = null, + createCallId = null, + awaitCallId = newCallId + ) + this.mxCall.transfer( + transferTargetCall.mxCall.opponentUserId, + targetRoomId = null, + createCallId = newCallId, + awaitCallId = null + ) + endCall(true, CallHangupContent.Reason.REPLACED) + transferTargetCall.endCall(true, CallHangupContent.Reason.REPLACED) + } + fun acceptIncomingCall() { sessionScope?.launch { Timber.v("## VOIP acceptIncomingCall from state ${mxCall.state}") From bd8e46c84f8ea4b3739f990f66143b71963ebe7e Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 26 May 2021 12:09:59 +0200 Subject: [PATCH 02/34] Call transfer: start branching consult first action --- .../app/features/call/VectorCallActivity.kt | 15 +++++++-- .../features/call/VectorCallViewActions.kt | 2 ++ .../app/features/call/VectorCallViewModel.kt | 26 +++++++++++++-- .../app/features/call/VectorCallViewState.kt | 4 ++- .../call/transfer/CallTransferViewModel.kt | 33 +++++++++++++++---- .../app/features/call/webrtc/WebRtcCall.kt | 12 +++---- .../features/call/webrtc/WebRtcCallManager.kt | 15 +++++++-- .../res/layout/activity_call_transfer.xml | 1 - vector/src/main/res/values/strings.xml | 3 +- 9 files changed, 90 insertions(+), 21 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt index a9e2982714..6e5a0d5ace 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt @@ -198,7 +198,14 @@ class VectorCallActivity : VectorBaseActivity(), CallContro } is CallState.Connected -> { if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) { - if (state.isLocalOnHold || state.isRemoteOnHold) { + if(state.transfereeName.hasValue()){ + views.callActionText.text = getString(R.string.call_transfer_transfer_to_title, state.transfereeName.get()) + views.callActionText.isVisible = true + views.callActionText.setOnClickListener { callViewModel.handle(VectorCallViewActions.TransferCall) } + views.callStatusText.text = state.formattedDuration + configureCallInfo(state) + } + else if (state.isLocalOnHold || state.isRemoteOnHold) { views.smallIsHeldIcon.isVisible = true views.callVideoGroup.isInvisible = true views.callInfoGroup.isVisible = true @@ -247,7 +254,11 @@ class VectorCallActivity : VectorBaseActivity(), CallContro state.callInfo.otherUserItem?.let { val colorFilter = ContextCompat.getColor(this, R.color.bg_call_screen) avatarRenderer.renderBlur(it, views.bgCallView, sampling = 20, rounded = false, colorFilter = colorFilter) - views.participantNameText.text = it.getBestName() + if(state.transfereeName.hasValue()) { + views.participantNameText.text = getString(R.string.call_transfer_consulting_with, it.getBestName()) + }else { + views.participantNameText.text = it.getBestName() + } if (blurAvatar) { avatarRenderer.renderBlur(it, views.otherMemberAvatar, sampling = 2, rounded = true, colorFilter = colorFilter) } else { diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt index 7addabf724..804d272d7f 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt @@ -18,6 +18,7 @@ package im.vector.app.features.call import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.features.call.audio.CallAudioManager +import im.vector.app.features.call.webrtc.WebRtcCall sealed class VectorCallViewActions : VectorViewModelAction { object EndCall : VectorCallViewActions() @@ -34,4 +35,5 @@ sealed class VectorCallViewActions : VectorViewModelAction { object ToggleCamera : VectorCallViewActions() object ToggleHDSD : VectorCallViewActions() object InitiateCallTransfer : VectorCallViewActions() + object TransferCall: VectorCallViewActions() } diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt index 8a2d56a5a2..c00326f05e 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt @@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.call.MxPeerConnectionState import org.matrix.android.sdk.api.session.room.model.call.supportCallTransfer import org.matrix.android.sdk.api.util.MatrixItem +import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toMatrixItem class VectorCallViewModel @AssistedInject constructor( @@ -109,15 +110,24 @@ class VectorCallViewModel @AssistedInject constructor( } } } + val transfereeName = computeTransfereeNameIfAny(call) setState { copy( callState = Success(callState), - canOpponentBeTransferred = call.capabilities.supportCallTransfer() + canOpponentBeTransferred = call.capabilities.supportCallTransfer(), + transfereeName = transfereeName ) } } } + private fun computeTransfereeNameIfAny(call: MxCall): Optional { + val transfereeCall = callManager.getTransfereeForCallId(call.callId) ?: return Optional.empty() + val transfereeRoom = session.getRoomSummary(transfereeCall.roomId) + val transfereeName = transfereeRoom?.displayName ?: "Unknown person" + return Optional.from(transfereeName) + } + private val currentCallListener = object : WebRtcCallManager.CurrentCallListener { override fun onCurrentCallChange(call: WebRtcCall?) { @@ -186,7 +196,8 @@ class VectorCallViewModel @AssistedInject constructor( canSwitchCamera = webRtcCall.canSwitchCamera(), formattedDuration = webRtcCall.formattedDuration(), isHD = webRtcCall.mxCall.isVideoCall && webRtcCall.currentCaptureFormat() is CaptureFormat.HD, - canOpponentBeTransferred = webRtcCall.mxCall.capabilities.supportCallTransfer() + canOpponentBeTransferred = webRtcCall.mxCall.capabilities.supportCallTransfer(), + transfereeName = computeTransfereeNameIfAny(webRtcCall.mxCall) ) } updateOtherKnownCall(webRtcCall) @@ -273,9 +284,20 @@ class VectorCallViewModel @AssistedInject constructor( VectorCallViewEvents.ShowCallTransferScreen ) } + VectorCallViewActions.TransferCall -> { + handleCallTransfer() + } }.exhaustive } + private fun handleCallTransfer() { + viewModelScope.launch { + val currentCall = call ?: return@launch + val transfereeCall = callManager.getTransfereeForCallId(currentCall.callId) ?: return@launch + currentCall.transferToCall(transfereeCall) + } + } + @AssistedFactory interface Factory { fun create(initialState: VectorCallViewState): VectorCallViewModel diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt index cdd002114a..4129004d7e 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt @@ -22,6 +22,7 @@ import com.airbnb.mvrx.Uninitialized import im.vector.app.features.call.audio.CallAudioManager import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.util.MatrixItem +import org.matrix.android.sdk.api.util.Optional data class VectorCallViewState( val callId: String, @@ -41,7 +42,8 @@ data class VectorCallViewState( val otherKnownCallInfo: CallInfo? = null, val callInfo: CallInfo = CallInfo(callId), val formattedDuration: String = "", - val canOpponentBeTransferred: Boolean = false + val canOpponentBeTransferred: Boolean = false, + val transfereeName: Optional = Optional.empty() ) : MvRxState { data class CallInfo( diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt index b2371fcdec..d6745d9592 100644 --- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt @@ -28,13 +28,16 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.call.dialpad.DialPadLookup import im.vector.app.features.call.webrtc.WebRtcCall import im.vector.app.features.call.webrtc.WebRtcCallManager +import im.vector.app.features.createdirect.DirectRoomHelper import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.session.call.MxCall class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: CallTransferViewState, private val dialPadLookup: DialPadLookup, - callManager: WebRtcCallManager) + private val directRoomHelper: DirectRoomHelper, + private val callManager: WebRtcCallManager) : VectorViewModel(initialState) { @AssistedFactory @@ -83,9 +86,18 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: private fun connectWithUserId(action: CallTransferAction.ConnectWithUserId) { viewModelScope.launch { try { - _viewEvents.post(CallTransferViewEvents.Loading) - call?.transferToUser(action.selectedUserId, null) - _viewEvents.post(CallTransferViewEvents.Dismiss) + if (action.consultFirst) { + val dmRoomId = directRoomHelper.ensureDMExists(action.selectedUserId) + callManager.startOutgoingCall( + signalingRoomId = dmRoomId, + otherUserId = action.selectedUserId, + isVideoCall = call?.mxCall?.isVideoCall.orFalse(), + transferee = call + ) + } else { + call?.transferToUser(action.selectedUserId, null) + _viewEvents.post(CallTransferViewEvents.Dismiss) + } } catch (failure: Throwable) { _viewEvents.post(CallTransferViewEvents.FailToTransfer) } @@ -97,8 +109,17 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: try { _viewEvents.post(CallTransferViewEvents.Loading) val result = dialPadLookup.lookupPhoneNumber(action.phoneNumber) - call?.transferToUser(result.userId, result.roomId) - _viewEvents.post(CallTransferViewEvents.Dismiss) + if (action.consultFirst) { + callManager.startOutgoingCall( + signalingRoomId = result.roomId, + otherUserId = result.userId, + isVideoCall = call?.mxCall?.isVideoCall.orFalse(), + transferee = call + ) + } else { + call?.transferToUser(result.userId, result.roomId) + _viewEvents.post(CallTransferViewEvents.Dismiss) + } } catch (failure: Throwable) { _viewEvents.post(CallTransferViewEvents.FailToTransfer) } diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt index 4aed01965b..81fa41dc0d 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt @@ -287,7 +287,7 @@ class WebRtcCall(val mxCall: MxCall, } } - suspend fun transferToUser(targetUserId: String, targetRoomId: String?) { + suspend fun transferToUser(targetUserId: String, targetRoomId: String?) = withContext(dispatcher){ mxCall.transfer( targetUserId = targetUserId, targetRoomId = targetRoomId, @@ -297,21 +297,21 @@ class WebRtcCall(val mxCall: MxCall, endCall(true, CallHangupContent.Reason.REPLACED) } - suspend fun transferToCall(transferTargetCall: WebRtcCall) { + suspend fun transferToCall(transferTargetCall: WebRtcCall)= withContext(dispatcher) { val newCallId = CallIdGenerator.generate() transferTargetCall.mxCall.transfer( - targetUserId = this.mxCall.opponentUserId, + targetUserId = this@WebRtcCall.mxCall.opponentUserId, targetRoomId = null, createCallId = null, awaitCallId = newCallId ) - this.mxCall.transfer( - transferTargetCall.mxCall.opponentUserId, + this@WebRtcCall.mxCall.transfer( + targetUserId = transferTargetCall.mxCall.opponentUserId, targetRoomId = null, createCallId = newCallId, awaitCallId = null ) - endCall(true, CallHangupContent.Reason.REPLACED) + this@WebRtcCall.endCall(true, CallHangupContent.Reason.REPLACED) transferTargetCall.endCall(true, CallHangupContent.Reason.REPLACED) } diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt index 2f8f84051e..0ed91ac821 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt @@ -137,6 +137,10 @@ class WebRtcCallManager @Inject constructor( private val advertisedCalls = HashSet() private val callsByCallId = ConcurrentHashMap() private val callsByRoomId = ConcurrentHashMap>() + // Calls started as an attended transfer, ie. with the intention of transferring another + // call with a different party to this one. + // callId (target) -> call (transferee) + private val transferees = ConcurrentHashMap() fun getCallById(callId: String): WebRtcCall? { return callsByCallId[callId] @@ -146,6 +150,10 @@ class WebRtcCallManager @Inject constructor( return callsByRoomId[roomId] ?: emptyList() } + fun getTransfereeForCallId(callId: String): WebRtcCall? { + return transferees[callId] + } + fun getCurrentCall(): WebRtcCall? { return currentCall.get() } @@ -219,6 +227,7 @@ class WebRtcCallManager @Inject constructor( } CallService.onCallTerminated(context, callId) callsByRoomId[webRtcCall.roomId]?.remove(webRtcCall) + transferees.remove(callId) if (getCurrentCall()?.callId == callId) { val otherCall = getCalls().lastOrNull() currentCall.setAndNotify(otherCall) @@ -245,7 +254,7 @@ class WebRtcCallManager @Inject constructor( } } - fun startOutgoingCall(signalingRoomId: String, otherUserId: String, isVideoCall: Boolean) { + fun startOutgoingCall(signalingRoomId: String, otherUserId: String, isVideoCall: Boolean, transferee: WebRtcCall? = null) { Timber.v("## VOIP startOutgoingCall in room $signalingRoomId to $otherUserId isVideo $isVideoCall") if (getCallsByRoomId(signalingRoomId).isNotEmpty()) { Timber.w("## VOIP you already have a call in this room") @@ -263,7 +272,9 @@ class WebRtcCallManager @Inject constructor( val mxCall = currentSession?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return val webRtcCall = createWebRtcCall(mxCall) currentCall.setAndNotify(webRtcCall) - + if(transferee != null){ + transferees[webRtcCall.callId] = transferee + } CallService.onOutgoingCallRinging( context = context.applicationContext, callId = mxCall.callId) diff --git a/vector/src/main/res/layout/activity_call_transfer.xml b/vector/src/main/res/layout/activity_call_transfer.xml index 64ddd29319..5540eb91d3 100644 --- a/vector/src/main/res/layout/activity_call_transfer.xml +++ b/vector/src/main/res/layout/activity_call_transfer.xml @@ -52,7 +52,6 @@ android:layout_width="wrap_content" android:layout_centerVertical="true" android:layout_alignParentStart="true" - android:enabled="false" android:layout_height="wrap_content"/> Transfer An error occurred while transferring call Users - + Consulting with %1$s + Transfer to %1$s Re-Authentication Needed From 447bd98a78dc67ee5d7a7210f34b603aaf1b798c Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 24 May 2021 10:54:09 +0200 Subject: [PATCH 03/34] Fixes #3386 show space description in explore header --- .../api/session/room/model/SpaceChildInfo.kt | 4 +- .../api/session/room/peeking/PeekResult.kt | 3 +- .../matrix/android/sdk/api/util/MatrixItem.kt | 2 +- .../database/mapper/RoomSummaryMapper.kt | 4 +- .../session/room/peeking/PeekRoomTask.kt | 13 +- .../session/space/DefaultSpaceService.kt | 3 +- .../home/room/list/SpaceChildInfoItem.kt | 2 +- .../matrixto/MatrixToBottomSheetState.kt | 3 +- .../matrixto/MatrixToBottomSheetViewModel.kt | 14 +- .../matrixto/MatrixToRoomSpaceFragment.kt | 9 + .../app/features/matrixto/SpaceCardHelper.kt | 169 ++++++++++++++++++ .../spaces/explore/SpaceDirectoryFragment.kt | 87 ++++++++- .../spaces/explore/SpaceDirectoryViewModel.kt | 13 +- .../spaces/invite/SpaceInviteBottomSheet.kt | 47 +---- .../fragment_matrix_to_room_space_card.xml | 36 +++- .../layout/fragment_room_directory_picker.xml | 67 ++++--- vector/src/main/res/values/strings.xml | 4 + 17 files changed, 389 insertions(+), 91 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/matrixto/SpaceCardHelper.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt index 66293bcb8c..8cd2a0538d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt @@ -32,5 +32,7 @@ data class SpaceChildInfo( val parentRoomId: String?, val suggested: Boolean?, val canonicalAlias: String?, - val aliases: List? + val aliases: List?, + val worldReadable: Boolean + ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt index 888950dc12..b78cd5e032 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt @@ -28,7 +28,8 @@ sealed class PeekResult { val numJoinedMembers: Int?, val roomType: String?, val viaServers: List, - val someMembers: List? + val someMembers: List?, + val isPublic: Boolean ) : PeekResult() data class PeekingNotAllowed( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt index deb279eb95..bc397c5b4a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt @@ -159,4 +159,4 @@ fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl) -fun SpaceChildInfo.toMatrixItem() = MatrixItem.RoomItem(childRoomId, name ?: canonicalAlias ?: "", avatarUrl) +fun SpaceChildInfo.toMatrixItem() = MatrixItem.RoomItem(childRoomId, name ?: canonicalAlias, avatarUrl) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index 3fea15bd3d..c32c019625 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.database.mapper +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.SpaceParentInfo @@ -92,7 +93,8 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa parentRoomId = roomSummaryEntity.roomId, suggested = it.suggested, canonicalAlias = it.childSummaryEntity?.canonicalAlias, - aliases = it.childSummaryEntity?.aliases?.toList() + aliases = it.childSummaryEntity?.aliases?.toList(), + worldReadable = it.childSummaryEntity?.joinRules == RoomJoinRules.PUBLIC ) }, flattenParentIds = roomSummaryEntity.flattenParentIds?.split("|") ?: emptyList() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt index c6f4bbb4e1..219e9c903f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt @@ -23,6 +23,8 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomNameContent import org.matrix.android.sdk.api.session.room.model.RoomTopicContent @@ -105,7 +107,8 @@ internal class DefaultPeekRoomTask @Inject constructor( numJoinedMembers = publicRepoResult.numJoinedMembers, viaServers = serverList, roomType = null, // would be nice to get that from directory... - someMembers = null + someMembers = null, + isPublic = true ) } @@ -143,6 +146,11 @@ internal class DefaultPeekRoomTask @Inject constructor( } } + val historyVisibility = + stateEvents + .lastOrNull { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY && it.stateKey?.isNotEmpty() == true } + ?.let { it.content?.toModel()?.historyVisibility } + val roomType = stateEvents .lastOrNull { it.type == EventType.STATE_ROOM_CREATE } ?.content @@ -158,7 +166,8 @@ internal class DefaultPeekRoomTask @Inject constructor( numJoinedMembers = memberCount, roomType = roomType, viaServers = serverList, - someMembers = someMembers + someMembers = someMembers, + isPublic = historyVisibility == RoomHistoryVisibility.WORLD_READABLE ) } catch (failure: Throwable) { // Would be M_FORBIDDEN if cannot peek :/ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt index d0ad19245f..9c6153b349 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt @@ -147,7 +147,8 @@ internal class DefaultSpaceService @Inject constructor( parentRoomId = childStateEv.roomId, suggested = childStateEvContent.suggested, canonicalAlias = childSummary.canonicalAlias, - aliases = childSummary.aliases + aliases = childSummary.aliases, + worldReadable = childSummary.worldReadable ) } }.orEmpty() diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceChildInfoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceChildInfoItem.kt index cb9c8b1f2e..f03cc4c9dc 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceChildInfoItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceChildInfoItem.kt @@ -63,7 +63,7 @@ abstract class SpaceChildInfoItem : VectorEpoxyModel( it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) itemLongClickListener?.onLongClick(it) ?: false } - holder.titleView.text = matrixItem.getBestName() + holder.titleView.text = matrixItem.displayName ?: holder.rootView.context.getString(R.string.unnamed_room) if (space) { avatarRenderer.renderSpace(matrixItem, holder.avatarImageView) } else { diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt index 2f341d48ec..7082cd16a3 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt @@ -48,7 +48,8 @@ sealed class RoomInfoResult { val alias: String?, val membership: Membership, val roomType: String?, - val viaServers: List? + val viaServers: List?, + val isPublic: Boolean ) : RoomInfoResult() data class PartialInfo( diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt index 694f324025..1c78348b56 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt @@ -118,11 +118,9 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( session.getRoom(permalinkData.roomIdOrAlias) } ?.roomSummary() - // don't take if not active, as it could be outdated - ?.takeIf { it.membership.isActive() } - // XXX fix that - val forceRefresh = true - if (!forceRefresh && knownRoom != null) { + // don't take if not Join, as it could be outdated + ?.takeIf { it.membership == Membership.JOIN } + if (knownRoom != null) { setState { copy( roomPeekResult = Success( @@ -134,7 +132,8 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( alias = knownRoom.canonicalAlias, membership = knownRoom.membership, roomType = knownRoom.roomType, - viaServers = null + viaServers = null, + isPublic = knownRoom.isPublic ) ) ) @@ -150,7 +149,8 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( alias = peekResult.alias, membership = knownRoom?.membership ?: Membership.NONE, roomType = peekResult.roomType, - viaServers = peekResult.viaServers.takeIf { it.isNotEmpty() } ?: permalinkData.viaParameters + viaServers = peekResult.viaServers.takeIf { it.isNotEmpty() } ?: permalinkData.viaParameters, + isPublic = peekResult.isPublic ).also { peekResult.someMembers?.let { checkForKnownMembers(it) } } diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt index 04f72000fa..730a04aef7 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt @@ -81,6 +81,15 @@ class MatrixToRoomSpaceFragment @Inject constructor( if (peek.roomType == RoomType.SPACE) { views.matrixToBetaTag.isVisible = true avatarRenderer.renderSpace(matrixItem, views.matrixToCardAvatar) + if (peek.isPublic) { + views.matrixToAccessText.setTextOrHide(context?.getString(R.string.public_space)) + views.matrixToAccessImage.isVisible = true + views.matrixToAccessImage.setImageResource(R.drawable.ic_public_room) + } else { + views.matrixToAccessText.setTextOrHide(context?.getString(R.string.private_space)) + views.matrixToAccessImage.isVisible = true + views.matrixToAccessImage.setImageResource(R.drawable.ic_room_private) + } } else { views.matrixToBetaTag.isVisible = false avatarRenderer.render(matrixItem, views.matrixToCardAvatar) diff --git a/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardHelper.kt b/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardHelper.kt new file mode 100644 index 0000000000..5a64c15f30 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardHelper.kt @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.matrixto + +import androidx.core.view.isGone +import androidx.core.view.isVisible +import im.vector.app.R +import im.vector.app.core.extensions.setTextOrHide +import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.resources.DrawableProvider +import im.vector.app.core.resources.StringProvider +import im.vector.app.databinding.FragmentMatrixToRoomSpaceCardBinding +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod +import im.vector.app.features.home.room.detail.timeline.tools.linkify +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo +import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.api.util.toMatrixItem +import javax.inject.Inject + +class SpaceCardHelper @Inject constructor( + private val avatarRenderer: AvatarRenderer, + private val stringProvider: StringProvider, + private val drawableProvider: DrawableProvider, + private val colorProvider: ColorProvider +) { + + fun render(spaceSummary: RoomSummary?, + peopleYouKnow: List, + matrixLinkCallback: TimelineEventController.UrlClickCallback?, + inCard: FragmentMatrixToRoomSpaceCardBinding) { + if (spaceSummary == null) { + inCard.matrixToCardContentVisibility.isVisible = false + inCard.matrixToCardButtonLoading.isVisible = true + } else { + inCard.matrixToCardContentVisibility.isVisible = true + inCard.matrixToCardButtonLoading.isVisible = false + avatarRenderer.renderSpace(spaceSummary.toMatrixItem(), inCard.matrixToCardAvatar) + inCard.matrixToCardNameText.text = spaceSummary.name + inCard.matrixToBetaTag.isVisible = true + inCard.matrixToCardAliasText.setTextOrHide(spaceSummary.canonicalAlias) + inCard.matrixToCardDescText.setTextOrHide(spaceSummary.topic.linkify(matrixLinkCallback)) + if (spaceSummary.isPublic) { + inCard.matrixToAccessText.setTextOrHide(stringProvider.getString(R.string.public_space)) + inCard.matrixToAccessImage.isVisible = true + inCard.matrixToAccessImage.setImageResource(R.drawable.ic_public_room) + } else { + inCard.matrixToAccessText.setTextOrHide(stringProvider.getString(R.string.private_space)) + inCard.matrixToAccessImage.isVisible = true + inCard.matrixToAccessImage.setImageResource(R.drawable.ic_room_private) + } + val memberCount = spaceSummary.otherMemberIds.size + if (memberCount != 0) { + inCard.matrixToMemberPills.isVisible = true + inCard.spaceChildMemberCountText.text = stringProvider.getQuantityString(R.plurals.room_title_members, memberCount, memberCount) + } else { + // hide the pill + inCard.matrixToMemberPills.isVisible = false + } + + val images = listOf( + inCard.knownMember1, + inCard.knownMember2, + inCard.knownMember3, + inCard.knownMember4, + inCard.knownMember5 + ).onEach { it.isGone = true } + + if (peopleYouKnow.isEmpty()) { + inCard.peopleYouMayKnowText.isVisible = false + } else { + peopleYouKnow.forEachIndexed { index, item -> + images[index].isVisible = true + avatarRenderer.render(item.toMatrixItem(), images[index]) + } + inCard.peopleYouMayKnowText.setTextOrHide( + stringProvider.getQuantityString(R.plurals.space_people_you_know, + peopleYouKnow.count(), + peopleYouKnow.count() + ) + ) + } + } + inCard.matrixToCardDescText.movementMethod = createLinkMovementMethod(object : TimelineEventController.UrlClickCallback { + override fun onUrlClicked(url: String, title: String): Boolean { + return false + } + + override fun onUrlLongClicked(url: String): Boolean { + // host.callback?.onUrlInTopicLongClicked(url) + return true + } + }) + } + + fun render(spaceChildInfo: SpaceChildInfo?, + peopleYouKnow: List, + matrixLinkCallback: TimelineEventController.UrlClickCallback?, + inCard: FragmentMatrixToRoomSpaceCardBinding) { + if (spaceChildInfo == null) { + inCard.matrixToCardContentVisibility.isVisible = false + inCard.matrixToCardButtonLoading.isVisible = true + } else { + inCard.matrixToCardContentVisibility.isVisible = true + inCard.matrixToCardButtonLoading.isVisible = false + avatarRenderer.renderSpace(spaceChildInfo.toMatrixItem(), inCard.matrixToCardAvatar) + inCard.matrixToCardNameText.setTextOrHide(spaceChildInfo.name) + inCard.matrixToBetaTag.isVisible = true + inCard.matrixToCardAliasText.setTextOrHide(spaceChildInfo.canonicalAlias) + inCard.matrixToCardDescText.setTextOrHide(spaceChildInfo.topic?.linkify(matrixLinkCallback)) + if (spaceChildInfo.worldReadable) { + inCard.matrixToAccessText.setTextOrHide(stringProvider.getString(R.string.public_space)) + inCard.matrixToAccessImage.isVisible = true + inCard.matrixToAccessImage.setImageResource(R.drawable.ic_public_room) + } else { + inCard.matrixToAccessText.setTextOrHide(stringProvider.getString(R.string.private_space)) + inCard.matrixToAccessImage.isVisible = true + inCard.matrixToAccessImage.setImageResource(R.drawable.ic_room_private) + } + val memberCount = spaceChildInfo.activeMemberCount ?: 0 + if (memberCount != 0) { + inCard.matrixToMemberPills.isVisible = true + inCard.spaceChildMemberCountText.text = stringProvider.getQuantityString(R.plurals.room_title_members, memberCount, memberCount) + } else { + // hide the pill + inCard.matrixToMemberPills.isVisible = false + } + + val images = listOf( + inCard.knownMember1, + inCard.knownMember2, + inCard.knownMember3, + inCard.knownMember4, + inCard.knownMember5 + ).onEach { it.isGone = true } + + if (peopleYouKnow.isEmpty()) { + inCard.peopleYouMayKnowText.isVisible = false + } else { + peopleYouKnow.forEachIndexed { index, item -> + images[index].isVisible = true + avatarRenderer.render(item.toMatrixItem(), images[index]) + } + inCard.peopleYouMayKnowText.setTextOrHide( + stringProvider.getQuantityString(R.plurals.space_people_you_know, + peopleYouKnow.count(), + peopleYouKnow.count() + ) + ) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt index a866ea9b89..7f45307a0d 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt @@ -16,6 +16,7 @@ package im.vector.app.features.spaces.explore +import android.content.DialogInterface import android.os.Bundle import android.os.Parcelable import android.view.LayoutInflater @@ -23,19 +24,33 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.appcompat.app.AlertDialog +import androidx.core.text.toSpannable +import androidx.core.view.isVisible import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import im.vector.app.R +import im.vector.app.core.dialogs.withColoredButton import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.utils.colorizeMatchingText +import im.vector.app.core.utils.isValidUrl +import im.vector.app.core.utils.openUrlInExternalBrowser import im.vector.app.databinding.FragmentRoomDirectoryPickerBinding +import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.matrixto.SpaceCardHelper +import im.vector.app.features.permalink.PermalinkHandler import im.vector.app.features.spaces.manage.ManageType import im.vector.app.features.spaces.manage.SpaceManageActivity +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo +import java.net.URL import javax.inject.Inject @Parcelize @@ -44,9 +59,13 @@ data class SpaceDirectoryArgs( ) : Parcelable class SpaceDirectoryFragment @Inject constructor( - private val epoxyController: SpaceDirectoryController + private val epoxyController: SpaceDirectoryController, + private val permalinkHandler: PermalinkHandler, + private val spaceCardHelper: SpaceCardHelper, + private val colorProvider: ColorProvider ) : VectorBaseFragment(), SpaceDirectoryController.InteractionListener, + TimelineEventController.UrlClickCallback, OnBackPressed { override fun getMenuRes() = R.menu.menu_space_directory @@ -71,6 +90,15 @@ class SpaceDirectoryFragment @Inject constructor( viewModel.selectSubscribe(this, SpaceDirectoryState::canAddRooms) { invalidateOptionsMenu() } + + views.spaceCard.matrixToCardMainButton.isVisible = false + views.spaceCard.matrixToCardSecondaryButton.isVisible = false + views.spaceCard.knownMember1.isVisible = false + views.spaceCard.knownMember2.isVisible = false + views.spaceCard.knownMember3.isVisible = false + views.spaceCard.knownMember4.isVisible = false + views.spaceCard.knownMember5.isVisible = false + views.spaceCard.peopleYouMayKnowText.isVisible = false } override fun onDestroyView() { @@ -82,10 +110,21 @@ class SpaceDirectoryFragment @Inject constructor( override fun invalidate() = withState(viewModel) { state -> epoxyController.setData(state) - val title = state.hierarchyStack.lastOrNull()?.let { currentParent -> + val currentParent = state.hierarchyStack.lastOrNull()?.let { currentParent -> state.spaceSummaryApiResult.invoke()?.firstOrNull { it.childRoomId == currentParent } - }?.name ?: getString(R.string.space_explore_activity_title) - views.toolbar.title = title + } + + if (currentParent == null) { + val title = getString(R.string.space_explore_activity_title) + views.toolbar.title = title + + spaceCardHelper.render(state.spaceSummary.invoke(), emptyList(), this, views.spaceCard) + } else { + val title = currentParent.name ?: currentParent.canonicalAlias ?: getString(R.string.space_explore_activity_title) + views.toolbar.title = title + + spaceCardHelper.render(currentParent, emptyList(), this, views.spaceCard) + } } override fun onPrepareOptionsMenu(menu: Menu) = withState(viewModel) { state -> @@ -96,7 +135,7 @@ class SpaceDirectoryFragment @Inject constructor( override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - R.id.spaceAddRoom -> { + R.id.spaceAddRoom -> { withState(viewModel) { state -> addExistingRooms(state.spaceId) } @@ -138,6 +177,44 @@ class SpaceDirectoryFragment @Inject constructor( override fun addExistingRooms(spaceId: String) { addExistingRoomActivityResult.launch(SpaceManageActivity.newIntent(requireContext(), spaceId, ManageType.AddRooms)) } + + override fun onUrlClicked(url: String, title: String): Boolean { + permalinkHandler + .launch(requireActivity(), url, null) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { managed -> + if (!managed) { + if (title.isValidUrl() && url.isValidUrl() && URL(title).host != URL(url).host) { + AlertDialog.Builder(requireActivity()) + .setTitle(R.string.external_link_confirmation_title) + .setMessage( + getString(R.string.external_link_confirmation_message, title, url) + .toSpannable() + .colorizeMatchingText(url, colorProvider.getColorFromAttribute(R.attr.riotx_text_primary_body_contrast)) + .colorizeMatchingText(title, colorProvider.getColorFromAttribute(R.attr.riotx_text_primary_body_contrast)) + ) + .setPositiveButton(R.string._continue) { _, _ -> + openUrlInExternalBrowser(requireContext(), url) + } + .setNegativeButton(R.string.cancel, null) + .show() + .withColoredButton(DialogInterface.BUTTON_NEGATIVE) + } else { + // Open in external browser, in a new Tab + openUrlInExternalBrowser(requireContext(), url) + } + } + } + .disposeOnDestroyView() + // In fact it is always managed + return true + } + + override fun onUrlLongClicked(url: String): Boolean { + // nothing? + return false + } // override fun navigateToRoom(roomId: String) { // viewModel.handle(SpaceDirectoryViewAction.NavigateToRoom(roomId)) // } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index 313ddfe1dc..4d0faedd7e 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -66,7 +66,8 @@ class SpaceDirectoryViewModel @AssistedInject constructor( val spaceSum = session.getRoomSummary(initialState.spaceId) setState { copy( - childList = spaceSum?.spaceChildren ?: emptyList() + childList = spaceSum?.spaceChildren ?: emptyList(), + spaceSummary = spaceSum?.let { Success(spaceSum) } ?: Loading() ) } @@ -148,7 +149,7 @@ class SpaceDirectoryViewModel @AssistedInject constructor( copy(hierarchyStack = hierarchyStack + listOf(action.spaceChildInfo.childRoomId)) } } - SpaceDirectoryViewAction.HandleBack -> { + SpaceDirectoryViewAction.HandleBack -> { withState { if (it.hierarchyStack.isEmpty()) { _viewEvents.post(SpaceDirectoryViewEvents.Dismiss) @@ -161,20 +162,20 @@ class SpaceDirectoryViewModel @AssistedInject constructor( } } } - is SpaceDirectoryViewAction.JoinOrOpen -> { + is SpaceDirectoryViewAction.JoinOrOpen -> { handleJoinOrOpen(action.spaceChildInfo) } - is SpaceDirectoryViewAction.NavigateToRoom -> { + is SpaceDirectoryViewAction.NavigateToRoom -> { _viewEvents.post(SpaceDirectoryViewEvents.NavigateToRoom(action.roomId)) } - is SpaceDirectoryViewAction.ShowDetails -> { + is SpaceDirectoryViewAction.ShowDetails -> { // This is temporary for now to at least display something for the space beta // It's not ideal as it's doing some peeking that is not needed. session.permalinkService().createRoomPermalink(action.spaceChildInfo.childRoomId)?.let { _viewEvents.post(SpaceDirectoryViewEvents.NavigateToMxToBottomSheet(it)) } } - SpaceDirectoryViewAction.Retry -> { + SpaceDirectoryViewAction.Retry -> { refreshFromApi() } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheet.kt b/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheet.kt index 8e536459a3..d393943f72 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheet.kt @@ -22,7 +22,6 @@ import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.core.view.isGone import androidx.core.view.isVisible import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading @@ -33,12 +32,12 @@ import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.di.ScreenComponent -import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.platform.ButtonStateView import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.core.utils.toast import im.vector.app.databinding.BottomSheetInvitedToSpaceBinding import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.matrixto.SpaceCardHelper import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -60,6 +59,9 @@ class SpaceInviteBottomSheet : VectorBaseBottomSheetDialogFragment - images[index].isVisible = true - avatarRenderer.render(item.toMatrixItem(), images[index]) - } - views.spaceCard.peopleYouMayKnowText.setTextOrHide( - resources.getQuantityString(R.plurals.space_people_you_know, - peopleYouKnow.count(), - peopleYouKnow.count() - ) - ) - } } override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetInvitedToSpaceBinding { diff --git a/vector/src/main/res/layout/fragment_matrix_to_room_space_card.xml b/vector/src/main/res/layout/fragment_matrix_to_room_space_card.xml index b2d24f5218..091c8f15b2 100644 --- a/vector/src/main/res/layout/fragment_matrix_to_room_space_card.xml +++ b/vector/src/main/res/layout/fragment_matrix_to_room_space_card.xml @@ -58,7 +58,7 @@ android:id="@+id/matrixToCardAliasText" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="4dp" + android:layout_marginTop="8dp" android:maxLines="1" android:singleLine="true" android:textAlignment="textStart" @@ -70,12 +70,43 @@ tools:text="@sample/rooms.json/data/alias" tools:visibility="visible" /> + + + + + + app:layout_constraintTop_toBottomOf="@id/matrixToAccessText"> - + android:layout_height="wrap_content" + android:elevation="4dp"> - - + android:layout_height="match_parent" + android:theme="@style/Vector.Toolbar.Profile" + app:contentScrim="?riotx_background" + app:layout_scrollFlags="scroll|exitUntilCollapsed|snap" + app:scrimAnimationDuration="250" + app:scrimVisibleHeightTrigger="120dp" + app:titleEnabled="false" + app:toolbarId="@+id/toolbar"> - + + + + + + + + + + + + + + - \ 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 8649b14ca3..4f59d00d27 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3262,6 +3262,7 @@ Do not notify View read receipts This room is public + This Space is public Dev Tools Explore Room State @@ -3293,6 +3294,8 @@ Delete unsent messages Are you sure you want to delete all unsent messages in this room? + Public space + Private space Add Space Your public space Your private space @@ -3388,4 +3391,5 @@ Some rooms may be hidden because they’re private and you need an invite.\nYou don’t have permission to add rooms. Some rooms may be hidden because they’re private and you need an invite. + Unnamed Room From 5d4f60ff6f74b8600107c20097f19b2bd1b15775 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 24 May 2021 11:03:14 +0200 Subject: [PATCH 04/34] Update changelog --- newsfragment/3401.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragment/3401.bugfix diff --git a/newsfragment/3401.bugfix b/newsfragment/3401.bugfix new file mode 100644 index 0000000000..42509f3ae3 --- /dev/null +++ b/newsfragment/3401.bugfix @@ -0,0 +1 @@ +Fix | On Android it seems to be impossible to view the complete description of a Space (without dev tools) (#3386) \ No newline at end of file From 432fd67ce20a7e50704b26b938f906e8ef4ed49e Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 25 May 2021 14:08:48 +0200 Subject: [PATCH 05/34] Fixes #3406 --- .../api/session/permalinks/PermalinkService.kt | 2 +- .../permalinks/DefaultPermalinkService.kt | 4 ++-- .../session/permalinks/PermalinkFactory.kt | 12 ++++++++++-- newsfragment/3401.bugfix | 3 ++- .../features/home/room/list/RoomListAction.kt | 1 + .../features/home/room/list/RoomListFragment.kt | 17 +++++++++++++---- .../features/home/room/list/RoomListListener.kt | 1 + .../home/room/list/RoomListViewEvents.kt | 1 + .../home/room/list/RoomListViewModel.kt | 7 +++++++ .../home/room/list/RoomSummaryItemFactory.kt | 6 +++--- .../room/list/SuggestedRoomListController.kt | 6 +----- .../app/features/media/ImageContentRenderer.kt | 4 ++-- 12 files changed, 44 insertions(+), 20 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt index ac1d726d03..a6d4583c76 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt @@ -54,7 +54,7 @@ interface PermalinkService { * * @return the permalink, or null in case of error */ - fun createRoomPermalink(roomId: String): String? + fun createRoomPermalink(roomId: String, viaServers: List? = null): String? /** * Creates a permalink for an event. If you have an event you can use [createPermalink] diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/DefaultPermalinkService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/DefaultPermalinkService.kt index 7db9d8f68a..8b45c9f570 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/DefaultPermalinkService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/DefaultPermalinkService.kt @@ -33,8 +33,8 @@ internal class DefaultPermalinkService @Inject constructor( return permalinkFactory.createPermalink(id) } - override fun createRoomPermalink(roomId: String): String? { - return permalinkFactory.createRoomPermalink(roomId) + override fun createRoomPermalink(roomId: String, via: List?): String? { + return permalinkFactory.createRoomPermalink(roomId, via) } override fun createPermalink(roomId: String, eventId: String): String { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt index 970752449a..f8bc1b9b34 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.permalinks import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.permalinks.PermalinkService.Companion.MATRIX_TO_URL_BASE import org.matrix.android.sdk.internal.di.UserId +import java.net.URLEncoder import javax.inject.Inject internal class PermalinkFactory @Inject constructor( @@ -40,11 +41,18 @@ internal class PermalinkFactory @Inject constructor( } else MATRIX_TO_URL_BASE + escape(id) } - fun createRoomPermalink(roomId: String): String? { + fun createRoomPermalink(roomId: String, via: List? = null): String? { return if (roomId.isEmpty()) { null } else { - MATRIX_TO_URL_BASE + escape(roomId) + viaParameterFinder.computeViaParams(userId, roomId) + buildString { + append(MATRIX_TO_URL_BASE) + append(escape(roomId)) + append( + via?.joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") } + ?: viaParameterFinder.computeViaParams(userId, roomId) + ) + } } } diff --git a/newsfragment/3401.bugfix b/newsfragment/3401.bugfix index 42509f3ae3..c1774521af 100644 --- a/newsfragment/3401.bugfix +++ b/newsfragment/3401.bugfix @@ -1 +1,2 @@ -Fix | On Android it seems to be impossible to view the complete description of a Space (without dev tools) (#3386) \ No newline at end of file +Fix | On Android it seems to be impossible to view the complete description of a Space (without dev tools) (#3386) +Fix | Suggest Rooms, Show a detailed view of the room on click (#3406) \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt index 37f7d148aa..e6b6b34503 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt @@ -30,4 +30,5 @@ sealed class RoomListAction : VectorViewModelAction { data class ToggleTag(val roomId: String, val tag: String) : RoomListAction() data class LeaveRoom(val roomId: String) : RoomListAction() data class JoinSuggestedRoom(val roomId: String, val viaServers: List?) : RoomListAction() + data class ShowRoomDetails(val roomId: String, val viaServers: List?) : RoomListAction() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index 76d7752ea7..8049d3c057 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -108,10 +108,11 @@ class RoomListFragment @Inject constructor( sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) roomListViewModel.observeViewEvents { when (it) { - is RoomListViewEvents.Loading -> showLoading(it.message) - is RoomListViewEvents.Failure -> showFailure(it.throwable) - is RoomListViewEvents.SelectRoom -> handleSelectRoom(it) - is RoomListViewEvents.Done -> Unit + is RoomListViewEvents.Loading -> showLoading(it.message) + is RoomListViewEvents.Failure -> showFailure(it.throwable) + is RoomListViewEvents.SelectRoom -> handleSelectRoom(it) + is RoomListViewEvents.Done -> Unit + is RoomListViewEvents.NavigateToMxToBottomSheet -> handleShowMxToLink(it.link) }.exhaustive } @@ -155,6 +156,10 @@ class RoomListFragment @Inject constructor( showErrorInSnackbar(throwable) } + private fun handleShowMxToLink(link: String) { + navigator.openMatrixToBottomSheet(requireContext(), link) + } + override fun onDestroyView() { adapterInfosList.onEach { it.contentEpoxyController.removeModelBuildListener(modelBuildListener) } adapterInfosList.clear() @@ -474,6 +479,10 @@ class RoomListFragment @Inject constructor( roomListViewModel.handle(RoomListAction.JoinSuggestedRoom(room.childRoomId, room.viaServers)) } + override fun onSuggestedRoomClicked(room: SpaceChildInfo) { + roomListViewModel.handle(RoomListAction.ShowRoomDetails(room.childRoomId, room.viaServers)) + } + override fun onRejectRoomInvitation(room: RoomSummary) { notificationDrawerManager.clearMemberShipNotificationForRoom(room.roomId) roomListViewModel.handle(RoomListAction.RejectInvitation(room)) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt index 0ba265f841..cf619ce435 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt @@ -26,4 +26,5 @@ interface RoomListListener : FilteredRoomFooterItem.FilteredRoomFooterItemListen fun onRejectRoomInvitation(room: RoomSummary) fun onAcceptRoomInvitation(room: RoomSummary) fun onJoinSuggestedRoom(room: SpaceChildInfo) + fun onSuggestedRoomClicked(room: SpaceChildInfo) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewEvents.kt index cb84d91373..df2ff58da6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewEvents.kt @@ -29,4 +29,5 @@ sealed class RoomListViewEvents : VectorViewEvents { data class SelectRoom(val roomSummary: RoomSummary) : RoomListViewEvents() object Done : RoomListViewEvents() + data class NavigateToMxToBottomSheet(val link: String) : RoomListViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 246d5052cf..fbb8faebb0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -161,6 +161,7 @@ class RoomListViewModel @Inject constructor( is RoomListAction.ToggleTag -> handleToggleTag(action) is RoomListAction.ToggleSection -> handleToggleSection(action.section) is RoomListAction.JoinSuggestedRoom -> handleJoinSuggestedRoom(action) + is RoomListAction.ShowRoomDetails -> handleShowRoomDetails(action) }.exhaustive } @@ -289,6 +290,12 @@ class RoomListViewModel @Inject constructor( } } + private fun handleShowRoomDetails(action: RoomListAction.ShowRoomDetails) { + session.permalinkService().createRoomPermalink(action.roomId, action.viaServers)?.let { + _viewEvents.post(RoomListViewEvents.NavigateToMxToBottomSheet(it)) + } + } + private fun handleToggleTag(action: RoomListAction.ToggleTag) { session.getRoom(action.roomId)?.let { room -> viewModelScope.launch(Dispatchers.IO) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt index e4826af04c..ec78a13c9f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt @@ -16,7 +16,6 @@ package im.vector.app.features.home.room.list -import android.view.View import com.airbnb.mvrx.Async import com.airbnb.mvrx.Loading import im.vector.app.R @@ -56,7 +55,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor fun createSuggestion(spaceChildInfo: SpaceChildInfo, suggestedRoomJoiningStates: Map>, - onJoinClick: View.OnClickListener): VectorEpoxyModel<*> { + listener: RoomListListener?): VectorEpoxyModel<*> { return SpaceChildInfoItem_() .id("sug_${spaceChildInfo.childRoomId}") .matrixItem(spaceChildInfo.toMatrixItem()) @@ -65,7 +64,8 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor .buttonLabel(stringProvider.getString(R.string.join)) .loading(suggestedRoomJoiningStates[spaceChildInfo.childRoomId] is Loading) .memberCount(spaceChildInfo.activeMemberCount ?: 0) - .buttonClickListener(onJoinClick) + .buttonClickListener(DebouncedClickListener({ listener?.onJoinSuggestedRoom(spaceChildInfo) })) + .itemClickListener(DebouncedClickListener({ listener?.onSuggestedRoomClicked(spaceChildInfo) })) } private fun createInvitationItem(roomSummary: RoomSummary, diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SuggestedRoomListController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SuggestedRoomListController.kt index 073fb43b6c..f9775967ee 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SuggestedRoomListController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SuggestedRoomListController.kt @@ -24,11 +24,7 @@ class SuggestedRoomListController( override fun buildModels(data: SuggestedRoomInfo?) { data?.rooms?.forEach { info -> - roomSummaryItemFactory.createSuggestion(info, data.joinEcho) { - listener?.onJoinSuggestedRoom(info) - }.let { - add(it) - } + add(roomSummaryItemFactory.createSuggestion(info, data.joinEcho, listener)) } } } diff --git a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt index 1c0d859fa9..8d6d1f467b 100644 --- a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt @@ -245,7 +245,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver() val resolvedUrl = when (mode) { Mode.FULL_SIZE, - Mode.STICKER -> resolveUrl(data) + Mode.STICKER -> resolveUrl(data) Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, size.width, size.height, ContentUrlResolver.ThumbnailMethod.SCALE) } // Fallback to base url @@ -313,7 +313,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc finalHeight = min(maxImageWidth * height / width, maxImageHeight) finalWidth = finalHeight * width / height } - Mode.STICKER -> { + Mode.STICKER -> { // limit on width val maxWidthDp = min(dimensionConverter.dpToPx(120), maxImageWidth / 2) finalWidth = min(dimensionConverter.dpToPx(width), maxWidthDp) From a832da2124f968adcacb9e06499ad38f29c512a8 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 26 May 2021 09:24:51 +0200 Subject: [PATCH 06/34] Fix reconcile summary API results and known room for display --- .../features/spaces/explore/SpaceDirectoryController.kt | 5 ++++- .../app/features/spaces/explore/SpaceDirectoryState.kt | 4 +++- .../app/features/spaces/explore/SpaceDirectoryViewModel.kt | 7 ++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt index e334868d7c..313af72c90 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt @@ -122,9 +122,12 @@ class SpaceDirectoryController @Inject constructor( val isSpace = info.roomType == RoomType.SPACE val isJoined = data?.joinedRoomsIds?.contains(info.childRoomId) == true val isLoading = data?.changeMembershipStates?.get(info.childRoomId)?.isInProgress() ?: false + // if it's known use that matrixItem because it would have a better computed name + val matrixItem = data?.knownRoomSummaries?.find { it.roomId == info.childRoomId }?.toMatrixItem() + ?: info.toMatrixItem() spaceChildInfoItem { id(info.childRoomId) - matrixItem(info.toMatrixItem()) + matrixItem(matrixItem) avatarRenderer(host.avatarRenderer) topic(info.topic) memberCount(info.activeMemberCount ?: 0) diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryState.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryState.kt index 220c3e3492..21541a51ab 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryState.kt @@ -37,7 +37,9 @@ data class SpaceDirectoryState( val joinedRoomsIds: Set = emptySet(), // keys are room alias or roomId val changeMembershipStates: Map = emptyMap(), - val canAddRooms: Boolean = false + val canAddRooms: Boolean = false, + // cached room summaries of known rooms + val knownRoomSummaries : List = emptyList() ) : MvRxState { constructor(args: SpaceDirectoryArgs) : this( spaceId = args.spaceId diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index 4d0faedd7e..3d3e1dac65 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -102,9 +102,14 @@ class SpaceDirectoryViewModel @AssistedInject constructor( viewModelScope.launch(Dispatchers.IO) { try { val query = session.spaceService().querySpaceChildren(initialState.spaceId) + val knownSummaries = query.second.mapNotNull { + session.getRoomSummary(it.childRoomId) + ?.takeIf { it.membership == Membership.JOIN } // only take if joined because it will be up to date (synced) + } setState { copy( - spaceSummaryApiResult = Success(query.second) + spaceSummaryApiResult = Success(query.second), + knownRoomSummaries = knownSummaries ) } } catch (failure: Throwable) { From b75d0cbfc6108e3aafe6c92b5de1896f2e75be2c Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 26 May 2021 11:16:38 +0200 Subject: [PATCH 07/34] Introduce SpaceItem --- .../matrix/android/sdk/api/util/MatrixItem.kt | 37 +++++++++- .../form/FormEditableSquareAvatarItem.kt | 2 +- .../app/features/home/AvatarRenderer.kt | 68 +++++++++++-------- .../home/room/list/SpaceChildInfoItem.kt | 7 +- .../matrixto/MatrixToBottomSheetState.kt | 2 +- .../matrixto/MatrixToRoomSpaceFragment.kt | 3 +- .../app/features/matrixto/SpaceCardHelper.kt | 4 +- .../settings/RoomSettingsController.kt | 10 ++- .../spaces/SpaceSettingsMenuBottomSheet.kt | 2 +- .../app/features/spaces/SpaceSummaryItem.kt | 2 +- .../features/spaces/SubSpaceSummaryItem.kt | 2 +- .../create/SpaceDetailEpoxyController.kt | 2 +- .../explore/SpaceDirectoryController.kt | 1 - .../spaces/manage/AddRoomListController.kt | 2 - .../spaces/manage/RoomManageSelectionItem.kt | 8 +-- .../spaces/manage/RoomSelectionItem.kt | 8 +-- .../manage/SpaceManageRoomsController.kt | 2 - .../spaces/manage/SpaceSettingsController.kt | 3 +- .../spaces/manage/SpaceSettingsFragment.kt | 2 +- .../spaces/preview/SpacePreviewFragment.kt | 4 +- .../features/spaces/preview/SubSpaceItem.kt | 4 +- 21 files changed, 98 insertions(+), 77 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt index bc397c5b4a..3d2773fb4b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt @@ -20,6 +20,7 @@ import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom import org.matrix.android.sdk.api.session.room.sender.SenderInfo @@ -38,6 +39,8 @@ sealed class MatrixItem( init { if (BuildConfig.DEBUG) checkId() } + + override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar) } data class EventItem(override val id: String, @@ -47,6 +50,8 @@ sealed class MatrixItem( init { if (BuildConfig.DEBUG) checkId() } + + override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar) } data class RoomItem(override val id: String, @@ -56,6 +61,19 @@ sealed class MatrixItem( init { if (BuildConfig.DEBUG) checkId() } + + override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar) + } + + data class SpaceItem(override val id: String, + override val displayName: String? = null, + override val avatarUrl: String? = null) + : MatrixItem(id, displayName, avatarUrl) { + init { + if (BuildConfig.DEBUG) checkId() + } + + override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar) } data class RoomAliasItem(override val id: String, @@ -68,6 +86,8 @@ sealed class MatrixItem( // Best name is the id, and we keep the displayName of the room for the case we need the first letter override fun getBestName() = id + + override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar) } data class GroupItem(override val id: String, @@ -80,6 +100,8 @@ sealed class MatrixItem( // Best name is the id, and we keep the displayName of the room for the case we need the first letter override fun getBestName() = id + + override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar) } open fun getBestName(): String { @@ -92,12 +114,15 @@ sealed class MatrixItem( } } + abstract fun updateAvatar(newAvatar: String?): MatrixItem + /** * Return the prefix as defined in the matrix spec (and not extracted from the id) */ fun getIdPrefix() = when (this) { is UserItem -> '@' is EventItem -> '$' + is SpaceItem, is RoomItem -> '!' is RoomAliasItem -> '#' is GroupItem -> '+' @@ -148,7 +173,11 @@ fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl) fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, avatarUrl) -fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl) +fun RoomSummary.toMatrixItem() = if (roomType == RoomType.SPACE) { + MatrixItem.SpaceItem(roomId, displayName, avatarUrl) +} else { + MatrixItem.RoomItem(roomId, displayName, avatarUrl) +} fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl) @@ -159,4 +188,8 @@ fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl) -fun SpaceChildInfo.toMatrixItem() = MatrixItem.RoomItem(childRoomId, name ?: canonicalAlias, avatarUrl) +fun SpaceChildInfo.toMatrixItem() = if (roomType == RoomType.SPACE) { + MatrixItem.SpaceItem(childRoomId, name ?: canonicalAlias, avatarUrl) +} else { + MatrixItem.RoomItem(childRoomId, name ?: canonicalAlias, avatarUrl) +} diff --git a/vector/src/main/java/im/vector/app/features/form/FormEditableSquareAvatarItem.kt b/vector/src/main/java/im/vector/app/features/form/FormEditableSquareAvatarItem.kt index 0a07d27f64..b02e5c52df 100644 --- a/vector/src/main/java/im/vector/app/features/form/FormEditableSquareAvatarItem.kt +++ b/vector/src/main/java/im/vector/app/features/form/FormEditableSquareAvatarItem.kt @@ -71,7 +71,7 @@ abstract class FormEditableSquareAvatarItem : EpoxyModelWithHolder { - avatarRenderer?.renderSpace(matrixItem!!, holder.image) + avatarRenderer?.render(matrixItem!!, holder.image) } else -> { avatarRenderer?.clear(holder.image) diff --git a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt index 65bc5e1200..c6cceee3b9 100644 --- a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt @@ -66,24 +66,24 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active DrawableImageViewTarget(imageView)) } - @UiThread - fun renderSpace(matrixItem: MatrixItem, imageView: ImageView, glideRequests: GlideRequests) { - val placeholder = getSpacePlaceholderDrawable(matrixItem) - val resolvedUrl = resolvedUrl(matrixItem.avatarUrl) - glideRequests - .load(resolvedUrl) - .transform(MultiTransformation(CenterCrop(), RoundedCorners(dimensionConverter.dpToPx(8)))) - .placeholder(placeholder) - .into(DrawableImageViewTarget(imageView)) - } - - fun renderSpace(matrixItem: MatrixItem, imageView: ImageView) { - renderSpace( - matrixItem, - imageView, - GlideApp.with(imageView) - ) - } +// fun renderSpace(matrixItem: MatrixItem, imageView: ImageView) { +// renderSpace( +// matrixItem, +// imageView, +// GlideApp.with(imageView) +// ) +// } +// +// @UiThread +// private fun renderSpace(matrixItem: MatrixItem, imageView: ImageView, glideRequests: GlideRequests) { +// val placeholder = getSpacePlaceholderDrawable(matrixItem) +// val resolvedUrl = resolvedUrl(matrixItem.avatarUrl) +// glideRequests +// .load(resolvedUrl) +// .transform(MultiTransformation(CenterCrop(), RoundedCorners(dimensionConverter.dpToPx(8)))) +// .placeholder(placeholder) +// .into(DrawableImageViewTarget(imageView)) +// } fun clear(imageView: ImageView) { // It can be called after recycler view is destroyed, just silently catch @@ -137,7 +137,16 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active target: Target) { val placeholder = getPlaceholderDrawable(matrixItem) buildGlideRequest(glideRequests, matrixItem.avatarUrl) - .apply(RequestOptions.circleCropTransform()) + .apply { + when (matrixItem) { + is MatrixItem.SpaceItem -> { + transform(MultiTransformation(CenterCrop(), RoundedCorners(dimensionConverter.dpToPx(8)))) + } + else -> { + apply(RequestOptions.circleCropTransform()) + } + } + } .placeholder(placeholder) .into(target) } @@ -197,17 +206,16 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active .beginConfig() .bold() .endConfig() - .buildRound(matrixItem.firstLetterOfDisplayName(), avatarColor) - } - - @AnyThread - fun getSpacePlaceholderDrawable(matrixItem: MatrixItem): Drawable { - val avatarColor = matrixItemColorProvider.getColor(matrixItem) - return TextDrawable.builder() - .beginConfig() - .bold() - .endConfig() - .buildRoundRect(matrixItem.firstLetterOfDisplayName(), avatarColor, dimensionConverter.dpToPx(8)) + .let { + when (matrixItem) { + is MatrixItem.SpaceItem -> { + it.buildRoundRect(matrixItem.firstLetterOfDisplayName(), avatarColor, dimensionConverter.dpToPx(8)) + } + else -> { + it.buildRound(matrixItem.firstLetterOfDisplayName(), avatarColor) + } + } + } } // PRIVATE API ********************************************************************************* diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceChildInfoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceChildInfoItem.kt index f03cc4c9dc..65b42f437b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceChildInfoItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceChildInfoItem.kt @@ -48,7 +48,6 @@ abstract class SpaceChildInfoItem : VectorEpoxyModel( @EpoxyAttribute var memberCount: Int = 0 @EpoxyAttribute var loading: Boolean = false - @EpoxyAttribute var space: Boolean = false @EpoxyAttribute var buttonLabel: String? = null @@ -64,11 +63,7 @@ abstract class SpaceChildInfoItem : VectorEpoxyModel( itemLongClickListener?.onLongClick(it) ?: false } holder.titleView.text = matrixItem.displayName ?: holder.rootView.context.getString(R.string.unnamed_room) - if (space) { - avatarRenderer.renderSpace(matrixItem, holder.avatarImageView) - } else { - avatarRenderer.render(matrixItem, holder.avatarImageView) - } + avatarRenderer.render(matrixItem, holder.avatarImageView) holder.descriptionText.text = span { span { diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt index 7082cd16a3..40213dc0ee 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt @@ -41,7 +41,7 @@ data class MatrixToBottomSheetState( sealed class RoomInfoResult { data class FullInfo( - val roomItem: MatrixItem.RoomItem, + val roomItem: MatrixItem, val name: String, val topic: String, val memberCount: Int?, diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt index 730a04aef7..f4affcc218 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt @@ -78,9 +78,9 @@ class MatrixToRoomSpaceFragment @Inject constructor( when (val peek = item.invoke()) { is RoomInfoResult.FullInfo -> { val matrixItem = peek.roomItem + avatarRenderer.render(matrixItem, views.matrixToCardAvatar) if (peek.roomType == RoomType.SPACE) { views.matrixToBetaTag.isVisible = true - avatarRenderer.renderSpace(matrixItem, views.matrixToCardAvatar) if (peek.isPublic) { views.matrixToAccessText.setTextOrHide(context?.getString(R.string.public_space)) views.matrixToAccessImage.isVisible = true @@ -92,7 +92,6 @@ class MatrixToRoomSpaceFragment @Inject constructor( } } else { views.matrixToBetaTag.isVisible = false - avatarRenderer.render(matrixItem, views.matrixToCardAvatar) } views.matrixToCardNameText.setTextOrHide(peek.name) views.matrixToCardAliasText.setTextOrHide(peek.alias) diff --git a/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardHelper.kt b/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardHelper.kt index 5a64c15f30..4028b3a444 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardHelper.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardHelper.kt @@ -51,7 +51,7 @@ class SpaceCardHelper @Inject constructor( } else { inCard.matrixToCardContentVisibility.isVisible = true inCard.matrixToCardButtonLoading.isVisible = false - avatarRenderer.renderSpace(spaceSummary.toMatrixItem(), inCard.matrixToCardAvatar) + avatarRenderer.render(spaceSummary.toMatrixItem(), inCard.matrixToCardAvatar) inCard.matrixToCardNameText.text = spaceSummary.name inCard.matrixToBetaTag.isVisible = true inCard.matrixToCardAliasText.setTextOrHide(spaceSummary.canonicalAlias) @@ -119,7 +119,7 @@ class SpaceCardHelper @Inject constructor( } else { inCard.matrixToCardContentVisibility.isVisible = true inCard.matrixToCardButtonLoading.isVisible = false - avatarRenderer.renderSpace(spaceChildInfo.toMatrixItem(), inCard.matrixToCardAvatar) + avatarRenderer.render(spaceChildInfo.toMatrixItem(), inCard.matrixToCardAvatar) inCard.matrixToCardNameText.setTextOrHide(spaceChildInfo.name) inCard.matrixToBetaTag.isVisible = true inCard.matrixToCardAliasText.setTextOrHide(spaceChildInfo.canonicalAlias) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt index 24836bc504..a045f43af0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt @@ -68,16 +68,14 @@ class RoomSettingsController @Inject constructor( id("avatar") enabled(data.actionPermissions.canChangeAvatar) when (val avatarAction = data.avatarAction) { - RoomSettingsViewState.AvatarAction.None -> { + RoomSettingsViewState.AvatarAction.None -> { // Use the current value avatarRenderer(host.avatarRenderer) // We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar. - matrixItem(roomSummary.toMatrixItem().copy(avatarUrl = data.currentRoomAvatarUrl)) + matrixItem(roomSummary.toMatrixItem().updateAvatar(data.currentRoomAvatarUrl)) } - RoomSettingsViewState.AvatarAction.DeleteAvatar -> - imageUri(null) - is RoomSettingsViewState.AvatarAction.UpdateAvatar -> - imageUri(avatarAction.newAvatarUri) + RoomSettingsViewState.AvatarAction.DeleteAvatar -> imageUri(null) + is RoomSettingsViewState.AvatarAction.UpdateAvatar -> imageUri(avatarAction.newAvatarUri) } clickListener { host.callback?.onAvatarChange() } deleteListener { host.callback?.onAvatarDelete() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceSettingsMenuBottomSheet.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceSettingsMenuBottomSheet.kt index 66572f5a82..0efb0535e8 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceSettingsMenuBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceSettingsMenuBottomSheet.kt @@ -96,7 +96,7 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment() { holder.indentSpace.isVisible = indent > 0 holder.separator.isVisible = showSeparator - avatarRenderer.renderSpace(matrixItem, holder.avatarImageView) + avatarRenderer.render(matrixItem, holder.avatarImageView) holder.counterBadgeView.render(countState) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SubSpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/spaces/SubSpaceSummaryItem.kt index db58353e5c..977ab57bc9 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SubSpaceSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SubSpaceSummaryItem.kt @@ -81,7 +81,7 @@ abstract class SubSpaceSummaryItem : VectorEpoxyModel() @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute lateinit var matrixItem: MatrixItem - @EpoxyAttribute var space: Boolean = false @EpoxyAttribute var selected: Boolean = false @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null override fun bind(holder: Holder) { super.bind(holder) - if (space) { - avatarRenderer.renderSpace(matrixItem, holder.avatarImageView) - } else { - avatarRenderer.render(matrixItem, holder.avatarImageView) - } + avatarRenderer.render(matrixItem, holder.avatarImageView) + holder.titleText.text = matrixItem.getBestName() if (selected) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsController.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsController.kt index b16c6de921..f740938ee4 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsController.kt @@ -27,7 +27,6 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.list.genericFooterItem import im.vector.app.core.utils.DebouncedClickListener import im.vector.app.features.home.AvatarRenderer -import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -83,7 +82,6 @@ class SpaceManageRoomsController @Inject constructor( matrixItem(childInfo.toMatrixItem()) avatarRenderer(host.avatarRenderer) suggested(childInfo.suggested ?: false) - space(childInfo.roomType == RoomType.SPACE) selected(data.selectedRooms.contains(childInfo.childRoomId)) itemClickListener(DebouncedClickListener({ host.listener?.toggleSelection(childInfo) diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsController.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsController.kt index 614f6f92c8..c0cf91ecaa 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsController.kt @@ -70,8 +70,9 @@ class SpaceSettingsController @Inject constructor( RoomSettingsViewState.AvatarAction.None -> { // Use the current value avatarRenderer(host.avatarRenderer) + val mxItem = roomSummary.toMatrixItem() // We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar. - matrixItem(roomSummary.toMatrixItem().copy(avatarUrl = data.currentRoomAvatarUrl)) + matrixItem(roomSummary.toMatrixItem().updateAvatar(data.currentRoomAvatarUrl)) } RoomSettingsViewState.AvatarAction.DeleteAvatar -> imageUri(null) diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt index 85c73ac8ef..7b405eb4f3 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt @@ -139,7 +139,7 @@ class SpaceSettingsFragment @Inject constructor( drawableProvider.getDrawable(R.drawable.ic_beta_pill), null ) - avatarRenderer.renderSpace(it.toMatrixItem(), views.roomSettingsToolbarAvatarImageView) + avatarRenderer.render(it.toMatrixItem(), views.roomSettingsToolbarAvatarImageView) views.roomSettingsDecorationToolbarAvatarImageView.render(it.roomEncryptionTrustLevel) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt index b6f1fb6a4e..eb02ed7c2d 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt @@ -148,8 +148,8 @@ class SpacePreviewFragment @Inject constructor( // val roomPeekResult = preview.summary.roomPeekResult val spaceName = spacePreviewState.spaceInfo.invoke()?.name ?: spacePreviewState.name ?: "" val spaceAvatarUrl = spacePreviewState.spaceInfo.invoke()?.avatarUrl ?: spacePreviewState.avatarUrl - val mxItem = MatrixItem.RoomItem(spacePreviewState.idOrAlias, spaceName, spaceAvatarUrl) - avatarRenderer.renderSpace(mxItem, views.spacePreviewToolbarAvatar) + val mxItem = MatrixItem.SpaceItem(spacePreviewState.idOrAlias, spaceName, spaceAvatarUrl) + avatarRenderer.render(mxItem, views.spacePreviewToolbarAvatar) views.roomPreviewNoPreviewToolbarTitle.text = spaceName // } // is SpacePeekResult.SpacePeekError, diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SubSpaceItem.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SubSpaceItem.kt index 367a81fe5a..1856edb61f 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SubSpaceItem.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SubSpaceItem.kt @@ -48,8 +48,8 @@ abstract class SubSpaceItem : VectorEpoxyModel() { super.bind(holder) holder.nameText.text = title - avatarRenderer.renderSpace( - MatrixItem.RoomItem(roomId, title, avatarUrl), + avatarRenderer.render( + MatrixItem.SpaceItem(roomId, title, avatarUrl), holder.avatarImageView ) holder.tabView.tabDepth = depth From d04cbadd04229caa08f6fcc0c8840ee5ce40723d Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 26 May 2021 11:19:56 +0200 Subject: [PATCH 08/34] Compute via param from list utility method --- .../sdk/internal/session/permalinks/PermalinkFactory.kt | 3 +-- .../sdk/internal/session/permalinks/ViaParameterFinder.kt | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt index f8bc1b9b34..23ed2f68a3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.permalinks import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.permalinks.PermalinkService.Companion.MATRIX_TO_URL_BASE import org.matrix.android.sdk.internal.di.UserId -import java.net.URLEncoder import javax.inject.Inject internal class PermalinkFactory @Inject constructor( @@ -49,7 +48,7 @@ internal class PermalinkFactory @Inject constructor( append(MATRIX_TO_URL_BASE) append(escape(roomId)) append( - via?.joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") } + via?.takeIf { it.isNotEmpty() }?.let { viaParameterFinder.computeViaParams(it) } ?: viaParameterFinder.computeViaParams(userId, roomId) ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt index 0da60e9ba2..f00d2a9ef7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt @@ -43,6 +43,10 @@ internal class ViaParameterFinder @Inject constructor( .joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") } } + fun computeViaParams(viaList: List): String { + return viaList.joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") } + } + fun computeViaParams(userId: String, roomId: String, max: Int): List { val userHomeserver = userId.substringAfter(":") return getUserIdsOfJoinedMembers(roomId) From f602caf323378d33cfc965aef5b6b3a52f51f65d Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 26 May 2021 11:22:56 +0200 Subject: [PATCH 09/34] Fix towncrier --- newsfragment/3401.bugfix | 3 +-- newsfragment/3406.bugfix | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 newsfragment/3406.bugfix diff --git a/newsfragment/3401.bugfix b/newsfragment/3401.bugfix index c1774521af..7f7539316d 100644 --- a/newsfragment/3401.bugfix +++ b/newsfragment/3401.bugfix @@ -1,2 +1 @@ -Fix | On Android it seems to be impossible to view the complete description of a Space (without dev tools) (#3386) -Fix | Suggest Rooms, Show a detailed view of the room on click (#3406) \ No newline at end of file +Fix | On Android it seems to be impossible to view the complete description of a Space (without dev tools) \ No newline at end of file diff --git a/newsfragment/3406.bugfix b/newsfragment/3406.bugfix new file mode 100644 index 0000000000..2623ccc1cd --- /dev/null +++ b/newsfragment/3406.bugfix @@ -0,0 +1 @@ +Fix | Suggest Rooms, Show a detailed view of the room on click \ No newline at end of file From 60dee6eea3fa9d448e36a8f9e3e35a151cf96f59 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 26 May 2021 14:25:09 +0200 Subject: [PATCH 10/34] Code review --- .../matrixto/MatrixToRoomSpaceFragment.kt | 21 +---- ...paceCardHelper.kt => SpaceCardRenderer.kt} | 76 +++++++------------ .../spaces/explore/SpaceDirectoryFragment.kt | 14 +--- .../spaces/invite/SpaceInviteBottomSheet.kt | 6 +- 4 files changed, 39 insertions(+), 78 deletions(-) rename vector/src/main/java/im/vector/app/features/matrixto/{SpaceCardHelper.kt => SpaceCardRenderer.kt} (73%) diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt index f4affcc218..d17fe7d951 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt @@ -39,7 +39,8 @@ import org.matrix.android.sdk.api.session.room.model.RoomType import javax.inject.Inject class MatrixToRoomSpaceFragment @Inject constructor( - private val avatarRenderer: AvatarRenderer + private val avatarRenderer: AvatarRenderer, + private val spaceCardRenderer: SpaceCardRenderer ) : VectorBaseFragment() { private val sharedViewModel: MatrixToBottomSheetViewModel by parentFragmentViewModel() @@ -81,13 +82,12 @@ class MatrixToRoomSpaceFragment @Inject constructor( avatarRenderer.render(matrixItem, views.matrixToCardAvatar) if (peek.roomType == RoomType.SPACE) { views.matrixToBetaTag.isVisible = true + views.matrixToAccessImage.isVisible = true if (peek.isPublic) { views.matrixToAccessText.setTextOrHide(context?.getString(R.string.public_space)) - views.matrixToAccessImage.isVisible = true views.matrixToAccessImage.setImageResource(R.drawable.ic_public_room) } else { views.matrixToAccessText.setTextOrHide(context?.getString(R.string.private_space)) - views.matrixToAccessImage.isVisible = true views.matrixToAccessImage.setImageResource(R.drawable.ic_room_private) } } else { @@ -179,20 +179,7 @@ class MatrixToRoomSpaceFragment @Inject constructor( when (state.peopleYouKnow) { is Success -> { val someYouKnow = state.peopleYouKnow.invoke() - if (someYouKnow.isEmpty()) { - views.peopleYouMayKnowText.isVisible = false - } else { - someYouKnow.forEachIndexed { index, item -> - images[index].isVisible = true - avatarRenderer.render(item, images[index]) - } - views.peopleYouMayKnowText.setTextOrHide( - resources.getQuantityString(R.plurals.space_people_you_know, - someYouKnow.count(), - someYouKnow.count() - ) - ) - } + spaceCardRenderer.renderPeopleYouKnow(views, someYouKnow) } else -> { views.peopleYouMayKnowText.isVisible = false diff --git a/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardHelper.kt b/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardRenderer.kt similarity index 73% rename from vector/src/main/java/im/vector/app/features/matrixto/SpaceCardHelper.kt rename to vector/src/main/java/im/vector/app/features/matrixto/SpaceCardRenderer.kt index 4028b3a444..e51490a59c 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardHelper.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/SpaceCardRenderer.kt @@ -20,8 +20,6 @@ import androidx.core.view.isGone import androidx.core.view.isVisible import im.vector.app.R import im.vector.app.core.extensions.setTextOrHide -import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.resources.DrawableProvider import im.vector.app.core.resources.StringProvider import im.vector.app.databinding.FragmentMatrixToRoomSpaceCardBinding import im.vector.app.features.home.AvatarRenderer @@ -31,14 +29,13 @@ import im.vector.app.features.home.room.detail.timeline.tools.linkify import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject -class SpaceCardHelper @Inject constructor( +class SpaceCardRenderer @Inject constructor( private val avatarRenderer: AvatarRenderer, - private val stringProvider: StringProvider, - private val drawableProvider: DrawableProvider, - private val colorProvider: ColorProvider + private val stringProvider: StringProvider ) { fun render(spaceSummary: RoomSummary?, @@ -74,28 +71,7 @@ class SpaceCardHelper @Inject constructor( inCard.matrixToMemberPills.isVisible = false } - val images = listOf( - inCard.knownMember1, - inCard.knownMember2, - inCard.knownMember3, - inCard.knownMember4, - inCard.knownMember5 - ).onEach { it.isGone = true } - - if (peopleYouKnow.isEmpty()) { - inCard.peopleYouMayKnowText.isVisible = false - } else { - peopleYouKnow.forEachIndexed { index, item -> - images[index].isVisible = true - avatarRenderer.render(item.toMatrixItem(), images[index]) - } - inCard.peopleYouMayKnowText.setTextOrHide( - stringProvider.getQuantityString(R.plurals.space_people_you_know, - peopleYouKnow.count(), - peopleYouKnow.count() - ) - ) - } + renderPeopleYouKnow(inCard, peopleYouKnow.map { it.toMatrixItem() }) } inCard.matrixToCardDescText.movementMethod = createLinkMovementMethod(object : TimelineEventController.UrlClickCallback { override fun onUrlClicked(url: String, title: String): Boolean { @@ -142,28 +118,32 @@ class SpaceCardHelper @Inject constructor( inCard.matrixToMemberPills.isVisible = false } - val images = listOf( - inCard.knownMember1, - inCard.knownMember2, - inCard.knownMember3, - inCard.knownMember4, - inCard.knownMember5 - ).onEach { it.isGone = true } + renderPeopleYouKnow(inCard, peopleYouKnow.map { it.toMatrixItem() }) + } + } - if (peopleYouKnow.isEmpty()) { - inCard.peopleYouMayKnowText.isVisible = false - } else { - peopleYouKnow.forEachIndexed { index, item -> - images[index].isVisible = true - avatarRenderer.render(item.toMatrixItem(), images[index]) - } - inCard.peopleYouMayKnowText.setTextOrHide( - stringProvider.getQuantityString(R.plurals.space_people_you_know, - peopleYouKnow.count(), - peopleYouKnow.count() - ) - ) + fun renderPeopleYouKnow(inCard: FragmentMatrixToRoomSpaceCardBinding, peopleYouKnow: List) { + val images = listOf( + inCard.knownMember1, + inCard.knownMember2, + inCard.knownMember3, + inCard.knownMember4, + inCard.knownMember5 + ).onEach { it.isGone = true } + + if (peopleYouKnow.isEmpty()) { + inCard.peopleYouMayKnowText.isVisible = false + } else { + peopleYouKnow.forEachIndexed { index, item -> + images[index].isVisible = true + avatarRenderer.render(item, images[index]) } + inCard.peopleYouMayKnowText.setTextOrHide( + stringProvider.getQuantityString(R.plurals.space_people_you_know, + peopleYouKnow.count(), + peopleYouKnow.count() + ) + ) } } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt index 7f45307a0d..910c21926c 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt @@ -42,7 +42,7 @@ import im.vector.app.core.utils.isValidUrl import im.vector.app.core.utils.openUrlInExternalBrowser import im.vector.app.databinding.FragmentRoomDirectoryPickerBinding import im.vector.app.features.home.room.detail.timeline.TimelineEventController -import im.vector.app.features.matrixto.SpaceCardHelper +import im.vector.app.features.matrixto.SpaceCardRenderer import im.vector.app.features.permalink.PermalinkHandler import im.vector.app.features.spaces.manage.ManageType import im.vector.app.features.spaces.manage.SpaceManageActivity @@ -61,7 +61,7 @@ data class SpaceDirectoryArgs( class SpaceDirectoryFragment @Inject constructor( private val epoxyController: SpaceDirectoryController, private val permalinkHandler: PermalinkHandler, - private val spaceCardHelper: SpaceCardHelper, + private val spaceCardRenderer: SpaceCardRenderer, private val colorProvider: ColorProvider ) : VectorBaseFragment(), SpaceDirectoryController.InteractionListener, @@ -93,12 +93,6 @@ class SpaceDirectoryFragment @Inject constructor( views.spaceCard.matrixToCardMainButton.isVisible = false views.spaceCard.matrixToCardSecondaryButton.isVisible = false - views.spaceCard.knownMember1.isVisible = false - views.spaceCard.knownMember2.isVisible = false - views.spaceCard.knownMember3.isVisible = false - views.spaceCard.knownMember4.isVisible = false - views.spaceCard.knownMember5.isVisible = false - views.spaceCard.peopleYouMayKnowText.isVisible = false } override fun onDestroyView() { @@ -118,12 +112,12 @@ class SpaceDirectoryFragment @Inject constructor( val title = getString(R.string.space_explore_activity_title) views.toolbar.title = title - spaceCardHelper.render(state.spaceSummary.invoke(), emptyList(), this, views.spaceCard) + spaceCardRenderer.render(state.spaceSummary.invoke(), emptyList(), this, views.spaceCard) } else { val title = currentParent.name ?: currentParent.canonicalAlias ?: getString(R.string.space_explore_activity_title) views.toolbar.title = title - spaceCardHelper.render(currentParent, emptyList(), this, views.spaceCard) + spaceCardRenderer.render(currentParent, emptyList(), this, views.spaceCard) } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheet.kt b/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheet.kt index d393943f72..434fa613ab 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheet.kt @@ -37,7 +37,7 @@ import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.core.utils.toast import im.vector.app.databinding.BottomSheetInvitedToSpaceBinding import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.matrixto.SpaceCardHelper +import im.vector.app.features.matrixto.SpaceCardRenderer import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -60,7 +60,7 @@ class SpaceInviteBottomSheet : VectorBaseBottomSheetDialogFragment Date: Wed, 26 May 2021 15:33:08 +0200 Subject: [PATCH 11/34] Clean doc --- .../api/session/thirdparty/model/ThirdPartyUser.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/thirdparty/model/ThirdPartyUser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/thirdparty/model/ThirdPartyUser.kt index d77dfcfe35..246813a524 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/thirdparty/model/ThirdPartyUser.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/thirdparty/model/ThirdPartyUser.kt @@ -22,16 +22,16 @@ import org.matrix.android.sdk.api.util.JsonDict @JsonClass(generateAdapter = true) data class ThirdPartyUser( - /* - Required. A Matrix User ID represting a third party user. + /** + * Required. A Matrix User ID representing a third party user. */ @Json(name = "userid") val userId: String, - /* - Required. The protocol ID that the third party location is a part of. + /** + * Required. The protocol ID that the third party location is a part of. */ @Json(name = "protocol") val protocol: String, - /* - Required. Information used to identify this third party location. + /** + * Required. Information used to identify this third party location. */ @Json(name = "fields") val fields: JsonDict ) From dec650eae82621c6e687e9f40f36000ffdf02795 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 26 May 2021 15:34:33 +0200 Subject: [PATCH 12/34] Add Gitter.im as a default in the Change Network menu (#3196) --- newsfragment/3196.feature | 1 + vector/src/main/res/values/config.xml | 1 + 2 files changed, 2 insertions(+) create mode 100644 newsfragment/3196.feature diff --git a/newsfragment/3196.feature b/newsfragment/3196.feature new file mode 100644 index 0000000000..ca84dd51c8 --- /dev/null +++ b/newsfragment/3196.feature @@ -0,0 +1 @@ +Add Gitter.im as a default in the Change Network menu \ No newline at end of file diff --git a/vector/src/main/res/values/config.xml b/vector/src/main/res/values/config.xml index 2b22b1c49b..30ca8d7f56 100755 --- a/vector/src/main/res/values/config.xml +++ b/vector/src/main/res/values/config.xml @@ -23,6 +23,7 @@ matrix.org + gitter.im From 65f8546268ab0414eb0b5aa9ce77fc019c7c5b35 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 26 May 2021 16:03:44 +0200 Subject: [PATCH 13/34] Add string --- vector/src/main/res/values/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 8649b14ca3..442b79b5b0 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1587,6 +1587,7 @@ Homeserver URL All rooms on %s server All native %s rooms + Your server Type here… From 64222ff7044c024ad4295fb9f89f84319b11d8a0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 26 May 2021 16:08:56 +0200 Subject: [PATCH 14/34] Move the class RoomDirectoryData form the SDK to the app --- .../vector/app/features/navigation/DefaultNavigator.kt | 10 +++++----- .../im/vector/app/features/navigation/Navigator.kt | 4 ++-- .../app/features/roomdirectory/PublicRoomsViewState.kt | 1 - .../app/features/roomdirectory/RoomDirectoryAction.kt | 1 - .../app/features/roomdirectory}/RoomDirectoryData.kt | 4 ++-- .../features/roomdirectory/RoomDirectoryViewModel.kt | 1 - .../roomdirectory/picker/RoomDirectoryListCreator.kt | 2 +- .../picker/RoomDirectoryPickerController.kt | 2 +- .../picker/RoomDirectoryPickerFragment.kt | 3 +-- .../roomdirectory/roompreview/RoomPreviewActivity.kt | 2 +- 10 files changed, 13 insertions(+), 17 deletions(-) rename {matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/thirdparty => vector/src/main/java/im/vector/app/features/roomdirectory}/RoomDirectoryData.kt (92%) diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 3abf01583c..ed57ccb04c 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -65,6 +65,7 @@ import im.vector.app.features.pin.PinActivity import im.vector.app.features.pin.PinArgs import im.vector.app.features.pin.PinMode import im.vector.app.features.roomdirectory.RoomDirectoryActivity +import im.vector.app.features.roomdirectory.RoomDirectoryData import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity import im.vector.app.features.roomdirectory.roompreview.RoomPreviewActivity import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData @@ -86,7 +87,6 @@ import im.vector.app.features.widgets.WidgetArgsBuilder import im.vector.app.space import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom -import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData import org.matrix.android.sdk.api.session.terms.TermsService import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.WidgetType @@ -129,7 +129,7 @@ class DefaultNavigator @Inject constructor( } appStateHandler.setCurrentSpace(spaceId) when (postSwitchSpaceAction) { - Navigator.PostSwitchSpaceAction.None -> { + Navigator.PostSwitchSpaceAction.None -> { // go back to home if we are showing room details? // This is a bit ugly, but the navigator is supposed to know about the activity stack if (context is RoomDetailActivity) { @@ -139,7 +139,7 @@ class DefaultNavigator @Inject constructor( Navigator.PostSwitchSpaceAction.OpenAddExistingRooms -> { startActivity(context, SpaceManageActivity.newIntent(context, spaceId, ManageType.AddRooms), false) } - is Navigator.PostSwitchSpaceAction.OpenDefaultRoom -> { + is Navigator.PostSwitchSpaceAction.OpenDefaultRoom -> { val args = RoomDetailArgs( postSwitchSpaceAction.roomId, eventId = null, @@ -278,7 +278,7 @@ class DefaultNavigator @Inject constructor( val intent = RoomDirectoryActivity.getIntent(context, initialFilter) context.startActivity(intent) } - is RoomGroupingMethod.BySpace -> { + is RoomGroupingMethod.BySpace -> { val selectedSpace = groupingMethod.space() if (selectedSpace == null) { val intent = RoomDirectoryActivity.getIntent(context, initialFilter) @@ -320,7 +320,7 @@ class DefaultNavigator @Inject constructor( val intent = InviteUsersToRoomActivity.getIntent(context, roomId) context.startActivity(intent) } - is RoomGroupingMethod.BySpace -> { + is RoomGroupingMethod.BySpace -> { if (currentGroupingMethod.spaceSummary != null) { // let user decides if he does it from space or room (context as? AppCompatActivity)?.supportFragmentManager?.let { fm -> diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 444c48bddb..cf0263a1e8 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -26,11 +26,11 @@ import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.login.LoginConfig import im.vector.app.features.media.AttachmentData import im.vector.app.features.pin.PinMode +import im.vector.app.features.roomdirectory.RoomDirectoryData import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.share.SharedData import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom -import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData import org.matrix.android.sdk.api.session.terms.TermsService import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.util.MatrixItem @@ -44,7 +44,7 @@ interface Navigator { sealed class PostSwitchSpaceAction { object None : PostSwitchSpaceAction() data class OpenDefaultRoom(val roomId: String, val showShareSheet: Boolean) : PostSwitchSpaceAction() - object OpenAddExistingRooms: PostSwitchSpaceAction() + object OpenAddExistingRooms : PostSwitchSpaceAction() } fun switchToSpace(context: Context, spaceId: String, postSwitchSpaceAction: PostSwitchSpaceAction) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsViewState.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsViewState.kt index 16e5428b9c..fdab72caba 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsViewState.kt @@ -21,7 +21,6 @@ import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom -import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData data class PublicRoomsViewState( // The current filter diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryAction.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryAction.kt index a94cb7709f..77eec57ab3 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryAction.kt @@ -17,7 +17,6 @@ package im.vector.app.features.roomdirectory import im.vector.app.core.platform.VectorViewModelAction -import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData sealed class RoomDirectoryAction : VectorViewModelAction { data class SetRoomDirectoryData(val roomDirectoryData: RoomDirectoryData) : RoomDirectoryAction() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/thirdparty/RoomDirectoryData.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryData.kt similarity index 92% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/thirdparty/RoomDirectoryData.kt rename to vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryData.kt index 91f429d773..a2e1bc8bee 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/thirdparty/RoomDirectoryData.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryData.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2021 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.session.room.model.thirdparty +package im.vector.app.features.roomdirectory /** * This class describes a rooms directory server. diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt index f64105b759..7b717df6f2 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt @@ -37,7 +37,6 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFilter import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams -import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.rx.rx import timber.log.Timber diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt index d51ad5040b..a55d028e10 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt @@ -18,8 +18,8 @@ package im.vector.app.features.roomdirectory.picker import im.vector.app.R import im.vector.app.core.resources.StringArrayProvider +import im.vector.app.features.roomdirectory.RoomDirectoryData import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt index 75e9807bd0..9e2006b145 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt @@ -25,7 +25,7 @@ import im.vector.app.core.epoxy.errorWithRetryItem import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.resources.StringProvider -import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData +import im.vector.app.features.roomdirectory.RoomDirectoryData import javax.inject.Inject class RoomDirectoryPickerController @Inject constructor(private val stringProvider: StringProvider, diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt index 7f205078f1..7643503d5e 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt @@ -31,11 +31,10 @@ import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomDirectoryPickerBinding import im.vector.app.features.roomdirectory.RoomDirectoryAction +import im.vector.app.features.roomdirectory.RoomDirectoryData import im.vector.app.features.roomdirectory.RoomDirectorySharedAction import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel import im.vector.app.features.roomdirectory.RoomDirectoryViewModel - -import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData import timber.log.Timber import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewActivity.kt index 445d02d6e2..f9cf8e6dd7 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewActivity.kt @@ -25,9 +25,9 @@ import im.vector.app.core.extensions.addFragment import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleBinding +import im.vector.app.features.roomdirectory.RoomDirectoryData import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom -import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData import org.matrix.android.sdk.api.util.MatrixItem import timber.log.Timber From 4641f778425c33e1dab588a0867731afeb104d4d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 26 May 2021 17:38:35 +0200 Subject: [PATCH 15/34] Better understanding of the model: server / protocol Iso Element Web on this part. --- .../roomdirectory/RoomDirectoryData.kt | 19 ++-- .../roomdirectory/RoomDirectoryServer.kt | 32 ++++++ .../roomdirectory/RoomDirectoryViewModel.kt | 4 +- .../picker/RoomDirectoryListCreator.kt | 98 +++++++++++++------ .../picker/RoomDirectoryPickerController.kt | 75 +++++++++----- .../picker/RoomDirectoryServerItem.kt | 46 +++++++++ .../main/res/layout/item_room_directory.xml | 32 +++--- .../res/layout/item_room_directory_server.xml | 48 +++++++++ 8 files changed, 263 insertions(+), 91 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryServer.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryServerItem.kt create mode 100644 vector/src/main/res/layout/item_room_directory_server.xml diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryData.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryData.kt index a2e1bc8bee..49bb769460 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryData.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryData.kt @@ -17,10 +17,9 @@ package im.vector.app.features.roomdirectory /** - * This class describes a rooms directory server. + * This class describes a rooms directory server protocol. */ data class RoomDirectoryData( - /** * The server name (might be null) * Set null when the server is the current user's home server. @@ -30,7 +29,12 @@ data class RoomDirectoryData( /** * The display name (the server description) */ - val displayName: String = DEFAULT_HOME_SERVER_NAME, + val displayName: String = MATRIX_PROTOCOL_NAME, + + /** + * the avatar url + */ + val avatarUrl: String? = null, /** * The third party server identifier @@ -40,15 +44,10 @@ data class RoomDirectoryData( /** * Tell if all the federated servers must be included */ - val includeAllNetworks: Boolean = false, - - /** - * the avatar url - */ - val avatarUrl: String? = null + val includeAllNetworks: Boolean = false ) { companion object { - const val DEFAULT_HOME_SERVER_NAME = "Matrix" + const val MATRIX_PROTOCOL_NAME = "Matrix" } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryServer.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryServer.kt new file mode 100644 index 0000000000..63ca4998f4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryServer.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomdirectory + +data class RoomDirectoryServer( + val serverName: String, + + /** + * True if this is the current user server + */ + val isUserServer: Boolean, + + /** + * Supported protocols + * TODO Rename RoomDirectoryData to RoomDirectoryProtocols + */ + val protocols: List +) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt index 7b717df6f2..dc1cbfc58d 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt @@ -229,9 +229,7 @@ class RoomDirectoryViewModel @AssistedInject constructor( Timber.w("Try to join an already joining room. Should not happen") return@withState } - val viaServers = state.roomDirectoryData.homeServer - ?.let { listOf(it) } - .orEmpty() + val viaServers = listOfNotNull(state.roomDirectoryData.homeServer) viewModelScope.launch { try { session.joinRoom(action.roomId, viaServers = viaServers) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt index a55d028e10..e3cd9278a3 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt @@ -19,54 +19,88 @@ package im.vector.app.features.roomdirectory.picker import im.vector.app.R import im.vector.app.core.resources.StringArrayProvider import im.vector.app.features.roomdirectory.RoomDirectoryData +import im.vector.app.features.roomdirectory.RoomDirectoryServer import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol import javax.inject.Inject -class RoomDirectoryListCreator @Inject constructor(private val stringArrayProvider: StringArrayProvider, - private val session: Session) { +class RoomDirectoryListCreator @Inject constructor( + private val stringArrayProvider: StringArrayProvider, + private val session: Session +) { - fun computeDirectories(thirdPartyProtocolData: Map): List { - val result = ArrayList() + fun computeDirectories(thirdPartyProtocolData: Map): List { + val result = ArrayList() + + val protocols = ArrayList() // Add user homeserver name val userHsName = session.myUserId.substringAfter(":") - result.add(RoomDirectoryData( - displayName = userHsName, - includeAllNetworks = true - )) - - // Add user's HS but for Matrix public rooms only - result.add(RoomDirectoryData()) - - // Add custom directory servers - val hsNamesList = stringArrayProvider.getStringArray(R.array.room_directory_servers) - hsNamesList.forEach { - if (it != userHsName) { - // Use the server name as a default display name - result.add(RoomDirectoryData( - homeServer = it, - displayName = it, - includeAllNetworks = true - )) - } - } + // Add default protocol + protocols.add( + RoomDirectoryData( + homeServer = null, + displayName = RoomDirectoryData.MATRIX_PROTOCOL_NAME, + includeAllNetworks = false + ) + ) // Add result of the request thirdPartyProtocolData.forEach { it.value.instances?.forEach { thirdPartyProtocolInstance -> - result.add(RoomDirectoryData( - homeServer = null, - displayName = thirdPartyProtocolInstance.desc ?: "", - thirdPartyInstanceId = thirdPartyProtocolInstance.instanceId, - includeAllNetworks = false, - // Default to protocol icon - avatarUrl = thirdPartyProtocolInstance.icon ?: it.value.icon - )) + protocols.add( + RoomDirectoryData( + homeServer = null, + displayName = thirdPartyProtocolInstance.desc ?: "", + thirdPartyInstanceId = thirdPartyProtocolInstance.instanceId, + includeAllNetworks = false, + // Default to protocol icon + avatarUrl = thirdPartyProtocolInstance.icon ?: it.value.icon + ) + ) } } + // Add all rooms + protocols.add( + RoomDirectoryData( + homeServer = null, + displayName = RoomDirectoryData.MATRIX_PROTOCOL_NAME, + includeAllNetworks = true + ) + ) + + result.add( + RoomDirectoryServer( + serverName = userHsName, + isUserServer = true, + protocols = protocols + ) + ) + + // Add custom directory servers, form the config file, excluding the current user homeserver + stringArrayProvider.getStringArray(R.array.room_directory_servers) + .filter { it != userHsName } + .forEach { + // Use the server name as a default display name + result.add( + RoomDirectoryServer( + serverName = it, + isUserServer = false, + protocols = listOf( + RoomDirectoryData( + homeServer = it, + displayName = RoomDirectoryData.MATRIX_PROTOCOL_NAME, + includeAllNetworks = false + ) + ) + ) + ) + } + + // TODO Add manually added server by the user + return result } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt index 9e2006b145..4f9d1d0394 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt @@ -21,33 +21,38 @@ import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Success import im.vector.app.R +import im.vector.app.core.epoxy.dividerItem import im.vector.app.core.epoxy.errorWithRetryItem import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.error.ErrorFormatter +import im.vector.app.core.extensions.join +import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.features.roomdirectory.RoomDirectoryData +import im.vector.app.features.roomdirectory.RoomDirectoryServer import javax.inject.Inject -class RoomDirectoryPickerController @Inject constructor(private val stringProvider: StringProvider, - private val errorFormatter: ErrorFormatter, - private val roomDirectoryListCreator: RoomDirectoryListCreator +class RoomDirectoryPickerController @Inject constructor( + private val stringProvider: StringProvider, + colorProvider: ColorProvider, + private val errorFormatter: ErrorFormatter, + private val roomDirectoryListCreator: RoomDirectoryListCreator ) : TypedEpoxyController() { var callback: Callback? = null - var index = 0 + private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color) override fun buildModels(viewState: RoomDirectoryPickerViewState) { val host = this - val asyncThirdPartyProtocol = viewState.asyncThirdPartyRequest - when (asyncThirdPartyProtocol) { + when (val asyncThirdPartyProtocol = viewState.asyncThirdPartyRequest) { is Success -> { val directories = roomDirectoryListCreator.computeDirectories(asyncThirdPartyProtocol()) - - directories.forEach { - buildDirectory(it) - } + directories.join( + each = { _, roomDirectoryServer -> buildDirectory(roomDirectoryServer) }, + between = { idx, _ -> buildDivider(idx) } + ) } is Incomplete -> { loadingItem { @@ -64,28 +69,46 @@ class RoomDirectoryPickerController @Inject constructor(private val stringProvid } } - private fun buildDirectory(roomDirectoryData: RoomDirectoryData) { + private fun buildDivider(idx: Int) { val host = this - roomDirectoryItem { - id(host.index++) + dividerItem { + id("divider_${idx}") + color(host.dividerColor) + } + } - directoryName(roomDirectoryData.displayName) + private fun buildDirectory(roomDirectoryServer: RoomDirectoryServer) { + val host = this + roomDirectoryServerItem { + id("server_" + roomDirectoryServer.serverName) + serverName(roomDirectoryServer.serverName) - val description = when { - roomDirectoryData.includeAllNetworks -> - host.stringProvider.getString(R.string.directory_server_all_rooms_on_server, roomDirectoryData.displayName) - "Matrix" == roomDirectoryData.displayName -> - host.stringProvider.getString(R.string.directory_server_native_rooms, roomDirectoryData.displayName) - else -> - null + if (roomDirectoryServer.isUserServer) { + serverDescription(host.stringProvider.getString(R.string.directory_your_server)) } + } - directoryDescription(description) - directoryAvatarUrl(roomDirectoryData.avatarUrl) - includeAllNetworks(roomDirectoryData.includeAllNetworks) + roomDirectoryServer.protocols.forEach { roomDirectoryData -> + roomDirectoryItem { + id("server_" + roomDirectoryServer.serverName + "_proto_" + roomDirectoryData.displayName) + directoryName( + if (roomDirectoryData.includeAllNetworks) { + host.stringProvider.getString(R.string.directory_server_all_rooms_on_server, roomDirectoryServer.serverName) + } else { + roomDirectoryData.displayName + } + ) + if (roomDirectoryData.displayName == RoomDirectoryData.MATRIX_PROTOCOL_NAME && !roomDirectoryData.includeAllNetworks) { + directoryDescription( + host.stringProvider.getString(R.string.directory_server_native_rooms, roomDirectoryServer.serverName) + ) + } + directoryAvatarUrl(roomDirectoryData.avatarUrl) + includeAllNetworks(roomDirectoryData.includeAllNetworks) - globalListener { - host.callback?.onRoomDirectoryClicked(roomDirectoryData) + globalListener { + host.callback?.onRoomDirectoryClicked(roomDirectoryData) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryServerItem.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryServerItem.kt new file mode 100644 index 0000000000..83acfa581c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryServerItem.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomdirectory.picker + +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.extensions.setTextOrHide + +@EpoxyModelClass(layout = R.layout.item_room_directory_server) +abstract class RoomDirectoryServerItem : VectorEpoxyModel() { + + @EpoxyAttribute + var serverName: String? = null + + @EpoxyAttribute + var serverDescription: String? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.nameView.text = serverName + holder.descriptionView.setTextOrHide(serverDescription) + } + + class Holder : VectorEpoxyHolder() { + val nameView by bind(R.id.itemRoomDirectoryServerName) + val descriptionView by bind(R.id.itemRoomDirectoryServerDescription) + } +} diff --git a/vector/src/main/res/layout/item_room_directory.xml b/vector/src/main/res/layout/item_room_directory.xml index 391f52ad92..f7bd3344e4 100644 --- a/vector/src/main/res/layout/item_room_directory.xml +++ b/vector/src/main/res/layout/item_room_directory.xml @@ -1,5 +1,4 @@ - + tools:src="@drawable/network_matrix" /> - - + tools:text="@string/directory_server_native_rooms" + tools:visibility="visible" /> \ No newline at end of file diff --git a/vector/src/main/res/layout/item_room_directory_server.xml b/vector/src/main/res/layout/item_room_directory_server.xml new file mode 100644 index 0000000000..5459652e2c --- /dev/null +++ b/vector/src/main/res/layout/item_room_directory_server.xml @@ -0,0 +1,48 @@ + + + + + + + + \ No newline at end of file From f30eb4af8a45753f655efd309ccb808fbf49d7c9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 26 May 2021 18:17:58 +0200 Subject: [PATCH 16/34] Add checked mark on the current item --- .../roomdirectory/picker/RoomDirectoryItem.kt | 7 ++++++ .../picker/RoomDirectoryPickerController.kt | 3 ++- .../picker/RoomDirectoryPickerFragment.kt | 5 ++++ .../main/res/layout/item_room_directory.xml | 25 ++++++++++++++++--- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryItem.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryItem.kt index 7b2e329b6a..7cf8e538ac 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryItem.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryItem.kt @@ -16,10 +16,12 @@ package im.vector.app.features.roomdirectory.picker +import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.core.view.isInvisible +import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R @@ -43,6 +45,9 @@ abstract class RoomDirectoryItem : VectorEpoxyModel() @EpoxyAttribute var includeAllNetworks: Boolean = false + @EpoxyAttribute + var checked: Boolean = false + @EpoxyAttribute var globalListener: (() -> Unit)? = null @@ -63,6 +68,7 @@ abstract class RoomDirectoryItem : VectorEpoxyModel() holder.nameView.text = directoryName holder.descriptionView.setTextOrHide(directoryDescription) + holder.checkedView.isVisible = checked } class Holder : VectorEpoxyHolder() { @@ -71,5 +77,6 @@ abstract class RoomDirectoryItem : VectorEpoxyModel() val avatarView by bind(R.id.itemRoomDirectoryAvatar) val nameView by bind(R.id.itemRoomDirectoryName) val descriptionView by bind(R.id.itemRoomDirectoryDescription) + val checkedView by bind(R.id.itemRoomDirectoryChecked) } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt index 4f9d1d0394..c3f461cb46 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt @@ -39,6 +39,7 @@ class RoomDirectoryPickerController @Inject constructor( private val roomDirectoryListCreator: RoomDirectoryListCreator ) : TypedEpoxyController() { + var currentRoomDirectoryData: RoomDirectoryData? = null var callback: Callback? = null private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color) @@ -105,7 +106,7 @@ class RoomDirectoryPickerController @Inject constructor( } directoryAvatarUrl(roomDirectoryData.avatarUrl) includeAllNetworks(roomDirectoryData.includeAllNetworks) - + checked(roomDirectoryData == host.currentRoomDirectoryData) globalListener { host.callback?.onRoomDirectoryClicked(roomDirectoryData) } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt index 7643503d5e..701e8632c4 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt @@ -64,6 +64,11 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java) setupRecyclerView() + + // Give the current data to our controller. There maybe a better way to do that... + withState(viewModel) { + roomDirectoryPickerController.currentRoomDirectoryData = it.roomDirectoryData + } } override fun onDestroyView() { diff --git a/vector/src/main/res/layout/item_room_directory.xml b/vector/src/main/res/layout/item_room_directory.xml index f7bd3344e4..19a457ec37 100644 --- a/vector/src/main/res/layout/item_room_directory.xml +++ b/vector/src/main/res/layout/item_room_directory.xml @@ -30,7 +30,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="8dp" - android:layout_marginEnd="@dimen/layout_horizontal_margin" + android:layout_marginEnd="8dp" android:ellipsize="end" android:lines="1" android:maxLines="2" @@ -38,10 +38,11 @@ android:textSize="15sp" android:textStyle="bold" app:layout_constraintBottom_toTopOf="@+id/itemRoomDirectoryDescription" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toStartOf="@+id/itemRoomDirectoryChecked" app:layout_constraintStart_toEndOf="@id/itemRoomDirectoryAvatar" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_chainStyle="packed" + app:layout_goneMarginEnd="@dimen/layout_horizontal_margin" tools:text="@tools:sample/lorem/random" /> + + \ No newline at end of file From cbed1afaaad1a12c8fd907aace63c29a403f06a6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 27 May 2021 14:05:13 +0200 Subject: [PATCH 17/34] Add custom server --- newsfragment/1458.feature | 1 + .../app/core/ui/list/GenericSpaceItem.kt | 45 +++++++ .../discovery/SettingsContinueCancelItem.kt | 4 + .../app/features/form/FormEditTextItem.kt | 1 + .../roomdirectory/RoomDirectoryServer.kt | 5 + .../picker/RoomDirectoryListCreator.kt | 25 +++- .../picker/RoomDirectoryPickerAction.kt | 7 ++ .../picker/RoomDirectoryPickerController.kt | 111 +++++++++++++++++- .../picker/RoomDirectoryPickerFragment.kt | 48 ++++++-- .../picker/RoomDirectoryPickerViewModel.kt | 97 ++++++++++++++- .../picker/RoomDirectoryPickerViewState.kt | 6 +- .../picker/RoomDirectoryServerItem.kt | 13 ++ .../ui/SharedPreferencesUiStateRepository.kt | 16 ++- .../app/features/ui/UiStateRepository.kt | 4 + .../main/res/layout/item_generic_space.xml | 5 + .../res/layout/item_room_directory_server.xml | 27 ++++- .../res/menu/menu_directory_server_picker.xml | 13 -- vector/src/main/res/values/strings.xml | 5 +- 18 files changed, 387 insertions(+), 46 deletions(-) create mode 100644 newsfragment/1458.feature create mode 100644 vector/src/main/java/im/vector/app/core/ui/list/GenericSpaceItem.kt create mode 100644 vector/src/main/res/layout/item_generic_space.xml delete mode 100644 vector/src/main/res/menu/menu_directory_server_picker.xml diff --git a/newsfragment/1458.feature b/newsfragment/1458.feature new file mode 100644 index 0000000000..ded4f549ed --- /dev/null +++ b/newsfragment/1458.feature @@ -0,0 +1 @@ +Allow user to add custom "network" in room search \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericSpaceItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/GenericSpaceItem.kt new file mode 100644 index 0000000000..137fac9abe --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/list/GenericSpaceItem.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.app.core.ui.list + +import android.view.View +import androidx.core.view.updateLayoutParams +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel + +/** + * A generic item with empty space. + */ +@EpoxyModelClass(layout = R.layout.item_generic_space) +abstract class GenericSpaceItem : VectorEpoxyModel() { + + @EpoxyAttribute + var heightInPx: Int = 0 + + override fun bind(holder: Holder) { + super.bind(holder) + holder.space.updateLayoutParams { + height = heightInPx + } + } + + class Holder : VectorEpoxyHolder() { + val space by bind(R.id.item_generic_space) + } +} diff --git a/vector/src/main/java/im/vector/app/features/discovery/SettingsContinueCancelItem.kt b/vector/src/main/java/im/vector/app/features/discovery/SettingsContinueCancelItem.kt index b59b24fe55..47059128a1 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/SettingsContinueCancelItem.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/SettingsContinueCancelItem.kt @@ -33,6 +33,9 @@ abstract class SettingsContinueCancelItem : EpoxyModelWithHolder() { @EpoxyAttribute var inputType: Int? = null + // TODO Should be true by default @EpoxyAttribute var singleLine: Boolean? = null diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryServer.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryServer.kt index 63ca4998f4..0f29ae5986 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryServer.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryServer.kt @@ -24,6 +24,11 @@ data class RoomDirectoryServer( */ val isUserServer: Boolean, + /** + * True if manually added, so it can be removed by the user + */ + val isManuallyAdded: Boolean, + /** * Supported protocols * TODO Rename RoomDirectoryData to RoomDirectoryProtocols diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt index e3cd9278a3..65d8f2d1cb 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt @@ -29,7 +29,8 @@ class RoomDirectoryListCreator @Inject constructor( private val session: Session ) { - fun computeDirectories(thirdPartyProtocolData: Map): List { + fun computeDirectories(thirdPartyProtocolData: Map, + customHomeservers: Set): List { val result = ArrayList() val protocols = ArrayList() @@ -75,6 +76,7 @@ class RoomDirectoryListCreator @Inject constructor( RoomDirectoryServer( serverName = userHsName, isUserServer = true, + isManuallyAdded = false, protocols = protocols ) ) @@ -88,6 +90,7 @@ class RoomDirectoryListCreator @Inject constructor( RoomDirectoryServer( serverName = it, isUserServer = false, + isManuallyAdded = false, protocols = listOf( RoomDirectoryData( homeServer = it, @@ -99,7 +102,25 @@ class RoomDirectoryListCreator @Inject constructor( ) } - // TODO Add manually added server by the user + // Add manually added server by the user + customHomeservers + .forEach { + // Use the server name as a default display name + result.add( + RoomDirectoryServer( + serverName = it, + isUserServer = false, + isManuallyAdded = true, + protocols = listOf( + RoomDirectoryData( + homeServer = it, + displayName = RoomDirectoryData.MATRIX_PROTOCOL_NAME, + includeAllNetworks = false + ) + ) + ) + ) + } return result } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerAction.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerAction.kt index 36f2cd4296..8be3c6b2b2 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerAction.kt @@ -17,7 +17,14 @@ package im.vector.app.features.roomdirectory.picker import im.vector.app.core.platform.VectorViewModelAction +import im.vector.app.features.roomdirectory.RoomDirectoryServer sealed class RoomDirectoryPickerAction : VectorViewModelAction { object Retry : RoomDirectoryPickerAction() + object EnterEditMode : RoomDirectoryPickerAction() + object ExitEditMode : RoomDirectoryPickerAction() + data class SetServerUrl(val url: String) : RoomDirectoryPickerAction() + data class RemoveServer(val roomDirectoryServer: RoomDirectoryServer) : RoomDirectoryPickerAction() + + object Submit : RoomDirectoryPickerAction() } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt index c3f461cb46..0a28ad5d52 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt @@ -16,10 +16,14 @@ package im.vector.app.features.roomdirectory.picker +import android.text.InputType +import android.view.View import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Incomplete +import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized import im.vector.app.R import im.vector.app.core.epoxy.dividerItem import im.vector.app.core.epoxy.errorWithRetryItem @@ -28,13 +32,22 @@ import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.join import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider +import im.vector.app.core.ui.list.genericButtonItem +import im.vector.app.core.ui.list.genericSpaceItem +import im.vector.app.core.utils.DimensionConverter +import im.vector.app.features.discovery.settingsContinueCancelItem +import im.vector.app.features.discovery.settingsInformationItem +import im.vector.app.features.form.formEditTextItem import im.vector.app.features.roomdirectory.RoomDirectoryData import im.vector.app.features.roomdirectory.RoomDirectoryServer +import org.matrix.android.sdk.api.failure.Failure import javax.inject.Inject +import javax.net.ssl.HttpsURLConnection class RoomDirectoryPickerController @Inject constructor( private val stringProvider: StringProvider, - colorProvider: ColorProvider, + private val colorProvider: ColorProvider, + private val dimensionConverter: DimensionConverter, private val errorFormatter: ErrorFormatter, private val roomDirectoryListCreator: RoomDirectoryListCreator ) : TypedEpoxyController() { @@ -44,16 +57,24 @@ class RoomDirectoryPickerController @Inject constructor( private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color) - override fun buildModels(viewState: RoomDirectoryPickerViewState) { + override fun buildModels(data: RoomDirectoryPickerViewState) { val host = this - when (val asyncThirdPartyProtocol = viewState.asyncThirdPartyRequest) { + when (val asyncThirdPartyProtocol = data.asyncThirdPartyRequest) { is Success -> { - val directories = roomDirectoryListCreator.computeDirectories(asyncThirdPartyProtocol()) + val directories = roomDirectoryListCreator.computeDirectories( + asyncThirdPartyProtocol(), + data.customHomeservers + ) directories.join( each = { _, roomDirectoryServer -> buildDirectory(roomDirectoryServer) }, between = { idx, _ -> buildDivider(idx) } ) + buildForm(data) + genericSpaceItem { + id("space_bottom") + heightInPx(host.dimensionConverter.dpToPx(16)) + } } is Incomplete -> { loadingItem { @@ -70,6 +91,77 @@ class RoomDirectoryPickerController @Inject constructor( } } + private fun buildForm(data: RoomDirectoryPickerViewState) { + buildDivider(1000) + val host = this + if (data.inEditMode) { + genericSpaceItem { + id("form_space") + heightInPx(host.dimensionConverter.dpToPx(16)) + } + settingsInformationItem { + id("form_notice") + message(host.stringProvider.getString(R.string.directory_add_a_new_server_prompt)) + colorProvider(host.colorProvider) + } + genericSpaceItem { + id("form_space_2") + heightInPx(host.dimensionConverter.dpToPx(8)) + } + formEditTextItem { + id("edit") + showBottomSeparator(false) + value(data.enteredServer) + hint(host.stringProvider.getString(R.string.directory_server_placeholder)) + inputType(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI) + onTextChange { text -> + host.callback?.onEnterServerChange(text) + } + when (data.addServerAsync) { + Uninitialized -> enabled(true) + is Loading -> enabled(false) + is Success -> enabled(false) + is Fail -> { + enabled(true) + errorMessage(host.getErrorMessage(data.addServerAsync.error)) + } + } + } + when (data.addServerAsync) { + Uninitialized, + is Fail -> settingsContinueCancelItem { + id("continueCancel") + continueText(host.stringProvider.getString(R.string.ok)) + canContinue(data.enteredServer.isNotEmpty()) + continueOnClick { host.callback?.onSubmitServer() } + cancelOnClick { host.callback?.onCancelEnterServer() } + } + is Loading -> loadingItem { + id("addLoading") + } + is Success -> Unit /* This is a transitive state */ + } + } else { + genericButtonItem { + id("add") + text(host.stringProvider.getString(R.string.directory_add_a_new_server)) + textColor(host.colorProvider.getColor(R.color.riotx_accent)) + buttonClickAction(View.OnClickListener { + host.callback?.onStartEnterServer() + }) + } + } + } + + private fun getErrorMessage(error: Throwable): String { + return if (error is Failure.ServerError + && error.httpCode == HttpsURLConnection.HTTP_INTERNAL_ERROR /* 500 */) { + stringProvider.getString(R.string.directory_add_a_new_server_error) + } else { + errorFormatter.toHumanReadable(error) + } + } + private fun buildDivider(idx: Int) { val host = this dividerItem { @@ -81,8 +173,10 @@ class RoomDirectoryPickerController @Inject constructor( private fun buildDirectory(roomDirectoryServer: RoomDirectoryServer) { val host = this roomDirectoryServerItem { - id("server_" + roomDirectoryServer.serverName) + id("server_$roomDirectoryServer") serverName(roomDirectoryServer.serverName) + canRemove(roomDirectoryServer.isManuallyAdded) + removeListener { host.callback?.onRemoveServer(roomDirectoryServer) } if (roomDirectoryServer.isUserServer) { serverDescription(host.stringProvider.getString(R.string.directory_your_server)) @@ -91,7 +185,7 @@ class RoomDirectoryPickerController @Inject constructor( roomDirectoryServer.protocols.forEach { roomDirectoryData -> roomDirectoryItem { - id("server_" + roomDirectoryServer.serverName + "_proto_" + roomDirectoryData.displayName) + id("server_${roomDirectoryServer}_proto_$roomDirectoryData") directoryName( if (roomDirectoryData.includeAllNetworks) { host.stringProvider.getString(R.string.directory_server_all_rooms_on_server, roomDirectoryServer.serverName) @@ -117,5 +211,10 @@ class RoomDirectoryPickerController @Inject constructor( interface Callback { fun onRoomDirectoryClicked(roomDirectoryData: RoomDirectoryData) fun retry() + fun onStartEnterServer() + fun onEnterServerChange(server: String) + fun onSubmitServer() + fun onCancelEnterServer() + fun onRemoveServer(roomDirectoryServer: RoomDirectoryServer) } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt index 701e8632c4..e3c39a2ccb 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt @@ -28,20 +28,22 @@ import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith +import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomDirectoryPickerBinding import im.vector.app.features.roomdirectory.RoomDirectoryAction import im.vector.app.features.roomdirectory.RoomDirectoryData +import im.vector.app.features.roomdirectory.RoomDirectoryServer import im.vector.app.features.roomdirectory.RoomDirectorySharedAction import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel import im.vector.app.features.roomdirectory.RoomDirectoryViewModel import timber.log.Timber import javax.inject.Inject -// TODO Menu to add custom room directory (not done in RiotWeb so far...) class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerViewModelFactory: RoomDirectoryPickerViewModel.Factory, private val roomDirectoryPickerController: RoomDirectoryPickerController ) : VectorBaseFragment(), + OnBackPressed, RoomDirectoryPickerController.Callback { private val viewModel: RoomDirectoryViewModel by activityViewModel() @@ -77,18 +79,6 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie super.onDestroyView() } - override fun getMenuRes() = R.menu.menu_directory_server_picker - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == R.id.action_add_custom_hs) { - // TODO - vectorBaseActivity.notImplemented("Entering custom homeserver") - return true - } - - return super.onOptionsItemSelected(item) - } - private fun setupRecyclerView() { views.roomDirectoryPickerList.configureWith(roomDirectoryPickerController) roomDirectoryPickerController.callback = this @@ -101,6 +91,26 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie sharedActionViewModel.post(RoomDirectorySharedAction.Back) } + override fun onStartEnterServer() { + pickerViewModel.handle(RoomDirectoryPickerAction.EnterEditMode) + } + + override fun onCancelEnterServer() { + pickerViewModel.handle(RoomDirectoryPickerAction.ExitEditMode) + } + + override fun onEnterServerChange(server: String) { + pickerViewModel.handle(RoomDirectoryPickerAction.SetServerUrl(server)) + } + + override fun onSubmitServer() { + pickerViewModel.handle(RoomDirectoryPickerAction.Submit) + } + + override fun onRemoveServer(roomDirectoryServer: RoomDirectoryServer) { + pickerViewModel.handle(RoomDirectoryPickerAction.RemoveServer(roomDirectoryServer)) + } + override fun onResume() { super.onResume() (activity as? AppCompatActivity)?.supportActionBar?.setTitle(R.string.select_room_directory) @@ -115,4 +125,16 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie // Populate list with Epoxy roomDirectoryPickerController.setData(state) } + + override fun onBackPressed(toolbarButton: Boolean): Boolean { + // Leave the add server mode if started + return withState(pickerViewModel) { + if (it.inEditMode) { + pickerViewModel.handle(RoomDirectoryPickerAction.ExitEditMode) + true + } else { + false + } + } + } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt index d85b7937a2..049293cafd 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt @@ -22,18 +22,24 @@ import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.ui.UiStateRepository import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams -class RoomDirectoryPickerViewModel @AssistedInject constructor(@Assisted initialState: RoomDirectoryPickerViewState, - private val session: Session) - : VectorViewModel(initialState) { +class RoomDirectoryPickerViewModel @AssistedInject constructor( + @Assisted initialState: RoomDirectoryPickerViewState, + private val session: Session, + private val uiStateRepository: UiStateRepository +) : VectorViewModel(initialState) { @AssistedFactory interface Factory { @@ -51,6 +57,7 @@ class RoomDirectoryPickerViewModel @AssistedInject constructor(@Assisted initial init { load() + loadCustomRoomDirectoryHomeservers() } private fun load() { @@ -71,9 +78,89 @@ class RoomDirectoryPickerViewModel @AssistedInject constructor(@Assisted initial } } + private fun loadCustomRoomDirectoryHomeservers() { + setState { + copy( + customHomeservers = uiStateRepository.getCustomRoomDirectoryHomeservers(session.sessionId) + ) + } + } + override fun handle(action: RoomDirectoryPickerAction) { when (action) { - RoomDirectoryPickerAction.Retry -> load() + RoomDirectoryPickerAction.Retry -> load() + RoomDirectoryPickerAction.EnterEditMode -> handleEnterEditMode() + RoomDirectoryPickerAction.ExitEditMode -> handleExitEditMode() + is RoomDirectoryPickerAction.SetServerUrl -> handleSetServerUrl(action) + RoomDirectoryPickerAction.Submit -> handleSubmit() + is RoomDirectoryPickerAction.RemoveServer -> handleRemoveServer(action) + }.exhaustive + } + + private fun handleEnterEditMode() { + setState { + copy( + inEditMode = true, + enteredServer = "", + addServerAsync = Uninitialized + ) + } + } + + private fun handleExitEditMode() { + setState { + copy( + inEditMode = false, + enteredServer = "", + addServerAsync = Uninitialized + ) + } + } + + private fun handleSetServerUrl(action: RoomDirectoryPickerAction.SetServerUrl) { + setState { + copy( + enteredServer = action.url, + ) + } + } + + private fun handleSubmit() = withState { state -> + viewModelScope.launch { + setState { + copy(addServerAsync = Loading()) + } + try { + session.getPublicRooms( + server = state.enteredServer, + publicRoomsParams = PublicRoomsParams(limit = 1) + ) + // Success, let add the server to our local repository, and update the state + val newSet = uiStateRepository.getCustomRoomDirectoryHomeservers(session.sessionId) + state.enteredServer + uiStateRepository.setCustomRoomDirectoryHomeservers(session.sessionId, newSet) + setState { + copy( + inEditMode = false, + enteredServer = "", + addServerAsync = Uninitialized, + customHomeservers = newSet + ) + } + } catch (failure: Throwable) { + setState { + copy(addServerAsync = Fail(failure)) + } + } + } + } + + private fun handleRemoveServer(action: RoomDirectoryPickerAction.RemoveServer) { + val newSet = uiStateRepository.getCustomRoomDirectoryHomeservers(session.sessionId) - action.roomDirectoryServer.serverName + uiStateRepository.setCustomRoomDirectoryHomeservers(session.sessionId, newSet) + setState { + copy( + customHomeservers = newSet + ) } } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewState.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewState.kt index 61cf50e8dd..c78d4ac55c 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewState.kt @@ -22,5 +22,9 @@ import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol data class RoomDirectoryPickerViewState( - val asyncThirdPartyRequest: Async> = Uninitialized + val asyncThirdPartyRequest: Async> = Uninitialized, + val customHomeservers: Set = emptySet(), + val inEditMode: Boolean = false, + val enteredServer: String = "", + val addServerAsync: Async = Uninitialized ) : MvRxState diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryServerItem.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryServerItem.kt index 83acfa581c..6efb41d5b1 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryServerItem.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryServerItem.kt @@ -16,12 +16,16 @@ package im.vector.app.features.roomdirectory.picker +import android.view.View import android.widget.TextView +import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R +import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.onClick import im.vector.app.core.extensions.setTextOrHide @EpoxyModelClass(layout = R.layout.item_room_directory_server) @@ -33,14 +37,23 @@ abstract class RoomDirectoryServerItem : VectorEpoxyModel(R.id.itemRoomDirectoryServerName) val descriptionView by bind(R.id.itemRoomDirectoryServerDescription) + val deleteView by bind(R.id.itemRoomDirectoryServerRemove) } } diff --git a/vector/src/main/java/im/vector/app/features/ui/SharedPreferencesUiStateRepository.kt b/vector/src/main/java/im/vector/app/features/ui/SharedPreferencesUiStateRepository.kt index 1ec3a8ab46..e46c3516ca 100644 --- a/vector/src/main/java/im/vector/app/features/ui/SharedPreferencesUiStateRepository.kt +++ b/vector/src/main/java/im/vector/app/features/ui/SharedPreferencesUiStateRepository.kt @@ -39,7 +39,7 @@ class SharedPreferencesUiStateRepository @Inject constructor( override fun getDisplayMode(): RoomListDisplayMode { return when (sharedPreferences.getInt(KEY_DISPLAY_MODE, VALUE_DISPLAY_MODE_CATCHUP)) { VALUE_DISPLAY_MODE_PEOPLE -> RoomListDisplayMode.PEOPLE - VALUE_DISPLAY_MODE_ROOMS -> RoomListDisplayMode.ROOMS + VALUE_DISPLAY_MODE_ROOMS -> RoomListDisplayMode.ROOMS else -> if (vectorPreferences.labAddNotificationTab()) { RoomListDisplayMode.NOTIFICATIONS } else { @@ -89,6 +89,18 @@ class SharedPreferencesUiStateRepository @Inject constructor( return sharedPreferences.getBoolean("$KEY_SELECTED_METHOD@$sessionId", true) } + override fun setCustomRoomDirectoryHomeservers(sessionId: String, servers: Set) { + sharedPreferences.edit { + putStringSet("$KEY_CUSTOM_DIRECTORY_HOMESERVER@$sessionId", servers) + } + } + + override fun getCustomRoomDirectoryHomeservers(sessionId: String): Set { + return sharedPreferences.getStringSet("$KEY_CUSTOM_DIRECTORY_HOMESERVER@$sessionId", null) + .orEmpty() + .toSet() + } + companion object { private const val KEY_DISPLAY_MODE = "UI_STATE_DISPLAY_MODE" private const val VALUE_DISPLAY_MODE_CATCHUP = 0 @@ -98,5 +110,7 @@ class SharedPreferencesUiStateRepository @Inject constructor( private const val KEY_SELECTED_SPACE = "UI_STATE_SELECTED_SPACE" private const val KEY_SELECTED_GROUP = "UI_STATE_SELECTED_GROUP" private const val KEY_SELECTED_METHOD = "UI_STATE_SELECTED_METHOD" + + private const val KEY_CUSTOM_DIRECTORY_HOMESERVER = "KEY_CUSTOM_DIRECTORY_HOMESERVER" } } diff --git a/vector/src/main/java/im/vector/app/features/ui/UiStateRepository.kt b/vector/src/main/java/im/vector/app/features/ui/UiStateRepository.kt index 935da83f5d..3c48f8972d 100644 --- a/vector/src/main/java/im/vector/app/features/ui/UiStateRepository.kt +++ b/vector/src/main/java/im/vector/app/features/ui/UiStateRepository.kt @@ -32,6 +32,7 @@ interface UiStateRepository { fun storeDisplayMode(displayMode: RoomListDisplayMode) + // TODO Handle SharedPreference per session in a better way, also to cleanup when login out fun storeSelectedSpace(spaceId: String?, sessionId: String) fun storeSelectedGroup(groupId: String?, sessionId: String) @@ -40,4 +41,7 @@ interface UiStateRepository { fun getSelectedSpace(sessionId: String): String? fun getSelectedGroup(sessionId: String): String? fun isGroupingMethodSpace(sessionId: String): Boolean + + fun setCustomRoomDirectoryHomeservers(sessionId: String, servers: Set) + fun getCustomRoomDirectoryHomeservers(sessionId: String): Set } diff --git a/vector/src/main/res/layout/item_generic_space.xml b/vector/src/main/res/layout/item_generic_space.xml new file mode 100644 index 0000000000..aef6664f94 --- /dev/null +++ b/vector/src/main/res/layout/item_generic_space.xml @@ -0,0 +1,5 @@ + + diff --git a/vector/src/main/res/layout/item_room_directory_server.xml b/vector/src/main/res/layout/item_room_directory_server.xml index 5459652e2c..5705e1c623 100644 --- a/vector/src/main/res/layout/item_room_directory_server.xml +++ b/vector/src/main/res/layout/item_room_directory_server.xml @@ -12,7 +12,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="@dimen/layout_horizontal_margin" - android:layout_marginEnd="@dimen/layout_horizontal_margin" + android:layout_marginEnd="8dp" android:ellipsize="end" android:lines="1" android:maxLines="2" @@ -20,10 +20,11 @@ android:textSize="16sp" android:textStyle="bold" app:layout_constraintBottom_toTopOf="@+id/itemRoomDirectoryServerDescription" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toStartOf="@id/itemRoomDirectoryServerRemove" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_chainStyle="packed" + app:layout_goneMarginEnd="@dimen/layout_horizontal_margin" tools:text="@tools:sample/lorem/random" /> + + \ No newline at end of file diff --git a/vector/src/main/res/menu/menu_directory_server_picker.xml b/vector/src/main/res/menu/menu_directory_server_picker.xml deleted file mode 100644 index c544c80f8c..0000000000 --- a/vector/src/main/res/menu/menu_directory_server_picker.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - \ 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 442b79b5b0..7b8a933d30 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1584,10 +1584,13 @@ Select a room directory The server may be unavailable or overloaded Type a homeserver to list public rooms from - Homeserver URL + Server name All rooms on %s server All native %s rooms Your server + Add a new server + Enter the name of a new server you want to explore. + "Can't find this server or its room list" Type here… From d3949729e15f1e816b8bdc84be4e8cc4f06fca3a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 27 May 2021 13:02:10 +0200 Subject: [PATCH 18/34] Quick fix for edittext issue --- .../main/java/im/vector/app/features/form/FormEditTextItem.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt b/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt index 459e4ea838..346c80c62d 100644 --- a/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt +++ b/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt @@ -61,7 +61,8 @@ abstract class FormEditTextItem : VectorEpoxyModel() { @EpoxyAttribute var endIconMode: Int? = null - @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + // FIXME restore EpoxyAttribute.Option.DoNotHash and fix that properly + @EpoxyAttribute var onTextChange: ((String) -> Unit)? = null private val onTextChangeListener = object : SimpleTextWatcher() { From 8d0d7635c65a7ccacefb493750aa27bb8eb343b3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 27 May 2021 11:49:47 +0200 Subject: [PATCH 19/34] Single line set to true by default --- .../java/im/vector/app/features/form/FormEditTextItem.kt | 5 ++--- .../roomdirectory/createroom/CreateRoomController.kt | 1 + .../features/roomprofile/settings/RoomSettingsController.kt | 1 + .../spaces/create/SpaceDefaultRoomEpoxyController.kt | 3 --- .../app/features/spaces/create/SpaceDetailEpoxyController.kt | 1 - 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt b/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt index 346c80c62d..f3224b5be3 100644 --- a/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt +++ b/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt @@ -51,9 +51,8 @@ abstract class FormEditTextItem : VectorEpoxyModel() { @EpoxyAttribute var inputType: Int? = null - // TODO Should be true by default @EpoxyAttribute - var singleLine: Boolean? = null + var singleLine: Boolean = true @EpoxyAttribute var imeOptions: Int? = null @@ -82,7 +81,7 @@ abstract class FormEditTextItem : VectorEpoxyModel() { holder.textInputEditText.isEnabled = enabled inputType?.let { holder.textInputEditText.inputType = it } - holder.textInputEditText.isSingleLine = singleLine ?: false + holder.textInputEditText.isSingleLine = singleLine holder.textInputEditText.imeOptions = imeOptions ?: EditorInfo.IME_ACTION_NONE holder.textInputEditText.addTextChangedListener(onTextChangeListener) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt index efb54650b8..aaf82c5791 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt @@ -75,6 +75,7 @@ class CreateRoomController @Inject constructor( id("topic") enabled(enableFormElement) value(viewState.roomTopic) + singleLine(false) hint(host.stringProvider.getString(R.string.create_room_topic_hint)) onTextChange { text -> diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt index 24836bc504..a07589d501 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt @@ -102,6 +102,7 @@ class RoomSettingsController @Inject constructor( id("topic") enabled(data.actionPermissions.canChangeTopic) value(data.newTopic ?: roomSummary.topic) + singleLine(false) hint(host.stringProvider.getString(R.string.room_settings_topic_hint)) onTextChange { text -> diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt index a8b85c9887..3f712a4fda 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt @@ -69,7 +69,6 @@ class SpaceDefaultRoomEpoxyController @Inject constructor( id("roomName1") enabled(true) value(firstRoomName) - singleLine(true) hint(host.stringProvider.getString(R.string.create_room_name_section)) endIconMode(TextInputLayout.END_ICON_CLEAR_TEXT) showBottomSeparator(false) @@ -83,7 +82,6 @@ class SpaceDefaultRoomEpoxyController @Inject constructor( id("roomName2") enabled(true) value(secondRoomName) - singleLine(true) hint(host.stringProvider.getString(R.string.create_room_name_section)) endIconMode(TextInputLayout.END_ICON_CLEAR_TEXT) showBottomSeparator(false) @@ -97,7 +95,6 @@ class SpaceDefaultRoomEpoxyController @Inject constructor( id("roomName3") enabled(true) value(thirdRoomName) - singleLine(true) hint(host.stringProvider.getString(R.string.create_room_name_section)) endIconMode(TextInputLayout.END_ICON_CLEAR_TEXT) showBottomSeparator(false) diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDetailEpoxyController.kt b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDetailEpoxyController.kt index 6ab35d3bf6..662b272d40 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDetailEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDetailEpoxyController.kt @@ -64,7 +64,6 @@ class SpaceDetailEpoxyController @Inject constructor( enabled(true) value(data?.name) hint(host.stringProvider.getString(R.string.create_room_name_hint)) - singleLine(true) showBottomSeparator(false) errorMessage(data?.nameInlineError) // onBind { _, view, _ -> From f62ab84e5985a9fe4c0b5947ec3e0a9cec7561b9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 27 May 2021 14:12:48 +0200 Subject: [PATCH 20/34] Add custom server - add IME action --- .../vector/app/features/form/FormEditTextItem.kt | 5 +++++ .../picker/RoomDirectoryPickerController.kt | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt b/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt index f3224b5be3..8e9b72d2d8 100644 --- a/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt +++ b/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt @@ -19,6 +19,7 @@ package im.vector.app.features.form import android.text.Editable import android.view.View import android.view.inputmethod.EditorInfo +import android.widget.TextView import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass @@ -64,6 +65,9 @@ abstract class FormEditTextItem : VectorEpoxyModel() { @EpoxyAttribute var onTextChange: ((String) -> Unit)? = null + @EpoxyAttribute + var editorActionListener: TextView.OnEditorActionListener? = null + private val onTextChangeListener = object : SimpleTextWatcher() { override fun afterTextChanged(s: Editable) { onTextChange?.invoke(s.toString()) @@ -85,6 +89,7 @@ abstract class FormEditTextItem : VectorEpoxyModel() { holder.textInputEditText.imeOptions = imeOptions ?: EditorInfo.IME_ACTION_NONE holder.textInputEditText.addTextChangedListener(onTextChangeListener) + holder.textInputEditText.setOnEditorActionListener(editorActionListener) holder.bottomSeparator.isVisible = showBottomSeparator } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt index 0a28ad5d52..d6a78d435e 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt @@ -17,7 +17,10 @@ package im.vector.app.features.roomdirectory.picker import android.text.InputType +import android.view.KeyEvent import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.TextView import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Incomplete @@ -112,6 +115,18 @@ class RoomDirectoryPickerController @Inject constructor( id("edit") showBottomSeparator(false) value(data.enteredServer) + imeOptions(EditorInfo.IME_ACTION_DONE) + editorActionListener(object : TextView.OnEditorActionListener { + override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean { + if (actionId == EditorInfo.IME_ACTION_DONE) { + if (data.enteredServer.isNotEmpty()) { + host.callback?.onSubmitServer() + } + return true + } + return false + } + }) hint(host.stringProvider.getString(R.string.directory_server_placeholder)) inputType(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI) onTextChange { text -> From 96720af52f185b51557f95b83a197a286a9edd15 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 27 May 2021 14:35:29 +0200 Subject: [PATCH 21/34] Let the View model compute the state --- .../picker/RoomDirectoryPickerController.kt | 9 ++------- .../picker/RoomDirectoryPickerViewModel.kt | 17 ++++++++++++++++- .../picker/RoomDirectoryPickerViewState.kt | 5 ++++- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt index d6a78d435e..8f45422c29 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt @@ -51,8 +51,7 @@ class RoomDirectoryPickerController @Inject constructor( private val stringProvider: StringProvider, private val colorProvider: ColorProvider, private val dimensionConverter: DimensionConverter, - private val errorFormatter: ErrorFormatter, - private val roomDirectoryListCreator: RoomDirectoryListCreator + private val errorFormatter: ErrorFormatter ) : TypedEpoxyController() { var currentRoomDirectoryData: RoomDirectoryData? = null @@ -65,11 +64,7 @@ class RoomDirectoryPickerController @Inject constructor( when (val asyncThirdPartyProtocol = data.asyncThirdPartyRequest) { is Success -> { - val directories = roomDirectoryListCreator.computeDirectories( - asyncThirdPartyProtocol(), - data.customHomeservers - ) - directories.join( + data.directories.join( each = { _, roomDirectoryServer -> buildDirectory(roomDirectoryServer) }, between = { idx, _ -> buildDivider(idx) } ) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt index 049293cafd..f790aa7d67 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt @@ -38,7 +38,8 @@ import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsPa class RoomDirectoryPickerViewModel @AssistedInject constructor( @Assisted initialState: RoomDirectoryPickerViewState, private val session: Session, - private val uiStateRepository: UiStateRepository + private val uiStateRepository: UiStateRepository, + private val roomDirectoryListCreator: RoomDirectoryListCreator ) : VectorViewModel(initialState) { @AssistedFactory @@ -56,10 +57,24 @@ class RoomDirectoryPickerViewModel @AssistedInject constructor( } init { + observeAndCompute() load() loadCustomRoomDirectoryHomeservers() } + private fun observeAndCompute() { + selectSubscribe( + RoomDirectoryPickerViewState::asyncThirdPartyRequest, + RoomDirectoryPickerViewState::customHomeservers + ) { async, custom -> + async()?.let { + setState { + copy(directories = roomDirectoryListCreator.computeDirectories(it, custom)) + } + } + } + } + private fun load() { viewModelScope.launch { setState { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewState.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewState.kt index c78d4ac55c..5cdee862ab 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewState.kt @@ -19,6 +19,7 @@ package im.vector.app.features.roomdirectory.picker import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized +import im.vector.app.features.roomdirectory.RoomDirectoryServer import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol data class RoomDirectoryPickerViewState( @@ -26,5 +27,7 @@ data class RoomDirectoryPickerViewState( val customHomeservers: Set = emptySet(), val inEditMode: Boolean = false, val enteredServer: String = "", - val addServerAsync: Async = Uninitialized + val addServerAsync: Async = Uninitialized, + // computed + val directories: List = emptyList() ) : MvRxState From e925d29037e82cc07977e5b265cbf4dedd39dd93 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 27 May 2021 15:37:26 +0200 Subject: [PATCH 22/34] Avoid adding twice the same server --- .../im/vector/app/core/extensions/Activity.kt | 4 ++++ .../roomdirectory/RoomDirectoryActivity.kt | 3 ++- .../picker/RoomDirectoryPickerViewModel.kt | 19 +++++++++++++++++-- vector/src/main/res/values/strings.xml | 1 + 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/extensions/Activity.kt b/vector/src/main/java/im/vector/app/core/extensions/Activity.kt index 55ec8b605e..de469b9e3a 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/Activity.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/Activity.kt @@ -94,6 +94,10 @@ fun AppCompatActivity.addFragmentToBackstack( } } +fun AppCompatActivity.popBackstack() { + supportFragmentManager.popBackStack() +} + fun AppCompatActivity.resetBackstack() { repeat(supportFragmentManager.backStackEntryCount) { supportFragmentManager.popBackStack() diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt index d8edbcf503..9a63e81a2f 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt @@ -25,6 +25,7 @@ import im.vector.app.R import im.vector.app.core.di.ScreenComponent import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.addFragmentToBackstack +import im.vector.app.core.extensions.popBackstack import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.features.roomdirectory.createroom.CreateRoomFragment @@ -58,7 +59,7 @@ class RoomDirectoryActivity : VectorBaseActivity() { .observe() .subscribe { sharedAction -> when (sharedAction) { - is RoomDirectorySharedAction.Back -> onBackPressed() + is RoomDirectorySharedAction.Back -> popBackstack() is RoomDirectorySharedAction.CreateRoom -> { // Transmit the filter to the CreateRoomFragment withState(roomDirectoryViewModel) { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt index f790aa7d67..c9c7365ddc 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt @@ -27,9 +27,11 @@ import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import im.vector.app.R import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.resources.StringProvider import im.vector.app.features.ui.UiStateRepository import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session @@ -39,6 +41,7 @@ class RoomDirectoryPickerViewModel @AssistedInject constructor( @Assisted initialState: RoomDirectoryPickerViewState, private val session: Session, private val uiStateRepository: UiStateRepository, + private val stringProvider: StringProvider, private val roomDirectoryListCreator: RoomDirectoryListCreator ) : VectorViewModel(initialState) { @@ -141,17 +144,29 @@ class RoomDirectoryPickerViewModel @AssistedInject constructor( } private fun handleSubmit() = withState { state -> + // First avoid duplicate + val enteredServer = state.enteredServer + + val existingServerList = state.directories.map { it.serverName } + + if (enteredServer in existingServerList) { + setState { + copy(addServerAsync = Fail(Throwable(stringProvider.getString(R.string.directory_add_a_new_server_error_already_added)))) + } + return@withState + } + viewModelScope.launch { setState { copy(addServerAsync = Loading()) } try { session.getPublicRooms( - server = state.enteredServer, + server = enteredServer, publicRoomsParams = PublicRoomsParams(limit = 1) ) // Success, let add the server to our local repository, and update the state - val newSet = uiStateRepository.getCustomRoomDirectoryHomeservers(session.sessionId) + state.enteredServer + val newSet = uiStateRepository.getCustomRoomDirectoryHomeservers(session.sessionId) + enteredServer uiStateRepository.setCustomRoomDirectoryHomeservers(session.sessionId, newSet) setState { copy( diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 7b8a933d30..9250301cfd 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1591,6 +1591,7 @@ Add a new server Enter the name of a new server you want to explore. "Can't find this server or its room list" + This server is already present in the list Type here… From bcc360692ee236a7e5eb447461f2f2875a0f6db7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 27 May 2021 16:00:32 +0200 Subject: [PATCH 23/34] Call transfer: makes call transfer working properly --- .../android/sdk/api/session/call/MxCall.kt | 12 +++---- .../session/call/CallSignalingHandler.kt | 11 +++--- .../internal/session/call/MxCallFactory.kt | 19 +++++----- .../internal/session/call/model/MxCallImpl.kt | 12 +++++-- .../call/transfer/CallTransferViewModel.kt | 4 +-- .../app/features/call/webrtc/WebRtcCall.kt | 16 ++++----- .../features/call/webrtc/WebRtcCallManager.kt | 35 +++++++++---------- 7 files changed, 55 insertions(+), 54 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt index a8a3cf58aa..08278d8e4f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt @@ -26,8 +26,12 @@ interface MxCallDetail { val callId: String val isOutgoing: Boolean val roomId: String - val opponentUserId: String val isVideoCall: Boolean + val ourPartyId: String + val opponentPartyId: Optional? + val opponentVersion: Int + val opponentUserId: String + val capabilities: CallCapabilities? } /** @@ -39,12 +43,6 @@ interface MxCall : MxCallDetail { const val VOIP_PROTO_VERSION = 1 } - val ourPartyId: String - var opponentPartyId: Optional? - var opponentVersion: Int - - var capabilities: CallCapabilities? - var state: CallState /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt index 6bf11ab78f..dbf15d2624 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt @@ -24,7 +24,6 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent -import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent @@ -35,7 +34,6 @@ import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.SessionScope import timber.log.Timber -import java.math.BigDecimal import javax.inject.Inject @SessionScope @@ -192,6 +190,9 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa // Ignore remote echo return } + if (event.roomId == null || event.senderId == null) { + return + } if (event.senderId == userId) { // discard current call, it's answered by another of my session activeCallHandler.removeCall(call.callId) @@ -201,11 +202,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa Timber.v("Ignoring answer from party ID ${content.partyId} we already have an answer from ${call.opponentPartyId}") return } - call.apply { - opponentPartyId = Optional.from(content.partyId) - opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION - capabilities = content.capabilities ?: CallCapabilities() - } + mxCallFactory.updateOutgoingCallWithOpponentData(call, event.senderId, content, content.capabilities) callListenersDispatcher.onCallAnswerReceived(content) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt index b6aed98504..68ac4369b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt @@ -21,15 +21,13 @@ import org.matrix.android.sdk.api.session.call.CallIdGenerator import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent -import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent import org.matrix.android.sdk.internal.di.DeviceId import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.call.model.MxCallImpl import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor -import java.math.BigDecimal -import java.util.UUID import javax.inject.Inject internal class MxCallFactory @Inject constructor( @@ -49,16 +47,13 @@ internal class MxCallFactory @Inject constructor( roomId = roomId, userId = userId, ourPartyId = deviceId ?: "", - opponentUserId = opponentUserId, isVideoCall = content.isVideo(), localEchoEventFactory = localEchoEventFactory, eventSenderProcessor = eventSenderProcessor, matrixConfiguration = matrixConfiguration, getProfileInfoTask = getProfileInfoTask ).apply { - opponentPartyId = Optional.from(content.partyId) - opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION - capabilities = content.capabilities ?: CallCapabilities() + updateOpponentData(opponentUserId, content, content.capabilities) } } @@ -69,12 +64,18 @@ internal class MxCallFactory @Inject constructor( roomId = roomId, userId = userId, ourPartyId = deviceId ?: "", - opponentUserId = opponentUserId, isVideoCall = isVideoCall, localEchoEventFactory = localEchoEventFactory, eventSenderProcessor = eventSenderProcessor, matrixConfiguration = matrixConfiguration, getProfileInfoTask = getProfileInfoTask - ) + ).apply { + // Setup with this userId, might be updated when processing the Answer event + this.opponentUserId = opponentUserId + } + } + + fun updateOutgoingCallWithOpponentData(call: MxCall, userId: String, content: CallSignalingContent, callCapabilities: CallCapabilities?) { + (call as? MxCallImpl)?.updateOpponentData(userId, content, callCapabilities) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt index 6db2989a2e..f101685a4b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt @@ -37,6 +37,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent import org.matrix.android.sdk.api.session.room.model.call.CallReplacesContent import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent +import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent import org.matrix.android.sdk.api.session.room.model.call.SdpType import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService @@ -44,14 +45,13 @@ import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor import timber.log.Timber -import java.util.UUID +import java.math.BigDecimal internal class MxCallImpl( override val callId: String, override val isOutgoing: Boolean, override val roomId: String, private val userId: String, - override val opponentUserId: String, override val isVideoCall: Boolean, override val ourPartyId: String, private val localEchoEventFactory: LocalEchoEventFactory, @@ -62,8 +62,16 @@ internal class MxCallImpl( override var opponentPartyId: Optional? = null override var opponentVersion: Int = MxCall.VOIP_PROTO_VERSION + override lateinit var opponentUserId: String override var capabilities: CallCapabilities? = null + fun updateOpponentData(userId: String, content: CallSignalingContent, callCapabilities: CallCapabilities?) { + opponentPartyId = Optional.from(content.partyId) + opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION + opponentUserId = userId + capabilities = callCapabilities ?: CallCapabilities() + } + override var state: CallState = CallState.Idle set(value) { field = value diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt index 8a6302a5a6..0f37ccaa29 100644 --- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt @@ -96,8 +96,8 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: ) } else { call?.transferToUser(action.selectedUserId, null) - _viewEvents.post(CallTransferViewEvents.Dismiss) } + _viewEvents.post(CallTransferViewEvents.Dismiss) } catch (failure: Throwable) { _viewEvents.post(CallTransferViewEvents.FailToTransfer) } @@ -118,8 +118,8 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: ) } else { call?.transferToUser(result.userId, result.roomId) - _viewEvents.post(CallTransferViewEvents.Dismiss) } + _viewEvents.post(CallTransferViewEvents.Dismiss) } catch (failure: Throwable) { _viewEvents.post(CallTransferViewEvents.FailToTransfer) } diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt index eb382fe907..7abb077ee0 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt @@ -290,17 +290,17 @@ class WebRtcCall(val mxCall: MxCall, } } - suspend fun transferToUser(targetUserId: String, targetRoomId: String?) = withContext(dispatcher){ + suspend fun transferToUser(targetUserId: String, targetRoomId: String?) { mxCall.transfer( targetUserId = targetUserId, targetRoomId = targetRoomId, createCallId = CallIdGenerator.generate(), awaitCallId = null ) - endCall(true, CallHangupContent.Reason.REPLACED) + endCall(sendEndSignaling = false) } - suspend fun transferToCall(transferTargetCall: WebRtcCall)= withContext(dispatcher) { + suspend fun transferToCall(transferTargetCall: WebRtcCall) { val newCallId = CallIdGenerator.generate() transferTargetCall.mxCall.transfer( targetUserId = this@WebRtcCall.mxCall.opponentUserId, @@ -314,8 +314,8 @@ class WebRtcCall(val mxCall: MxCall, createCallId = newCallId, awaitCallId = null ) - this@WebRtcCall.endCall(true, CallHangupContent.Reason.REPLACED) - transferTargetCall.endCall(true, CallHangupContent.Reason.REPLACED) + this@WebRtcCall.endCall(sendEndSignaling = false) + transferTargetCall.endCall(sendEndSignaling = false) } fun acceptIncomingCall() { @@ -758,7 +758,7 @@ class WebRtcCall(val mxCall: MxCall, } } - fun endCall(originatedByMe: Boolean = true, reason: CallHangupContent.Reason? = null) { + fun endCall(sendEndSignaling: Boolean = true, reason: CallHangupContent.Reason? = null) { if (mxCall.state == CallState.Terminated) { return } @@ -773,9 +773,9 @@ class WebRtcCall(val mxCall: MxCall, mxCall.state = CallState.Terminated sessionScope?.launch(dispatcher) { release() + onCallEnded(callId) } - onCallEnded(callId) - if (originatedByMe) { + if (sendEndSignaling) { if (wasRinging) { mxCall.reject() } else { diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt index dabe09fc56..3c18d97937 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt @@ -146,6 +146,7 @@ class WebRtcCallManager @Inject constructor( private val advertisedCalls = HashSet() private val callsByCallId = ConcurrentHashMap() private val callsByRoomId = ConcurrentHashMap>() + // Calls started as an attended transfer, ie. with the intention of transferring another // call with a different party to this one. // callId (target) -> call (transferee) @@ -242,30 +243,26 @@ class WebRtcCallManager @Inject constructor( val otherCall = getCalls().lastOrNull() currentCall.setAndNotify(otherCall) } - // This must be done in this thread - executor.execute { - // There is no active calls - if (getCurrentCall() == null) { - Timber.v("## VOIP Dispose peerConnectionFactory as there is no need to keep one") - peerConnectionFactory?.dispose() - peerConnectionFactory = null - audioManager.setMode(CallAudioManager.Mode.DEFAULT) - // did we start background sync? so we should stop it - if (isInBackground) { - if (FcmHelper.isPushSupported()) { - currentSession?.stopAnyBackgroundSync() - } else { - // for fdroid we should not stop, it should continue syncing - // maybe we should restore default timeout/delay though? - } + // There is no active calls + if (getCurrentCall() == null) { + Timber.v("## VOIP Dispose peerConnectionFactory as there is no need to keep one") + peerConnectionFactory?.dispose() + peerConnectionFactory = null + audioManager.setMode(CallAudioManager.Mode.DEFAULT) + // did we start background sync? so we should stop it + if (isInBackground) { + if (FcmHelper.isPushSupported()) { + currentSession?.stopAnyBackgroundSync() + } else { + // for fdroid we should not stop, it should continue syncing + // maybe we should restore default timeout/delay though? } } - Timber.v("## VOIP WebRtcPeerConnectionManager close() executor done") } } suspend fun startOutgoingCall(nativeRoomId: String, otherUserId: String, isVideoCall: Boolean, transferee: WebRtcCall? = null) { - val signalingRoomId = callUserMapper?.getOrCreateVirtualRoomForRoom(nativeRoomId, otherUserId) ?: nativeRoomId + val signalingRoomId = callUserMapper?.getOrCreateVirtualRoomForRoom(nativeRoomId, otherUserId) ?: nativeRoomId Timber.v("## VOIP startOutgoingCall in room $signalingRoomId to $otherUserId isVideo $isVideoCall") if (getCallsByRoomId(nativeRoomId).isNotEmpty()) { Timber.w("## VOIP you already have a call in this room") @@ -283,7 +280,7 @@ class WebRtcCallManager @Inject constructor( val mxCall = currentSession?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return val webRtcCall = createWebRtcCall(mxCall, nativeRoomId) currentCall.setAndNotify(webRtcCall) - if(transferee != null){ + if (transferee != null) { transferees[webRtcCall.callId] = transferee } CallService.onOutgoingCallRinging( From 8e8bc0055d58aff8880c204c2a0ea2219c3665d5 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 27 May 2021 16:32:14 +0200 Subject: [PATCH 24/34] Call transfer: clean & add changelog --- .../sdk/internal/session/call/CallSignalingHandler.kt | 1 - newsfragment/3420.feature | 1 + .../im/vector/app/features/call/VectorCallActivity.kt | 9 ++++----- .../im/vector/app/features/call/VectorCallViewActions.kt | 1 - .../im/vector/app/features/call/VectorCallViewModel.kt | 2 -- 5 files changed, 5 insertions(+), 9 deletions(-) create mode 100644 newsfragment/3420.feature diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt index dbf15d2624..61ea660b60 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt @@ -30,7 +30,6 @@ import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent -import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.SessionScope import timber.log.Timber diff --git a/newsfragment/3420.feature b/newsfragment/3420.feature new file mode 100644 index 0000000000..3f3df52f62 --- /dev/null +++ b/newsfragment/3420.feature @@ -0,0 +1 @@ +VoIP: support attended transfer \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt index 0233fdf3e8..d8f183a94a 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt @@ -198,14 +198,13 @@ class VectorCallActivity : VectorBaseActivity(), CallContro } is CallState.Connected -> { if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) { - if(state.transfereeName.hasValue()){ + if (state.transfereeName.hasValue()) { views.callActionText.text = getString(R.string.call_transfer_transfer_to_title, state.transfereeName.get()) views.callActionText.isVisible = true views.callActionText.setOnClickListener { callViewModel.handle(VectorCallViewActions.TransferCall) } views.callStatusText.text = state.formattedDuration configureCallInfo(state) - } - else if (state.isLocalOnHold || state.isRemoteOnHold) { + } else if (state.isLocalOnHold || state.isRemoteOnHold) { views.smallIsHeldIcon.isVisible = true views.callVideoGroup.isInvisible = true views.callInfoGroup.isVisible = true @@ -254,9 +253,9 @@ class VectorCallActivity : VectorBaseActivity(), CallContro state.callInfo.otherUserItem?.let { val colorFilter = ContextCompat.getColor(this, R.color.bg_call_screen) avatarRenderer.renderBlur(it, views.bgCallView, sampling = 20, rounded = false, colorFilter = colorFilter) - if(state.transfereeName.hasValue()) { + if (state.transfereeName.hasValue()) { views.participantNameText.text = getString(R.string.call_transfer_consulting_with, it.getBestName()) - }else { + } else { views.participantNameText.text = it.getBestName() } if (blurAvatar) { diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt index 804d272d7f..a332153aaa 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt @@ -18,7 +18,6 @@ package im.vector.app.features.call import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.features.call.audio.CallAudioManager -import im.vector.app.features.call.webrtc.WebRtcCall sealed class VectorCallViewActions : VectorViewModelAction { object EndCall : VectorCallViewActions() diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt index ddb9629d8f..aed38f9e98 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt @@ -39,9 +39,7 @@ import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.call.MxPeerConnectionState import org.matrix.android.sdk.api.session.room.model.call.supportCallTransfer -import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.api.util.toMatrixItem class VectorCallViewModel @AssistedInject constructor( @Assisted initialState: VectorCallViewState, From f559ae1b3513cc678fa7e653f6f33ad346914d86 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 27 May 2021 16:55:40 +0200 Subject: [PATCH 25/34] Rename to avoid collision with Matrix Spaces --- .../list/{GenericSpaceItem.kt => VerticalMarginItem.kt} | 6 +++--- .../roomdirectory/picker/RoomDirectoryPickerController.kt | 8 ++++---- .../{item_generic_space.xml => item_vertical_margin.xml} | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) rename vector/src/main/java/im/vector/app/core/ui/list/{GenericSpaceItem.kt => VerticalMarginItem.kt} (85%) rename vector/src/main/res/layout/{item_generic_space.xml => item_vertical_margin.xml} (80%) diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericSpaceItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/VerticalMarginItem.kt similarity index 85% rename from vector/src/main/java/im/vector/app/core/ui/list/GenericSpaceItem.kt rename to vector/src/main/java/im/vector/app/core/ui/list/VerticalMarginItem.kt index 137fac9abe..ec99c7c215 100644 --- a/vector/src/main/java/im/vector/app/core/ui/list/GenericSpaceItem.kt +++ b/vector/src/main/java/im/vector/app/core/ui/list/VerticalMarginItem.kt @@ -26,8 +26,8 @@ import im.vector.app.core.epoxy.VectorEpoxyModel /** * A generic item with empty space. */ -@EpoxyModelClass(layout = R.layout.item_generic_space) -abstract class GenericSpaceItem : VectorEpoxyModel() { +@EpoxyModelClass(layout = R.layout.item_vertical_margin) +abstract class VerticalMarginItem : VectorEpoxyModel() { @EpoxyAttribute var heightInPx: Int = 0 @@ -40,6 +40,6 @@ abstract class GenericSpaceItem : VectorEpoxyModel() { } class Holder : VectorEpoxyHolder() { - val space by bind(R.id.item_generic_space) + val space by bind(R.id.item_vertical_margin_space) } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt index 8f45422c29..2b60a5d294 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt @@ -36,7 +36,7 @@ import im.vector.app.core.extensions.join import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.list.genericButtonItem -import im.vector.app.core.ui.list.genericSpaceItem +import im.vector.app.core.ui.list.verticalMarginItem import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.discovery.settingsContinueCancelItem import im.vector.app.features.discovery.settingsInformationItem @@ -69,7 +69,7 @@ class RoomDirectoryPickerController @Inject constructor( between = { idx, _ -> buildDivider(idx) } ) buildForm(data) - genericSpaceItem { + verticalMarginItem { id("space_bottom") heightInPx(host.dimensionConverter.dpToPx(16)) } @@ -93,7 +93,7 @@ class RoomDirectoryPickerController @Inject constructor( buildDivider(1000) val host = this if (data.inEditMode) { - genericSpaceItem { + verticalMarginItem { id("form_space") heightInPx(host.dimensionConverter.dpToPx(16)) } @@ -102,7 +102,7 @@ class RoomDirectoryPickerController @Inject constructor( message(host.stringProvider.getString(R.string.directory_add_a_new_server_prompt)) colorProvider(host.colorProvider) } - genericSpaceItem { + verticalMarginItem { id("form_space_2") heightInPx(host.dimensionConverter.dpToPx(8)) } diff --git a/vector/src/main/res/layout/item_generic_space.xml b/vector/src/main/res/layout/item_vertical_margin.xml similarity index 80% rename from vector/src/main/res/layout/item_generic_space.xml rename to vector/src/main/res/layout/item_vertical_margin.xml index aef6664f94..fac46e47ea 100644 --- a/vector/src/main/res/layout/item_generic_space.xml +++ b/vector/src/main/res/layout/item_vertical_margin.xml @@ -1,5 +1,5 @@ From cc5a400cfdba41097e6a98c353f04c7c1a980b57 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 27 May 2021 17:02:41 +0200 Subject: [PATCH 26/34] ktlint --- .../roomdirectory/picker/RoomDirectoryPickerController.kt | 2 +- .../roomdirectory/picker/RoomDirectoryPickerFragment.kt | 1 - .../roomdirectory/picker/RoomDirectoryPickerViewModel.kt | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt index 2b60a5d294..9a397c586d 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt @@ -175,7 +175,7 @@ class RoomDirectoryPickerController @Inject constructor( private fun buildDivider(idx: Int) { val host = this dividerItem { - id("divider_${idx}") + id("divider_$idx") color(host.dividerColor) } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt index e3c39a2ccb..a32a3a897f 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt @@ -18,7 +18,6 @@ package im.vector.app.features.roomdirectory.picker import android.os.Bundle import android.view.LayoutInflater -import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt index c9c7365ddc..2558715834 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt @@ -138,7 +138,7 @@ class RoomDirectoryPickerViewModel @AssistedInject constructor( private fun handleSetServerUrl(action: RoomDirectoryPickerAction.SetServerUrl) { setState { copy( - enteredServer = action.url, + enteredServer = action.url ) } } From 302e29831e74487d8d83d9982e7b5acd0d4d9f55 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 27 May 2021 18:42:07 +0200 Subject: [PATCH 27/34] Compress thumbnail: change Jpeg quality from 100 to 80 (#3396) --- .../android/sdk/internal/session/content/ThumbnailExtractor.kt | 2 +- newsfragment/3396.feature | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 newsfragment/3396.feature diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt index fde3dd906c..82cd682eae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt @@ -52,7 +52,7 @@ internal class ThumbnailExtractor @Inject constructor( mediaMetadataRetriever.setDataSource(context, attachment.queryUri) mediaMetadataRetriever.frameAtTime?.let { thumbnail -> val outputStream = ByteArrayOutputStream() - thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) + thumbnail.compress(Bitmap.CompressFormat.JPEG, 80, outputStream) val thumbnailWidth = thumbnail.width val thumbnailHeight = thumbnail.height val thumbnailSize = outputStream.size() diff --git a/newsfragment/3396.feature b/newsfragment/3396.feature new file mode 100644 index 0000000000..29ae6422bf --- /dev/null +++ b/newsfragment/3396.feature @@ -0,0 +1 @@ +Compress thumbnail: change Jpeg quality from 100 to 80 \ No newline at end of file From cc6263b20f5dce54cdf337b5499210686d5d002b Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 28 May 2021 10:29:21 +0200 Subject: [PATCH 28/34] Fix crash on signout --- .../android/sdk/internal/session/SessionListeners.kt | 8 +++++--- newsfragment/3424.bugfix | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 newsfragment/3424.bugfix diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt index 5f529b3e66..d5c661b1e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt @@ -20,6 +20,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.di.SessionId +import timber.log.Timber import javax.inject.Inject @SessionScope @@ -43,15 +44,16 @@ internal class SessionListeners @Inject constructor( fun dispatch(block: (Session, Session.Listener) -> Unit) { synchronized(listeners) { - val session = getSession() + val session = getSession() ?: return Unit.also { + Timber.w("You don't have any attached session") + } listeners.forEach { tryOrNull { block(session, it) } } } } - private fun getSession(): Session { + private fun getSession(): Session? { return sessionManager.getSessionComponent(sessionId)?.session() - ?: throw IllegalStateException("No session found with this id.") } } diff --git a/newsfragment/3424.bugfix b/newsfragment/3424.bugfix new file mode 100644 index 0000000000..db66595cbd --- /dev/null +++ b/newsfragment/3424.bugfix @@ -0,0 +1 @@ +Fix app crashing when signing out \ No newline at end of file From 871360754e90c44c2f431be3fbe4bd9b24a77dd5 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 28 May 2021 11:12:10 +0200 Subject: [PATCH 29/34] Code review --- .../internal/session/permalinks/DefaultPermalinkService.kt | 4 ++-- .../sdk/internal/session/permalinks/PermalinkFactory.kt | 2 +- .../sdk/internal/session/permalinks/ViaParameterFinder.kt | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/DefaultPermalinkService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/DefaultPermalinkService.kt index 8b45c9f570..134da4ce51 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/DefaultPermalinkService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/DefaultPermalinkService.kt @@ -33,8 +33,8 @@ internal class DefaultPermalinkService @Inject constructor( return permalinkFactory.createPermalink(id) } - override fun createRoomPermalink(roomId: String, via: List?): String? { - return permalinkFactory.createRoomPermalink(roomId, via) + override fun createRoomPermalink(roomId: String, viaServers: List?): String? { + return permalinkFactory.createRoomPermalink(roomId, viaServers) } override fun createPermalink(roomId: String, eventId: String): String { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt index 23ed2f68a3..639e45582a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt @@ -48,7 +48,7 @@ internal class PermalinkFactory @Inject constructor( append(MATRIX_TO_URL_BASE) append(escape(roomId)) append( - via?.takeIf { it.isNotEmpty() }?.let { viaParameterFinder.computeViaParams(it) } + via?.takeIf { it.isNotEmpty() }?.let { viaParameterFinder.asUrlViaParameters(it) } ?: viaParameterFinder.computeViaParams(userId, roomId) ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt index f00d2a9ef7..72fbfcced5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt @@ -39,11 +39,10 @@ internal class ViaParameterFinder @Inject constructor( * current user one. */ fun computeViaParams(userId: String, roomId: String): String { - return computeViaParams(userId, roomId, 3) - .joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") } + return asUrlViaParameters(computeViaParams(userId, roomId, 3)) } - fun computeViaParams(viaList: List): String { + fun asUrlViaParameters(viaList: List): String { return viaList.joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") } } From 836643714761b2aae92a816a14eabca9838818d6 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 28 May 2021 12:10:20 +0200 Subject: [PATCH 30/34] Fix unused variables --- .../vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt | 2 +- .../app/features/spaces/manage/SpaceSettingsController.kt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt index d17fe7d951..ad71d0b1b5 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt @@ -174,7 +174,7 @@ class MatrixToRoomSpaceFragment @Inject constructor( } } - val images = listOf(views.knownMember1, views.knownMember2, views.knownMember3, views.knownMember4, views.knownMember5) + listOf(views.knownMember1, views.knownMember2, views.knownMember3, views.knownMember4, views.knownMember5) .onEach { it.isGone = true } when (state.peopleYouKnow) { is Success -> { diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsController.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsController.kt index c0cf91ecaa..e0e7575f35 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsController.kt @@ -70,7 +70,6 @@ class SpaceSettingsController @Inject constructor( RoomSettingsViewState.AvatarAction.None -> { // Use the current value avatarRenderer(host.avatarRenderer) - val mxItem = roomSummary.toMatrixItem() // We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar. matrixItem(roomSummary.toMatrixItem().updateAvatar(data.currentRoomAvatarUrl)) } From 34b012732e42876f3178361e2f895ecfb4feed01 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 28 May 2021 14:28:32 +0200 Subject: [PATCH 31/34] Call transfer: handle unknown person correctly --- .../app/features/call/VectorCallActivity.kt | 35 ++++++++------- .../app/features/call/VectorCallViewModel.kt | 43 +++++++++---------- .../app/features/call/VectorCallViewState.kt | 9 +++- vector/src/main/res/values/strings.xml | 1 + 4 files changed, 49 insertions(+), 39 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt index d8f183a94a..ad04e33414 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt @@ -175,7 +175,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro when (callState) { is CallState.Idle, is CallState.CreateOffer, - is CallState.Dialing -> { + is CallState.Dialing -> { views.callVideoGroup.isInvisible = true views.callInfoGroup.isVisible = true views.callStatusText.setText(R.string.call_ring) @@ -189,17 +189,22 @@ class VectorCallActivity : VectorBaseActivity(), CallContro configureCallInfo(state) } - is CallState.Answering -> { + is CallState.Answering -> { views.callVideoGroup.isInvisible = true views.callInfoGroup.isVisible = true views.callStatusText.setText(R.string.call_connecting) views.callConnectingProgress.isVisible = true configureCallInfo(state) } - is CallState.Connected -> { + is CallState.Connected -> { if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) { - if (state.transfereeName.hasValue()) { - views.callActionText.text = getString(R.string.call_transfer_transfer_to_title, state.transfereeName.get()) + if (state.transferee !is VectorCallViewState.TransfereeState.NoTransferee) { + val transfereeName = if (state.transferee is VectorCallViewState.TransfereeState.KnownTransferee) { + state.transferee.name + } else { + getString(R.string.call_transfer_unknown_person) + } + views.callActionText.text = getString(R.string.call_transfer_transfer_to_title, transfereeName) views.callActionText.isVisible = true views.callActionText.setOnClickListener { callViewModel.handle(VectorCallViewActions.TransferCall) } views.callStatusText.text = state.formattedDuration @@ -226,7 +231,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro if (callArgs.isVideoCall) { views.callVideoGroup.isVisible = true views.callInfoGroup.isVisible = false - views.pipRenderer.isVisible = !state.isVideoCaptureInError && state.otherKnownCallInfo == null + views.pipRenderer.isVisible = !state.isVideoCaptureInError && state.otherKnownCallInfo == null } else { views.callVideoGroup.isInvisible = true views.callInfoGroup.isVisible = true @@ -241,10 +246,10 @@ class VectorCallActivity : VectorBaseActivity(), CallContro views.callConnectingProgress.isVisible = true } } - is CallState.Terminated -> { + is CallState.Terminated -> { finish() } - null -> { + null -> { } } } @@ -253,10 +258,10 @@ class VectorCallActivity : VectorBaseActivity(), CallContro state.callInfo.otherUserItem?.let { val colorFilter = ContextCompat.getColor(this, R.color.bg_call_screen) avatarRenderer.renderBlur(it, views.bgCallView, sampling = 20, rounded = false, colorFilter = colorFilter) - if (state.transfereeName.hasValue()) { - views.participantNameText.text = getString(R.string.call_transfer_consulting_with, it.getBestName()) - } else { + if (state.transferee is VectorCallViewState.TransfereeState.NoTransferee) { views.participantNameText.text = it.getBestName() + } else { + views.participantNameText.text = getString(R.string.call_transfer_consulting_with, it.getBestName()) } if (blurAvatar) { avatarRenderer.renderBlur(it, views.otherMemberAvatar, sampling = 2, rounded = true, colorFilter = colorFilter) @@ -332,13 +337,13 @@ class VectorCallActivity : VectorBaseActivity(), CallContro private fun handleViewEvents(event: VectorCallViewEvents?) { Timber.v("## VOIP handleViewEvents $event") when (event) { - VectorCallViewEvents.DismissNoCall -> { + VectorCallViewEvents.DismissNoCall -> { finish() } - is VectorCallViewEvents.ConnectionTimeout -> { + is VectorCallViewEvents.ConnectionTimeout -> { onErrorTimoutConnect(event.turn) } - is VectorCallViewEvents.ShowDialPad -> { + is VectorCallViewEvents.ShowDialPad -> { CallDialPadBottomSheet.newInstance(false).apply { callback = dialPadCallback }.show(supportFragmentManager, FRAGMENT_DIAL_PAD_TAG) @@ -346,7 +351,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro is VectorCallViewEvents.ShowCallTransferScreen -> { navigator.openCallTransfer(this, callArgs.callId) } - null -> { + null -> { } } } diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt index aed38f9e98..18eda0fd6f 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt @@ -23,8 +23,8 @@ import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.call.audio.CallAudioManager @@ -39,7 +39,6 @@ import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.call.MxPeerConnectionState import org.matrix.android.sdk.api.session.room.model.call.supportCallTransfer -import org.matrix.android.sdk.api.util.Optional class VectorCallViewModel @AssistedInject constructor( @Assisted initialState: VectorCallViewState, @@ -109,22 +108,22 @@ class VectorCallViewModel @AssistedInject constructor( } } } - val transfereeName = computeTransfereeNameIfAny(call) setState { copy( callState = Success(callState), canOpponentBeTransferred = call.capabilities.supportCallTransfer(), - transfereeName = transfereeName + transferee = computeTransfereeState(call) ) } } } - private fun computeTransfereeNameIfAny(call: MxCall): Optional { - val transfereeCall = callManager.getTransfereeForCallId(call.callId) ?: return Optional.empty() + private fun computeTransfereeState(call: MxCall): VectorCallViewState.TransfereeState { + val transfereeCall = callManager.getTransfereeForCallId(call.callId) ?: return VectorCallViewState.TransfereeState.NoTransferee val transfereeRoom = session.getRoomSummary(transfereeCall.nativeRoomId) - val transfereeName = transfereeRoom?.displayName ?: "Unknown person" - return Optional.from(transfereeName) + return transfereeRoom?.displayName?.let { + VectorCallViewState.TransfereeState.KnownTransferee(it) + } ?: VectorCallViewState.TransfereeState.UnknownTransferee } private val currentCallListener = object : WebRtcCallManager.CurrentCallListener { @@ -176,7 +175,7 @@ class VectorCallViewModel @AssistedInject constructor( } else { call = webRtcCall callManager.addCurrentCallListener(currentCallListener) - val item = webRtcCall.getOpponentAsMatrixItem(session) + val item = webRtcCall.getOpponentAsMatrixItem(session) webRtcCall.addListener(callListener) val currentSoundDevice = callManager.audioManager.selectedDevice if (currentSoundDevice == CallAudioManager.Device.PHONE) { @@ -196,7 +195,7 @@ class VectorCallViewModel @AssistedInject constructor( formattedDuration = webRtcCall.formattedDuration(), isHD = webRtcCall.mxCall.isVideoCall && webRtcCall.currentCaptureFormat() is CaptureFormat.HD, canOpponentBeTransferred = webRtcCall.mxCall.capabilities.supportCallTransfer(), - transfereeName = computeTransfereeNameIfAny(webRtcCall.mxCall) + transferee = computeTransfereeState(webRtcCall.mxCall) ) } updateOtherKnownCall(webRtcCall) @@ -212,27 +211,27 @@ class VectorCallViewModel @AssistedInject constructor( override fun handle(action: VectorCallViewActions) = withState { state -> when (action) { - VectorCallViewActions.EndCall -> call?.endCall() - VectorCallViewActions.AcceptCall -> { + VectorCallViewActions.EndCall -> call?.endCall() + VectorCallViewActions.AcceptCall -> { setState { copy(callState = Loading()) } call?.acceptIncomingCall() } - VectorCallViewActions.DeclineCall -> { + VectorCallViewActions.DeclineCall -> { setState { copy(callState = Loading()) } call?.endCall() } - VectorCallViewActions.ToggleMute -> { + VectorCallViewActions.ToggleMute -> { val muted = state.isAudioMuted call?.muteCall(!muted) setState { copy(isAudioMuted = !muted) } } - VectorCallViewActions.ToggleVideo -> { + VectorCallViewActions.ToggleVideo -> { if (state.isVideoCall) { val videoEnabled = state.isVideoEnabled call?.enableVideo(!videoEnabled) @@ -242,14 +241,14 @@ class VectorCallViewModel @AssistedInject constructor( } Unit } - VectorCallViewActions.ToggleHoldResume -> { + VectorCallViewActions.ToggleHoldResume -> { val isRemoteOnHold = state.isRemoteOnHold call?.updateRemoteOnHold(!isRemoteOnHold) } is VectorCallViewActions.ChangeAudioDevice -> { callManager.audioManager.setAudioDevice(action.device) } - VectorCallViewActions.SwitchSoundDevice -> { + VectorCallViewActions.SwitchSoundDevice -> { _viewEvents.post( VectorCallViewEvents.ShowSoundDeviceChooser(state.availableDevices, state.device) ) @@ -265,17 +264,17 @@ class VectorCallViewModel @AssistedInject constructor( } Unit } - VectorCallViewActions.ToggleCamera -> { + VectorCallViewActions.ToggleCamera -> { call?.switchCamera() } - VectorCallViewActions.ToggleHDSD -> { + VectorCallViewActions.ToggleHDSD -> { if (!state.isVideoCall) return@withState call?.setCaptureFormat(if (state.isHD) CaptureFormat.SD else CaptureFormat.HD) } - VectorCallViewActions.OpenDialPad -> { + VectorCallViewActions.OpenDialPad -> { _viewEvents.post(VectorCallViewEvents.ShowDialPad) } - is VectorCallViewActions.SendDtmfDigit -> { + is VectorCallViewActions.SendDtmfDigit -> { call?.sendDtmfDigit(action.digit) } VectorCallViewActions.InitiateCallTransfer -> { @@ -283,7 +282,7 @@ class VectorCallViewModel @AssistedInject constructor( VectorCallViewEvents.ShowCallTransferScreen ) } - VectorCallViewActions.TransferCall -> { + VectorCallViewActions.TransferCall -> { handleCallTransfer() } }.exhaustive diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt index 70f28dcc23..448bda08c4 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt @@ -22,7 +22,6 @@ import com.airbnb.mvrx.Uninitialized import im.vector.app.features.call.audio.CallAudioManager import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.util.MatrixItem -import org.matrix.android.sdk.api.util.Optional data class VectorCallViewState( val callId: String, @@ -43,9 +42,15 @@ data class VectorCallViewState( val callInfo: CallInfo = CallInfo(callId), val formattedDuration: String = "", val canOpponentBeTransferred: Boolean = false, - val transfereeName: Optional = Optional.empty() + val transferee: TransfereeState = TransfereeState.NoTransferee ) : MvRxState { + sealed class TransfereeState { + object NoTransferee: TransfereeState() + data class KnownTransferee(val name:String): TransfereeState() + object UnknownTransferee: TransfereeState() + } + data class CallInfo( val callId: String, val otherUserItem: MatrixItem? = null diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index b1f1c12662..3f49994fd6 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3234,6 +3234,7 @@ Users Consulting with %1$s Transfer to %1$s + Unknown person Re-Authentication Needed From fca74e9eb45127e9516e3daa76f636928fb2e4c8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 28 May 2021 16:36:03 +0200 Subject: [PATCH 32/34] Small cleanup during review --- .../android/sdk/api/session/call/MxCall.kt | 1 + .../room/model/call/CallReplacesContent.kt | 17 ++++----- .../internal/session/call/MxCallFactory.kt | 5 ++- .../app/features/call/VectorCallViewState.kt | 8 ++-- .../app/features/call/webrtc/WebRtcCall.kt | 38 ++++++++++++------- 5 files changed, 41 insertions(+), 28 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt index 08278d8e4f..fcc9f7072d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt @@ -89,6 +89,7 @@ interface MxCall : MxCallDetail { /** * Send a m.call.replaces event to initiate call transfer. + * See [org.matrix.android.sdk.api.session.room.model.call.CallReplacesContent] for documentation about the parameters */ suspend fun transfer(targetUserId: String, targetRoomId: String?, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt index 1ae1c09a35..4559c5db6d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt @@ -38,23 +38,23 @@ data class CallReplacesContent( */ @Json(name = "replacement_id") val replacementId: String? = null, /** - * Optional. If specified, the transferee client waits for an invite to this room and joins it - * (possibly waiting for user confirmation) and then continues the transfer in this room. - * If absent, the transferee contacts the Matrix User ID given in the target_user field in a room of its choosing. + * Optional. If specified, the transferee client waits for an invite to this room and joins it + * (possibly waiting for user confirmation) and then continues the transfer in this room. + * If absent, the transferee contacts the Matrix User ID given in the target_user field in a room of its choosing. */ @Json(name = "target_room") val targetRoomId: String? = null, /** - * An object giving information about the transfer target + * An object giving information about the transfer target */ @Json(name = "target_user") val targetUser: TargetUser? = null, /** - * If specified, gives the call ID for the transferee's client to use when placing the replacement call. - * Mutually exclusive with await_call + * If specified, gives the call ID for the transferee's client to use when placing the replacement call. + * Mutually exclusive with await_call */ @Json(name = "create_call") val createCall: String? = null, /** - * If specified, gives the call ID that the transferee's client should wait for. - * Mutually exclusive with create_call. + * If specified, gives the call ID that the transferee's client should wait for. + * Mutually exclusive with create_call. */ @Json(name = "await_call") val awaitCall: String? = null, /** @@ -77,6 +77,5 @@ data class CallReplacesContent( * Optional. The avatar URL of the transfer target. */ @Json(name = "avatar_url") val avatarUrl: String? - ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt index 68ac4369b3..547be2253f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt @@ -75,7 +75,10 @@ internal class MxCallFactory @Inject constructor( } } - fun updateOutgoingCallWithOpponentData(call: MxCall, userId: String, content: CallSignalingContent, callCapabilities: CallCapabilities?) { + fun updateOutgoingCallWithOpponentData(call: MxCall, + userId: String, + content: CallSignalingContent, + callCapabilities: CallCapabilities?) { (call as? MxCallImpl)?.updateOpponentData(userId, content, callCapabilities) } } diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt index 448bda08c4..c5ae61cf60 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt @@ -46,9 +46,9 @@ data class VectorCallViewState( ) : MvRxState { sealed class TransfereeState { - object NoTransferee: TransfereeState() - data class KnownTransferee(val name:String): TransfereeState() - object UnknownTransferee: TransfereeState() + object NoTransferee : TransfereeState() + data class KnownTransferee(val name: String) : TransfereeState() + object UnknownTransferee : TransfereeState() } data class CallInfo( @@ -56,7 +56,7 @@ data class VectorCallViewState( val otherUserItem: MatrixItem? = null ) - constructor(callArgs: CallArgs): this( + constructor(callArgs: CallArgs) : this( callId = callArgs.callId, roomId = callArgs.signalingRoomId, isVideoCall = callArgs.isVideoCall diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt index 7abb077ee0..f2a008feb7 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt @@ -86,16 +86,19 @@ private const val AUDIO_TRACK_ID = "ARDAMSa0" private const val VIDEO_TRACK_ID = "ARDAMSv0" private val DEFAULT_AUDIO_CONSTRAINTS = MediaConstraints() -class WebRtcCall(val mxCall: MxCall, - // This is where the call is placed from an ui perspective. In case of virtual room, it can differs from the signalingRoomId. - val nativeRoomId: String, - private val rootEglBase: EglBase?, - private val context: Context, - private val dispatcher: CoroutineContext, - private val sessionProvider: Provider, - private val peerConnectionFactoryProvider: Provider, - private val onCallBecomeActive: (WebRtcCall) -> Unit, - private val onCallEnded: (String) -> Unit) : MxCall.StateListener { +class WebRtcCall( + val mxCall: MxCall, + // This is where the call is placed from an ui perspective. + // In case of virtual room, it can differs from the signalingRoomId. + val nativeRoomId: String, + private val rootEglBase: EglBase?, + private val context: Context, + private val dispatcher: CoroutineContext, + private val sessionProvider: Provider, + private val peerConnectionFactoryProvider: Provider, + private val onCallBecomeActive: (WebRtcCall) -> Unit, + private val onCallEnded: (String) -> Unit +) : MxCall.StateListener { interface Listener : MxCall.StateListener { fun onCaptureStateChanged() {} @@ -119,6 +122,7 @@ class WebRtcCall(val mxCall: MxCall, } val callId = mxCall.callId + // room where call signaling is placed. In case of virtual room it can differs from the nativeRoomId. val signalingRoomId = mxCall.roomId @@ -290,6 +294,9 @@ class WebRtcCall(val mxCall: MxCall, } } + /** + * Without consultation + */ suspend fun transferToUser(targetUserId: String, targetRoomId: String?) { mxCall.transfer( targetUserId = targetUserId, @@ -300,22 +307,25 @@ class WebRtcCall(val mxCall: MxCall, endCall(sendEndSignaling = false) } + /** + * With consultation + */ suspend fun transferToCall(transferTargetCall: WebRtcCall) { val newCallId = CallIdGenerator.generate() transferTargetCall.mxCall.transfer( - targetUserId = this@WebRtcCall.mxCall.opponentUserId, + targetUserId = mxCall.opponentUserId, targetRoomId = null, createCallId = null, awaitCallId = newCallId ) - this@WebRtcCall.mxCall.transfer( + mxCall.transfer( targetUserId = transferTargetCall.mxCall.opponentUserId, targetRoomId = null, createCallId = newCallId, awaitCallId = null ) - this@WebRtcCall.endCall(sendEndSignaling = false) - transferTargetCall.endCall(sendEndSignaling = false) + endCall(sendEndSignaling = false) + transferTargetCall.endCall(sendEndSignaling = false) } fun acceptIncomingCall() { From 90a16ebbafb7360620355c5bed66152e1a712397 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 31 May 2021 10:53:04 +0200 Subject: [PATCH 33/34] /snow -> /snowfall and update wording (iso Element Web) (#3430) --- .../sdk/api/session/room/model/message/MessageType.kt | 2 +- newsfragment/3430.feature | 1 + .../java/im/vector/app/features/command/Command.kt | 2 +- .../im/vector/app/features/command/CommandParser.kt | 6 +++--- .../app/features/home/room/detail/ChatEffectManager.kt | 10 +++++----- .../features/home/room/detail/RoomDetailFragment.kt | 2 +- .../features/home/room/detail/RoomDetailViewModel.kt | 2 +- vector/src/main/res/values/strings.xml | 4 ++-- 8 files changed, 15 insertions(+), 14 deletions(-) create mode 100644 newsfragment/3430.feature diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt index c96a800ee5..1e8959afc3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt @@ -35,5 +35,5 @@ object MessageType { const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker" const val MSGTYPE_CONFETTI = "nic.custom.confetti" - const val MSGTYPE_SNOW = "io.element.effect.snowfall" + const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall" } diff --git a/newsfragment/3430.feature b/newsfragment/3430.feature new file mode 100644 index 0000000000..ee9c269bcd --- /dev/null +++ b/newsfragment/3430.feature @@ -0,0 +1 @@ +/snow -> /snowfall and update wording (iso Element Web) \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/command/Command.kt b/vector/src/main/java/im/vector/app/features/command/Command.kt index 0b210cf298..61d39857cc 100644 --- a/vector/src/main/java/im/vector/app/features/command/Command.kt +++ b/vector/src/main/java/im/vector/app/features/command/Command.kt @@ -46,7 +46,7 @@ enum class Command(val command: String, val parameters: String, @StringRes val d PLAIN("/plain", "", R.string.command_description_plain, false), DISCARD_SESSION("/discardsession", "", R.string.command_description_discard_session, false), CONFETTI("/confetti", "", R.string.command_confetti, false), - SNOW("/snow", "", R.string.command_snow, false), + SNOWFALL("/snowfall", "", R.string.command_snow, false), CREATE_SPACE("/createspace", " *", R.string.command_description_create_space, true), ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_create_space, true), JOIN_SPACE("/joinSpace", "spaceId", R.string.command_description_join_space, true), diff --git a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt index 9b190d64fe..3de00f4d0c 100644 --- a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt @@ -296,9 +296,9 @@ object CommandParser { val message = textMessage.substring(Command.CONFETTI.command.length).trim() ParsedCommand.SendChatEffect(ChatEffect.CONFETTI, message) } - Command.SNOW.command -> { - val message = textMessage.substring(Command.SNOW.command.length).trim() - ParsedCommand.SendChatEffect(ChatEffect.SNOW, message) + Command.SNOWFALL.command -> { + val message = textMessage.substring(Command.SNOWFALL.command.length).trim() + ParsedCommand.SendChatEffect(ChatEffect.SNOWFALL, message) } Command.CREATE_SPACE.command -> { val rawCommand = textMessage.substring(Command.CREATE_SPACE.command.length).trim() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/ChatEffectManager.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/ChatEffectManager.kt index e7136762d5..24151c5c10 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/ChatEffectManager.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/ChatEffectManager.kt @@ -26,13 +26,13 @@ import javax.inject.Inject enum class ChatEffect { CONFETTI, - SNOW + SNOWFALL } fun ChatEffect.toMessageType(): String { return when (this) { ChatEffect.CONFETTI -> MessageType.MSGTYPE_CONFETTI - ChatEffect.SNOW -> MessageType.MSGTYPE_SNOW + ChatEffect.SNOWFALL -> MessageType.MSGTYPE_SNOWFALL } } @@ -112,14 +112,14 @@ class ChatEffectManager @Inject constructor() { private fun findEffect(content: MessageContent, event: TimelineEvent): ChatEffect? { return when (content.msgType) { MessageType.MSGTYPE_CONFETTI -> ChatEffect.CONFETTI - MessageType.MSGTYPE_SNOW -> ChatEffect.SNOW + MessageType.MSGTYPE_SNOWFALL -> ChatEffect.SNOWFALL MessageType.MSGTYPE_EMOTE, MessageType.MSGTYPE_TEXT -> { event.root.getClearContent().toModel()?.body ?.let { text -> when { EMOJIS_FOR_CONFETTI.any { text.contains(it) } -> ChatEffect.CONFETTI - EMOJIS_FOR_SNOW.any { text.contains(it) } -> ChatEffect.SNOW + EMOJIS_FOR_SNOWFALL.any { text.contains(it) } -> ChatEffect.SNOWFALL else -> null } } @@ -133,7 +133,7 @@ class ChatEffectManager @Inject constructor() { "🎉", "🎊" ) - private val EMOJIS_FOR_SNOW = listOf( + private val EMOJIS_FOR_SNOWFALL = listOf( "⛄️", "☃️", "❄️" diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index f13f9dba60..7fa36a39d7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -438,7 +438,7 @@ class RoomDetailFragment @Inject constructor( .setPosition(-50f, views.viewKonfetti.width + 50f, -50f, -50f) .streamFor(150, 3000L) } - ChatEffect.SNOW -> { + ChatEffect.SNOWFALL -> { views.viewSnowFall.isVisible = true views.viewSnowFall.restartFalling() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 205ccf7fca..a2041c0a80 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -893,7 +893,7 @@ class RoomDetailViewModel @AssistedInject constructor( if (sendChatEffect.message.isBlank()) { val defaultMessage = stringProvider.getString(when (sendChatEffect.chatEffect) { ChatEffect.CONFETTI -> R.string.default_message_emote_confetti - ChatEffect.SNOW -> R.string.default_message_emote_snow + ChatEffect.SNOWFALL -> R.string.default_message_emote_snow }) room.sendTextMessage(defaultMessage, MessageType.MSGTYPE_EMOTE) } else { diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index e151930efb..80120b51bf 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3010,11 +3010,11 @@ Sends the given message with confetti - Sends the given message with snow + Sends the given message with snowfall sends confetti 🎉 - sends snow ❄️ + sends snowfall ❄️ Unencrypted Encrypted by an unverified device From 00ee61a2a08c9023db1ca2adc83446cae454c4fd Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 1 Jun 2021 12:11:49 +0200 Subject: [PATCH 34/34] stable ids for MSC 2858 --- .../android/sdk/api/auth/data/SsoIdentityProvider.kt | 12 ++++++------ .../matrix/android/sdk/internal/auth/Constants.kt | 1 - .../internal/auth/DefaultAuthenticationService.kt | 4 +--- .../sdk/internal/auth/data/LoginFlowResponse.kt | 2 +- newsfragment/3442.bugfix | 1 + 5 files changed, 9 insertions(+), 11 deletions(-) create mode 100644 newsfragment/3442.bugfix diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SsoIdentityProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SsoIdentityProvider.kt index 64b3e180aa..a0733dda97 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SsoIdentityProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SsoIdentityProvider.kt @@ -51,12 +51,12 @@ data class SsoIdentityProvider( ) : Parcelable, Comparable { companion object { - const val BRAND_GOOGLE = "org.matrix.google" - const val BRAND_GITHUB = "org.matrix.github" - const val BRAND_APPLE = "org.matrix.apple" - const val BRAND_FACEBOOK = "org.matrix.facebook" - const val BRAND_TWITTER = "org.matrix.twitter" - const val BRAND_GITLAB = "org.matrix.gitlab" + const val BRAND_GOOGLE = "google" + const val BRAND_GITHUB = "github" + const val BRAND_APPLE = "apple" + const val BRAND_FACEBOOK = "facebook" + const val BRAND_TWITTER = "twitter" + const val BRAND_GITLAB = "gitlab" } override fun compareTo(other: SsoIdentityProvider): Int { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt index e0c52cf9ca..3742a429d2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt @@ -33,7 +33,6 @@ internal const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/" * Ref: https://matrix.org/docs/spec/client_server/latest#sso-client-login */ internal const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect" -internal const val MSC2858_SSO_REDIRECT_PATH = "/_matrix/client/unstable/org.matrix.msc2858/login/sso/redirect" internal const val SSO_REDIRECT_URL_PARAM = "redirectUrl" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 46256f4b81..20ce438d8e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -88,11 +88,9 @@ internal class DefaultAuthenticationService @Inject constructor( return buildString { append(homeServerUrlBase) + append(SSO_REDIRECT_PATH) if (providerId != null) { - append(MSC2858_SSO_REDIRECT_PATH) append("/$providerId") - } else { - append(SSO_REDIRECT_PATH) } // Set the redirect url appendParamToUrl(SSO_REDIRECT_URL_PARAM, redirectUrl) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt index d0d17e2cd5..c718fae390 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt @@ -42,7 +42,7 @@ internal data class LoginFlow( * the client can show a button for each of the supported providers * See MSC #2858 */ - @Json(name = "org.matrix.msc2858.identity_providers") + @Json(name = "identity_providers") val ssoIdentityProvider: List? = null ) diff --git a/newsfragment/3442.bugfix b/newsfragment/3442.bugfix new file mode 100644 index 0000000000..5a2d08ec18 --- /dev/null +++ b/newsfragment/3442.bugfix @@ -0,0 +1 @@ +Switch to stable endpoint/fields for MSC2858 \ No newline at end of file