diff --git a/CHANGES.md b/CHANGES.md index 89fdb65d69..09991b3c48 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ Bugfix 🐛: - Fix dark theme issue on login screen (#1097) - Incomplete predicate in RealmCryptoStore#getOutgoingRoomKeyRequest (#1519) - User could not redact message that they have sent (#1543) + - Use vendor prefix for non merged MSC (#1537) Translations 🗣: - @@ -27,7 +28,7 @@ Other changes: - Use `retrofit2.Call.awaitResponse` extension provided by Retrofit 2. (#1526) - Fix minor typo in contribution guide (#1512) - Fix self-assignment of callback in `DefaultRoomPushRuleService#setRoomNotificationState` (#1520) - - Random housekeeping clean-ups indicated by Lint (#1520) + - Random housekeeping clean-ups indicated by Lint (#1520, #1541) Changes in RiotX 0.22.0 (2020-06-15) =================================================== diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt index 35ad8ff4e1..4eda7c60c8 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt @@ -241,14 +241,14 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { val eventWireContent = event.content.toContent() assertNotNull(eventWireContent) - assertNull(eventWireContent.get("body")) - assertEquals(MXCRYPTO_ALGORITHM_MEGOLM, eventWireContent.get("algorithm")) + assertNull(eventWireContent["body"]) + assertEquals(MXCRYPTO_ALGORITHM_MEGOLM, eventWireContent["algorithm"]) - assertNotNull(eventWireContent.get("ciphertext")) - assertNotNull(eventWireContent.get("session_id")) - assertNotNull(eventWireContent.get("sender_key")) + assertNotNull(eventWireContent["ciphertext"]) + assertNotNull(eventWireContent["session_id"]) + assertNotNull(eventWireContent["sender_key"]) - assertEquals(senderSession.sessionParams.deviceId, eventWireContent.get("device_id")) + assertEquals(senderSession.sessionParams.deviceId, eventWireContent["device_id"]) assertNotNull(event.eventId) assertEquals(roomId, event.roomId) @@ -257,7 +257,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { val eventContent = event.toContent() assertNotNull(eventContent) - assertEquals(clearMessage, eventContent.get("body")) + assertEquals(clearMessage, eventContent["body"]) assertEquals(senderSession.myUserId, event.senderId) } diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt index cbd175f53f..d93b151ded 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt @@ -144,7 +144,7 @@ class QuadSTests : InstrumentedTest { val secretAccountData = assertAccountData(aliceSession, "secret.of.life") - val encryptedContent = secretAccountData.content.get("encrypted") as? Map<*, *> + val encryptedContent = secretAccountData.content["encrypted"] as? Map<*, *> assertNotNull("Element should be encrypted", encryptedContent) assertNotNull("Secret should be encrypted with default key", encryptedContent?.get(keyId)) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/EventMatchCondition.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/EventMatchCondition.kt index a6d7e48b9d..ca13155542 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/EventMatchCondition.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/EventMatchCondition.kt @@ -87,14 +87,13 @@ class EventMatchCondition( // Very simple glob to regexp converter private fun simpleGlobToRegExp(glob: String): String { var out = "" // "^" - for (i in 0 until glob.length) { - val c = glob[i] - when (c) { + for (element in glob) { + when (element) { '*' -> out += ".*" '?' -> out += '.'.toString() '.' -> out += "\\." '\\' -> out += "\\\\" - else -> out += c + else -> out += element } } out += "" // '$'.toString() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/RelationType.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/RelationType.kt index 3be9bdb7cc..8ffb8eb63f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/RelationType.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/RelationType.kt @@ -26,5 +26,5 @@ object RelationType { /** Lets you define an event which references an existing event.*/ const val REFERENCE = "m.reference" /** Lets you define an event which adds a response to an existing event.*/ - const val RESPONSE = "m.response" + const val RESPONSE = "org.matrix.response" } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/widgets/WidgetPostAPIMediator.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/widgets/WidgetPostAPIMediator.kt index ac3ed8df09..bc39afda8f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/widgets/WidgetPostAPIMediator.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/widgets/WidgetPostAPIMediator.kt @@ -90,6 +90,6 @@ interface WidgetPostAPIMediator { /** * Triggered when a widget is posting */ - fun handleWidgetRequest(eventData: JsonDict): Boolean + fun handleWidgetRequest(mediator: WidgetPostAPIMediator, eventData: JsonDict): Boolean } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/widgets/WidgetService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/widgets/WidgetService.kt index 2585d0a968..1f62a335a9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/widgets/WidgetService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/widgets/WidgetService.kt @@ -34,7 +34,8 @@ interface WidgetService { fun getWidgetURLFormatter(): WidgetURLFormatter /** - * Returns an instance of [WidgetPostAPIMediator]. + * Returns a new instance of [WidgetPostAPIMediator]. + * Be careful to call clearWebView method and setHandler to null to avoid memory leaks. * This is to be used for "admin" widgets so you can interact through JS. */ fun getWidgetPostAPIMediator(): WidgetPostAPIMediator diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/widgets/model/WidgetType.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/widgets/model/WidgetType.kt index a8ef77cdc3..3977a4c474 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/widgets/model/WidgetType.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/widgets/model/WidgetType.kt @@ -16,6 +16,22 @@ package im.vector.matrix.android.api.session.widgets.model +private val DEFINED_TYPES by lazy { + listOf( + WidgetType.Jitsi, + WidgetType.TradingView, + WidgetType.Spotify, + WidgetType.Video, + WidgetType.GoogleDoc, + WidgetType.GoogleCalendar, + WidgetType.Etherpad, + WidgetType.StickerPicker, + WidgetType.Grafana, + WidgetType.Custom, + WidgetType.IntegrationManager + ) +} + /** * Ref: https://github.com/matrix-org/matrix-doc/issues/1236 */ @@ -33,7 +49,7 @@ sealed class WidgetType(open val preferred: String, open val legacy: String = pr object IntegrationManager : WidgetType("m.integration_manager") data class Fallback(override val preferred: String) : WidgetType(preferred) - fun matches(type: String?): Boolean { + fun matches(type: String): Boolean { return type == preferred || type == legacy } @@ -43,20 +59,6 @@ sealed class WidgetType(open val preferred: String, open val legacy: String = pr companion object { - private val DEFINED_TYPES = listOf( - Jitsi, - TradingView, - Spotify, - Video, - GoogleDoc, - GoogleCalendar, - Etherpad, - StickerPicker, - Grafana, - Custom, - IntegrationManager - ) - fun fromString(type: String): WidgetType { val matchingType = DEFINED_TYPES.firstOrNull { it.matches(type) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt index 7048d790a0..db97ba1052 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt @@ -273,7 +273,7 @@ internal abstract class SASDefaultVerificationTransaction( if (keyIDNoPrefix == otherCrossSigningMasterKeyPublic) { // Check the signature val mac = macUsingAgreedMethod(otherCrossSigningMasterKeyPublic, baseInfo + it) - if (mac != theirMacSafe.mac.get(it)) { + if (mac != theirMacSafe.mac[it]) { // WRONG! Timber.e("## SAS Verification: mac mismatch for MasterKey with id $keyIDNoPrefix") cancel(CancelCode.MismatchedKeys) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventFilter.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventFilter.kt index 14560ead85..f313205c4d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventFilter.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventFilter.kt @@ -25,7 +25,7 @@ internal object TimelineEventFilter { */ internal object Content { internal const val EDIT = """{*"m.relates_to"*"rel_type":*"m.replace"*}""" - internal const val RESPONSE = """{*"m.relates_to"*"rel_type":*"m.response"*}""" + internal const val RESPONSE = """{*"m.relates_to"*"rel_type":*"org.matrix.response"*}""" } /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/widgets/DefaultWidgetPostAPIMediator.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/widgets/DefaultWidgetPostAPIMediator.kt index 345ef39edd..5bf2d02e3c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/widgets/DefaultWidgetPostAPIMediator.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/widgets/DefaultWidgetPostAPIMediator.kt @@ -78,7 +78,7 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh private fun onWidgetMessage(eventData: JsonDict) { try { - if (handler?.handleWidgetRequest(eventData) == false) { + if (handler?.handleWidgetRequest(this, eventData) == false) { sendError("", eventData) } } catch (e: Exception) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/widgets/DefaultWidgetService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/widgets/DefaultWidgetService.kt index 424168bc62..ab5f1e8858 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/widgets/DefaultWidgetService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/widgets/DefaultWidgetService.kt @@ -26,10 +26,11 @@ import im.vector.matrix.android.api.session.widgets.WidgetURLFormatter import im.vector.matrix.android.api.session.widgets.model.Widget import im.vector.matrix.android.api.util.Cancelable import javax.inject.Inject +import javax.inject.Provider internal class DefaultWidgetService @Inject constructor(private val widgetManager: WidgetManager, private val widgetURLFormatter: WidgetURLFormatter, - private val widgetPostAPIMediator: WidgetPostAPIMediator) + private val widgetPostAPIMediator: Provider) : WidgetService { override fun getWidgetURLFormatter(): WidgetURLFormatter { @@ -37,7 +38,7 @@ internal class DefaultWidgetService @Inject constructor(private val widgetManage } override fun getWidgetPostAPIMediator(): WidgetPostAPIMediator { - return widgetPostAPIMediator + return widgetPostAPIMediator.get() } override fun getRoomWidgets( diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/widgets/token/GetScalarTokenTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/widgets/token/GetScalarTokenTask.kt index f6d1ecb23b..7afff00d64 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/widgets/token/GetScalarTokenTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/widgets/token/GetScalarTokenTask.kt @@ -67,8 +67,7 @@ internal class DefaultGetScalarTokenTask @Inject constructor(private val widgets throw IllegalStateException("Scalar token is null") } scalarTokenStore.setToken(serverUrl, registerWidgetResponse.scalarToken) - widgetsAPI.validateToken(registerWidgetResponse.scalarToken, WIDGET_API_VERSION) - return registerWidgetResponse.scalarToken + return validateToken(widgetsAPI, serverUrl, registerWidgetResponse.scalarToken) } private suspend fun validateToken(widgetsAPI: WidgetsAPI, serverUrl: String, scalarToken: String): String { diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt index b0ae0e4cda..a2026475d0 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt @@ -62,9 +62,9 @@ class ContactPicker(override val requestCode: Int) : Picker() - var emailList = mutableListOf() + val photoUri = cursor.getString(photoUriColumn) + val phoneNumberList = mutableListOf() + val emailList = mutableListOf() getRawContactId(context.contentResolver, contactId)?.let { rawContactId -> val selection = ContactsContract.Data.RAW_CONTACT_ID + " = ?" diff --git a/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt index 57e5c6381f..b2e02d564c 100644 --- a/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt @@ -40,14 +40,14 @@ class DefaultErrorFormatter @Inject constructor( null -> null is IdentityServiceError -> identityServerError(throwable) is Failure.NetworkConnection -> { - when { - throwable.ioException is SocketTimeoutException -> + when (throwable.ioException) { + is SocketTimeoutException -> stringProvider.getString(R.string.error_network_timeout) - throwable.ioException is UnknownHostException -> + is UnknownHostException -> // Invalid homeserver? // TODO Check network state, airplane mode, etc. stringProvider.getString(R.string.login_error_unknown_host) - else -> + else -> stringProvider.getString(R.string.error_no_network) } } diff --git a/vector/src/main/java/im/vector/riotx/core/preference/VectorEditTextPreference.kt b/vector/src/main/java/im/vector/riotx/core/preference/VectorEditTextPreference.kt index 90246dbb92..e2c61118a7 100644 --- a/vector/src/main/java/im/vector/riotx/core/preference/VectorEditTextPreference.kt +++ b/vector/src/main/java/im/vector/riotx/core/preference/VectorEditTextPreference.kt @@ -45,7 +45,7 @@ class VectorEditTextPreference : EditTextPreference { override fun onBindViewHolder(holder: PreferenceViewHolder) { // display the title in multi-line to avoid ellipsis. try { - holder.itemView.findViewById(android.R.id.title)?.setSingleLine(false) + holder.itemView.findViewById(android.R.id.title)?.isSingleLine = false } catch (e: Exception) { Timber.e(e, "onBindView") } diff --git a/vector/src/main/java/im/vector/riotx/core/preference/VectorPreference.kt b/vector/src/main/java/im/vector/riotx/core/preference/VectorPreference.kt index 396bf3054f..048625ded6 100755 --- a/vector/src/main/java/im/vector/riotx/core/preference/VectorPreference.kt +++ b/vector/src/main/java/im/vector/riotx/core/preference/VectorPreference.kt @@ -87,7 +87,7 @@ open class VectorPreference : Preference { val title = itemView.findViewById(android.R.id.title) val summary = itemView.findViewById(android.R.id.summary) if (title != null) { - title.setSingleLine(false) + title.isSingleLine = false title.setTypeface(null, mTypeface) } diff --git a/vector/src/main/java/im/vector/riotx/core/preference/VectorSwitchPreference.kt b/vector/src/main/java/im/vector/riotx/core/preference/VectorSwitchPreference.kt index e6292d102b..9a2064d741 100644 --- a/vector/src/main/java/im/vector/riotx/core/preference/VectorSwitchPreference.kt +++ b/vector/src/main/java/im/vector/riotx/core/preference/VectorSwitchPreference.kt @@ -43,7 +43,7 @@ class VectorSwitchPreference : SwitchPreference { override fun onBindViewHolder(holder: PreferenceViewHolder) { // display the title in multi-line to avoid ellipsis. - holder.itemView.findViewById(android.R.id.title)?.setSingleLine(false) + holder.itemView.findViewById(android.R.id.title)?.isSingleLine = false super.onBindViewHolder(holder) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt index 6911ca84f6..2cca3c013d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt @@ -78,4 +78,5 @@ sealed class RoomDetailAction : VectorViewModelAction { data class ReRequestKeys(val eventId: String) : RoomDetailAction() object SelectStickerAttachment : RoomDetailAction() + object OpenIntegrationManager: RoomDetailAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index e697e41e0e..4a6fc01e75 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -144,7 +144,6 @@ import im.vector.riotx.features.crypto.verification.VerificationBottomSheet import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.composer.TextComposerView import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet -import im.vector.riotx.features.home.room.detail.sticker.StickerPickerConstants import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.action.EventSharedAction import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet @@ -159,6 +158,7 @@ import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet import im.vector.riotx.features.home.room.detail.widget.RoomWidgetsBannerView import im.vector.riotx.features.home.room.detail.widget.RoomWidgetsBottomSheet +import im.vector.riotx.features.home.room.detail.widget.WidgetRequestCodes import im.vector.riotx.features.html.EventHtmlRenderer import im.vector.riotx.features.html.PillImageSpan import im.vector.riotx.features.invite.VectorInviteView @@ -330,22 +330,33 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.observeViewEvents { when (it) { - is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable) - is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds) - is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it) - is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it) - is RoomDetailViewEvents.ShowMessage -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG) - is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it) - is RoomDetailViewEvents.FileTooBigError -> displayFileTooBigError(it) - is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it) - is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it) - is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it) - RoomDetailViewEvents.DisplayPromptForIntegrationManager -> displayPromptForIntegrationManager() - is RoomDetailViewEvents.OpenStickerPicker -> openStickerPicker(it) + is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable) + is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds) + is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it) + is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it) + is RoomDetailViewEvents.ShowMessage -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG) + is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it) + is RoomDetailViewEvents.FileTooBigError -> displayFileTooBigError(it) + is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it) + is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it) + is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it) + RoomDetailViewEvents.DisplayPromptForIntegrationManager -> displayPromptForIntegrationManager() + is RoomDetailViewEvents.OpenStickerPicker -> openStickerPicker(it) + is RoomDetailViewEvents.DisplayEnableIntegrationsWarning -> displayDisabledIntegrationDialog() + is RoomDetailViewEvents.OpenIntegrationManager -> openIntegrationManager() }.exhaustive } } + private fun openIntegrationManager(screen: String? = null) { + navigator.openIntegrationManager( + fragment = this, + roomId = roomDetailArgs.roomId, + integId = null, + screen = screen + ) + } + private fun setupWidgetsBannerView() { roomWidgetsBannerView.callback = this } @@ -362,10 +373,7 @@ class RoomDetailFragment @Inject constructor( .setView(v) .setPositiveButton(R.string.yes) { _, _ -> // Open integration manager, to the sticker installation page - navigator.openIntegrationManager( - context = requireContext(), - roomId = roomDetailArgs.roomId, - integId = null, + openIntegrationManager( screen = WidgetType.StickerPicker.preferred ) } @@ -508,11 +516,7 @@ class RoomDetailFragment @Inject constructor( true } R.id.open_matrix_apps -> { - if (session.integrationManagerService().isIntegrationEnabled()) { - navigator.openIntegrationManager(requireContext(), roomDetailArgs.roomId, null, null) - } else { - displayDisabledIntegrationDialog() - } + roomDetailViewModel.handle(RoomDetailAction.OpenIntegrationManager) true } R.id.voice_call, @@ -645,16 +649,16 @@ class RoomDetailFragment @Inject constructor( val hasBeenHandled = attachmentsHelper.onActivityResult(requestCode, resultCode, data) if (!hasBeenHandled && resultCode == RESULT_OK && data != null) { when (requestCode) { - AttachmentsPreviewActivity.REQUEST_CODE -> { + AttachmentsPreviewActivity.REQUEST_CODE -> { val sendData = AttachmentsPreviewActivity.getOutput(data) val keepOriginalSize = AttachmentsPreviewActivity.getKeepOriginalSize(data) roomDetailViewModel.handle(RoomDetailAction.SendMedia(sendData, !keepOriginalSize)) } - REACTION_SELECT_REQUEST_CODE -> { + REACTION_SELECT_REQUEST_CODE -> { val (eventId, reaction) = EmojiReactionPickerActivity.getOutput(data) ?: return roomDetailViewModel.handle(RoomDetailAction.SendReaction(eventId, reaction)) } - StickerPickerConstants.STICKER_PICKER_REQUEST_CODE -> { + WidgetRequestCodes.STICKER_PICKER_REQUEST_CODE -> { val content = WidgetActivity.getOutput(data).toModel() ?: return roomDetailViewModel.handle(RoomDetailAction.SendSticker(content)) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt index 73ce95eda2..560da2e116 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt @@ -52,8 +52,12 @@ sealed class RoomDetailViewEvents : VectorViewEvents { object DisplayPromptForIntegrationManager: RoomDetailViewEvents() + object DisplayEnableIntegrationsWarning: RoomDetailViewEvents() + data class OpenStickerPicker(val widget: Widget): RoomDetailViewEvents() + object OpenIntegrationManager: RoomDetailViewEvents() + object MessageSent : SendMessageResult() data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult() class SlashCommandError(val command: Command) : SendMessageResult() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 4cbdd0b8d2..6d4f6578ca 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -81,7 +81,9 @@ import io.reactivex.Observable import io.reactivex.functions.BiFunction import io.reactivex.rxkotlin.subscribeBy import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.commonmark.parser.Parser import org.commonmark.renderer.html.HtmlRenderer import timber.log.Timber @@ -257,6 +259,7 @@ class RoomDetailViewModel @AssistedInject constructor( is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action) is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action) is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment() + is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager() is RoomDetailAction.StartCall -> handleStartCall(action) is RoomDetailAction.EndCall -> handleEndCall() }.exhaustive @@ -283,6 +286,19 @@ class RoomDetailViewModel @AssistedInject constructor( } } + private fun handleOpenIntegrationManager() { + viewModelScope.launch { + val viewEvent = withContext(Dispatchers.Default) { + if (isIntegrationEnabled()) { + RoomDetailViewEvents.OpenIntegrationManager + } else { + RoomDetailViewEvents.DisplayEnableIntegrationsWarning + } + } + _viewEvents.post(viewEvent) + } + } + private fun startTrackingUnreadMessages() { trackUnreadMessages.set(true) setState { copy(canShowJumpToReadMarker = false) } @@ -382,6 +398,8 @@ class RoomDetailViewModel @AssistedInject constructor( } } + private fun isIntegrationEnabled() = session.integrationManagerService().isIntegrationEnabled() + fun isMenuItemVisible(@IdRes itemId: Int) = when (itemId) { R.id.clear_message_queue -> // For now always disable when not in developer mode, worker cancellation is not working properly diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt index 33e18595a0..4616cb4b25 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt @@ -33,6 +33,7 @@ import com.airbnb.epoxy.EpoxyTouchHelperCallback import com.airbnb.epoxy.EpoxyViewHolder import timber.log.Timber import kotlin.math.abs +import kotlin.math.min class RoomMessageTouchHelperCallback(private val context: Context, @DrawableRes actionIcon: Int, @@ -92,7 +93,7 @@ class RoomMessageTouchHelperCallback(private val context: Context, setTouchListener(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) } val size = triggerDistance - if (Math.abs(viewHolder.itemView.translationX) < size || dX > this.dX /*going back*/) { + if (abs(viewHolder.itemView.translationX) < size || dX > this.dX /*going back*/) { super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) this.dX = dX startTracking = true @@ -127,9 +128,9 @@ class RoomMessageTouchHelperCallback(private val context: Context, private fun drawReplyButton(canvas: Canvas, itemView: View) { // Timber.v("drawReplyButton") - val translationX = Math.abs(itemView.translationX) + val translationX = abs(itemView.translationX) val newTime = System.currentTimeMillis() - val dt = Math.min(17, newTime - lastReplyButtonAnimationTime) + val dt = min(17, newTime - lastReplyButtonAnimationTime) lastReplyButtonAnimationTime = newTime val showing = translationX >= minShowDistance if (showing) { @@ -163,10 +164,10 @@ class RoomMessageTouchHelperCallback(private val context: Context, } else { 1.2f - 0.2f * ((replyButtonProgress - 0.8f) / 0.2f) } - alpha = Math.min(255f, 255 * (replyButtonProgress / 0.8f)).toInt() + alpha = min(255f, 255 * (replyButtonProgress / 0.8f)).toInt() } else { scale = replyButtonProgress - alpha = Math.min(255f, 255 * replyButtonProgress).toInt() + alpha = min(255f, 255 * replyButtonProgress).toInt() } imageDrawable.alpha = alpha diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/sticker/StickerPickerActionHandler.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/sticker/StickerPickerActionHandler.kt index 3b939892b5..ebae583d8c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/sticker/StickerPickerActionHandler.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/sticker/StickerPickerActionHandler.kt @@ -27,6 +27,10 @@ class StickerPickerActionHandler @Inject constructor(private val session: Sessio suspend fun handle(): RoomDetailViewEvents = withContext(Dispatchers.Default) { // Search for the sticker picker widget in the user account + val integrationsEnabled = session.integrationManagerService().isIntegrationEnabled() + if (!integrationsEnabled) { + return@withContext RoomDetailViewEvents.DisplayEnableIntegrationsWarning + } val stickerWidget = session.widgetService().getUserWidgets(WidgetType.StickerPicker.values()).firstOrNull { it.isActive } if (stickerWidget == null || stickerWidget.computedUrl.isNullOrBlank()) { RoomDetailViewEvents.DisplayPromptForIntegrationManager diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/PollResultLineView.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/PollResultLineView.kt index a0ad3466f7..c52b863658 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/PollResultLineView.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/PollResultLineView.kt @@ -64,8 +64,8 @@ class PollResultLineView @JvmOverloads constructor( set(value) { field = value // Text in main color - labelTextView.setTypeface(labelTextView.getTypeface(), if (value) Typeface.BOLD else Typeface.NORMAL) - percentTextView.setTypeface(percentTextView.getTypeface(), if (value) Typeface.BOLD else Typeface.NORMAL) + labelTextView.setTypeface(labelTextView.typeface, if (value) Typeface.BOLD else Typeface.NORMAL) + percentTextView.setTypeface(percentTextView.typeface, if (value) Typeface.BOLD else Typeface.NORMAL) } init { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/sticker/StickerPickerConstants.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/widget/WidgetRequestCodes.kt similarity index 82% rename from vector/src/main/java/im/vector/riotx/features/home/room/detail/sticker/StickerPickerConstants.kt rename to vector/src/main/java/im/vector/riotx/features/home/room/detail/widget/WidgetRequestCodes.kt index 8068eafc85..6fdfa598e4 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/sticker/StickerPickerConstants.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/widget/WidgetRequestCodes.kt @@ -14,8 +14,9 @@ * limitations under the License. */ -package im.vector.riotx.features.home.room.detail.sticker +package im.vector.riotx.features.home.room.detail.widget -object StickerPickerConstants { +object WidgetRequestCodes { const val STICKER_PICKER_REQUEST_CODE = 16000 + const val INTEGRATION_MANAGER_REQUEST_CODE = 16001 } diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt index 6a0094520a..a909e5becf 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt @@ -30,8 +30,8 @@ import androidx.fragment.app.Fragment import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.matrix.android.api.session.terms.TermsService -import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.session.widgets.model.Widget +import im.vector.matrix.android.api.util.MatrixItem import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.error.fatalError @@ -46,7 +46,7 @@ import im.vector.riotx.features.crypto.verification.VerificationBottomSheet import im.vector.riotx.features.debug.DebugMenuActivity import im.vector.riotx.features.home.room.detail.RoomDetailActivity import im.vector.riotx.features.home.room.detail.RoomDetailArgs -import im.vector.riotx.features.home.room.detail.sticker.StickerPickerConstants +import im.vector.riotx.features.home.room.detail.widget.WidgetRequestCodes import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity import im.vector.riotx.features.invite.InviteUsersToRoomActivity import im.vector.riotx.features.media.BigImageViewerActivity @@ -230,12 +230,13 @@ class DefaultNavigator @Inject constructor( override fun openStickerPicker(fragment: Fragment, roomId: String, widget: Widget, requestCode: Int) { val widgetArgs = widgetArgsBuilder.buildStickerPickerArgs(roomId, widget) val intent = WidgetActivity.newIntent(fragment.requireContext(), widgetArgs) - fragment.startActivityForResult(intent, StickerPickerConstants.STICKER_PICKER_REQUEST_CODE) + fragment.startActivityForResult(intent, WidgetRequestCodes.STICKER_PICKER_REQUEST_CODE) } - override fun openIntegrationManager(context: Context, roomId: String, integId: String?, screen: String?) { + override fun openIntegrationManager(fragment: Fragment, roomId: String, integId: String?, screen: String?) { val widgetArgs = widgetArgsBuilder.buildIntegrationManagerArgs(roomId, integId, screen) - context.startActivity(WidgetActivity.newIntent(context, widgetArgs)) + val intent = WidgetActivity.newIntent(fragment.requireContext(), widgetArgs) + fragment.startActivityForResult(intent, WidgetRequestCodes.INTEGRATION_MANAGER_REQUEST_CODE) } override fun openRoomWidget(context: Context, roomId: String, widget: Widget) { diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt index 35ace87b6b..916a46c041 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt @@ -25,7 +25,7 @@ import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.matrix.android.api.session.terms.TermsService import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.session.widgets.model.Widget -import im.vector.riotx.features.home.room.detail.sticker.StickerPickerConstants +import im.vector.riotx.features.home.room.detail.widget.WidgetRequestCodes import im.vector.riotx.features.media.ImageContentRenderer import im.vector.riotx.features.media.VideoContentRenderer import im.vector.riotx.features.settings.VectorSettingsActivity @@ -85,9 +85,9 @@ interface Navigator { fun openStickerPicker(fragment: Fragment, roomId: String, widget: Widget, - requestCode: Int = StickerPickerConstants.STICKER_PICKER_REQUEST_CODE) + requestCode: Int = WidgetRequestCodes.STICKER_PICKER_REQUEST_CODE) - fun openIntegrationManager(context: Context, roomId: String, integId: String?, screen: String?) + fun openIntegrationManager(fragment: Fragment, roomId: String, integId: String?, screen: String?) fun openRoomWidget(context: Context, roomId: String, widget: Widget) diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/widget/DotsView.kt b/vector/src/main/java/im/vector/riotx/features/reactions/widget/DotsView.kt index 292b17ce9f..a0dc4a4ae5 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/widget/DotsView.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/widget/DotsView.kt @@ -22,6 +22,8 @@ import android.graphics.Paint import android.util.AttributeSet import android.util.Property import android.view.View +import kotlin.math.cos +import kotlin.math.sin /** * This view will draw dots floating around the center of it's view @@ -84,16 +86,16 @@ class DotsView @JvmOverloads constructor(context: Context, attrs: AttributeSet? private fun drawOuterDotsFrame(canvas: Canvas) { for (i in 0 until DOTS_COUNT) { - val cX = (centerX + currentRadius1 * Math.cos(i.toDouble() * OUTER_DOTS_POSITION_ANGLE.toDouble() * Math.PI / 180)).toFloat() - val cY = (centerY + currentRadius1 * Math.sin(i.toDouble() * OUTER_DOTS_POSITION_ANGLE.toDouble() * Math.PI / 180)).toFloat() + val cX = (centerX + currentRadius1 * cos(i.toDouble() * OUTER_DOTS_POSITION_ANGLE.toDouble() * Math.PI / 180)).toFloat() + val cY = (centerY + currentRadius1 * sin(i.toDouble() * OUTER_DOTS_POSITION_ANGLE.toDouble() * Math.PI / 180)).toFloat() canvas.drawCircle(cX, cY, currentDotSize1, circlePaints[i % circlePaints.size]) } } private fun drawInnerDotsFrame(canvas: Canvas) { for (i in 0 until DOTS_COUNT) { - val cX = (centerX + currentRadius2 * Math.cos((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180)).toFloat() - val cY = (centerY + currentRadius2 * Math.sin((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180)).toFloat() + val cX = (centerX + currentRadius2 * cos((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180)).toFloat() + val cY = (centerY + currentRadius2 * sin((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180)).toFloat() canvas.drawCircle(cX, cY, currentDotSize2, circlePaints[(i + 1) % circlePaints.size]) } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt index 76065b63ea..dc8c17b08b 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -337,7 +337,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( * @param aMyDeviceInfo the device info */ private fun refreshCryptographyPreference(devices: List) { - showDeviceListPref.isEnabled = devices.size > 0 + showDeviceListPref.isEnabled = devices.isNotEmpty() showDeviceListPref.summary = resources.getQuantityString(R.plurals.settings_active_sessions_count, devices.size, devices.size) // val userId = session.myUserId // val deviceId = session.sessionParams.deviceId diff --git a/vector/src/main/java/im/vector/riotx/features/terms/ReviewTermsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/terms/ReviewTermsViewModel.kt index 69197b7b59..e19b8e7675 100644 --- a/vector/src/main/java/im/vector/riotx/features/terms/ReviewTermsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/terms/ReviewTermsViewModel.kt @@ -139,7 +139,7 @@ class ReviewTermsViewModel @AssistedInject constructor( ) } } catch (failure: Throwable) { - Timber.e(failure, "Failed to agree to terms") + Timber.e(failure, "Failed to load terms") setState { copy( termsList = Uninitialized diff --git a/vector/src/main/java/im/vector/riotx/features/widgets/WidgetFragment.kt b/vector/src/main/java/im/vector/riotx/features/widgets/WidgetFragment.kt index 14b25d0439..b162149ffe 100644 --- a/vector/src/main/java/im/vector/riotx/features/widgets/WidgetFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/widgets/WidgetFragment.kt @@ -40,6 +40,7 @@ import im.vector.riotx.R import im.vector.riotx.core.platform.OnBackPressed import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.utils.openUrlInExternalBrowser +import im.vector.riotx.features.home.room.detail.widget.WidgetRequestCodes import im.vector.riotx.features.terms.ReviewTermsActivity import im.vector.riotx.features.webview.WebViewEventListener import im.vector.riotx.features.widgets.webview.clearAfterWidget @@ -77,7 +78,7 @@ class WidgetFragment @Inject constructor() : VectorBaseFragment(), WebViewEventL Timber.v("Observed view events: $it") when (it) { is WidgetViewEvents.DisplayTerms -> displayTerms(it) - is WidgetViewEvents.LoadFormattedURL -> loadFormattedUrl(it) + is WidgetViewEvents.OnURLFormatted -> loadFormattedUrl(it) is WidgetViewEvents.DisplayIntegrationManager -> displayIntegrationManager(it) is WidgetViewEvents.Failure -> displayErrorDialog(it.throwable) } @@ -86,11 +87,17 @@ class WidgetFragment @Inject constructor() : VectorBaseFragment(), WebViewEventL } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == ReviewTermsActivity.TERMS_REQUEST_CODE) { - if (resultCode == Activity.RESULT_OK) { - viewModel.handle(WidgetAction.OnTermsReviewed) - } else { - vectorBaseActivity.finish() + when (requestCode) { + ReviewTermsActivity.TERMS_REQUEST_CODE -> { + Timber.v("On terms results") + if (resultCode == Activity.RESULT_OK) { + viewModel.handle(WidgetAction.OnTermsReviewed) + } else { + vectorBaseActivity.finish() + } + } + WidgetRequestCodes.INTEGRATION_MANAGER_REQUEST_CODE -> { + viewModel.handle(WidgetAction.LoadFormattedUrl) } } } @@ -139,7 +146,7 @@ class WidgetFragment @Inject constructor() : VectorBaseFragment(), WebViewEventL override fun onOptionsItemSelected(item: MenuItem): Boolean = withState(viewModel) { state -> when (item.itemId) { R.id.action_edit -> { - navigator.openIntegrationManager(requireContext(), state.roomId, state.widgetId, state.widgetKind.screenId) + navigator.openIntegrationManager(this, state.roomId, state.widgetId, state.widgetKind.screenId) return@withState true } R.id.action_delete -> { @@ -261,9 +268,9 @@ class WidgetFragment @Inject constructor() : VectorBaseFragment(), WebViewEventL ) } - private fun loadFormattedUrl(loadFormattedUrl: WidgetViewEvents.LoadFormattedURL) { + private fun loadFormattedUrl(event: WidgetViewEvents.OnURLFormatted) { widgetWebView.clearHistory() - widgetWebView.loadUrl(loadFormattedUrl.formattedURL) + widgetWebView.loadUrl(event.formattedURL) } private fun setStateError(message: String?) { @@ -280,7 +287,7 @@ class WidgetFragment @Inject constructor() : VectorBaseFragment(), WebViewEventL private fun displayIntegrationManager(event: WidgetViewEvents.DisplayIntegrationManager) { navigator.openIntegrationManager( - context = vectorBaseActivity, + fragment = this, roomId = fragmentArgs.roomId, integId = event.integId, screen = event.integType diff --git a/vector/src/main/java/im/vector/riotx/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/riotx/features/widgets/WidgetPostAPIHandler.kt index 351a15aad1..7115a2ea62 100644 --- a/vector/src/main/java/im/vector/riotx/features/widgets/WidgetPostAPIHandler.kt +++ b/vector/src/main/java/im/vector/riotx/features/widgets/WidgetPostAPIHandler.kt @@ -39,13 +39,12 @@ import java.util.ArrayList import java.util.HashMap class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roomId: String, - @Assisted private val navigationCallback: NavigationCallback, private val stringProvider: StringProvider, private val session: Session) : WidgetPostAPIMediator.Handler { @AssistedInject.Factory interface Factory { - fun create(roomId: String, navigationCallback: NavigationCallback): WidgetPostAPIHandler + fun create(roomId: String): WidgetPostAPIHandler } interface NavigationCallback { @@ -54,31 +53,31 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo fun openIntegrationManager(integId: String?, integType: String?) } - private val widgetPostAPIMediator = session.widgetService().getWidgetPostAPIMediator() private val room = session.getRoom(roomId)!! + var navigationCallback: NavigationCallback? = null - override fun handleWidgetRequest(eventData: JsonDict): Boolean { + override fun handleWidgetRequest(mediator: WidgetPostAPIMediator, eventData: JsonDict): Boolean { return when (eventData["action"] as String?) { "integration_manager_open" -> handleIntegrationManagerOpenAction(eventData).run { true } - "bot_options" -> getBotOptions(eventData).run { true } - "can_send_event" -> canSendEvent(eventData).run { true } + "bot_options" -> getBotOptions(mediator, eventData).run { true } + "can_send_event" -> canSendEvent(mediator, eventData).run { true } "close_scalar" -> handleCloseScalar().run { true } - "get_membership_count" -> getMembershipCount(eventData).run { true } - "get_widgets" -> getWidgets(eventData).run { true } - "invite" -> inviteUser(eventData).run { true } - "join_rules_state" -> getJoinRules(eventData).run { true } - "membership_state" -> getMembershipState(eventData).run { true } - "set_bot_options" -> setBotOptions(eventData).run { true } - "set_bot_power" -> setBotPower(eventData).run { true } - "set_plumbing_state" -> setPlumbingState(eventData).run { true } - "set_widget" -> setWidget(eventData).run { true } - "m.sticker" -> pickStickerData(eventData).run { true } + "get_membership_count" -> getMembershipCount(mediator, eventData).run { true } + "get_widgets" -> getWidgets(mediator, eventData).run { true } + "invite" -> inviteUser(mediator, eventData).run { true } + "join_rules_state" -> getJoinRules(mediator, eventData).run { true } + "membership_state" -> getMembershipState(mediator, eventData).run { true } + "set_bot_options" -> setBotOptions(mediator, eventData).run { true } + "set_bot_power" -> setBotPower(mediator, eventData).run { true } + "set_plumbing_state" -> setPlumbingState(mediator, eventData).run { true } + "set_widget" -> setWidget(mediator, eventData).run { true } + "m.sticker" -> pickStickerData(mediator, eventData).run { true } else -> false } } private fun handleCloseScalar() { - navigationCallback.close() + navigationCallback?.close() } private fun handleIntegrationManagerOpenAction(eventData: JsonDict) { @@ -101,7 +100,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo // Add "type_" as a prefix integType?.let { integType = "type_$integType" } } - navigationCallback.openIntegrationManager(integId, integType) + navigationCallback?.openIntegrationManager(integId, integType) } /** @@ -109,8 +108,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo * * @param eventData the modular data */ - private fun getBotOptions(eventData: JsonDict) { - if (checkRoomId(eventData) || checkUserId(eventData)) { + private fun getBotOptions(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { + if (checkRoomId(widgetPostAPIMediator, eventData) || checkUserId(widgetPostAPIMediator, eventData)) { return } val userId = eventData["user_id"] as String @@ -134,8 +133,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo } } - private fun canSendEvent(eventData: JsonDict) { - if (checkRoomId(eventData)) { + private fun canSendEvent(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { + if (checkRoomId(widgetPostAPIMediator, eventData)) { return } Timber.d("Received request canSendEvent in room $roomId") @@ -170,8 +169,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo * * @param eventData the modular data */ - private fun getMembershipState(eventData: JsonDict) { - if (checkRoomId(eventData) || checkUserId(eventData)) { + private fun getMembershipState(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { + if (checkRoomId(widgetPostAPIMediator, eventData) || checkUserId(widgetPostAPIMediator, eventData)) { return } val userId = eventData["user_id"] as String @@ -189,8 +188,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo * * @param eventData the modular data */ - private fun getJoinRules(eventData: JsonDict) { - if (checkRoomId(eventData)) { + private fun getJoinRules(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { + if (checkRoomId(widgetPostAPIMediator, eventData)) { return } Timber.d("Received request join rules in room $roomId") @@ -207,8 +206,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo * * @param eventData the modular data */ - private fun getWidgets(eventData: JsonDict) { - if (checkRoomId(eventData)) { + private fun getWidgets(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { + if (checkRoomId(widgetPostAPIMediator, eventData)) { return } Timber.d("Received request to get widget in room $roomId") @@ -227,12 +226,12 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo * * @param eventData the modular data */ - private fun setWidget(eventData: JsonDict) { + private fun setWidget(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { val userWidget = eventData["userWidget"] as Boolean? if (userWidget == true) { Timber.d("Received request to set widget for user") } else { - if (checkRoomId(eventData)) { + if (checkRoomId(widgetPostAPIMediator, eventData)) { return } Timber.d("Received request to set widget in room $roomId") @@ -283,14 +282,14 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo session.updateAccountData( type = UserAccountData.TYPE_WIDGETS, content = addUserWidgetBody, - callback = createWidgetAPICallback(eventData) + callback = createWidgetAPICallback(widgetPostAPIMediator, eventData) ) } else { session.widgetService().createRoomWidget( roomId = roomId, widgetId = widgetId, content = widgetEventContent, - callback = createWidgetAPICallback(eventData) + callback = createWidgetAPICallback(widgetPostAPIMediator, eventData) ) } } @@ -300,8 +299,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo * * @param eventData the modular data */ - private fun setPlumbingState(eventData: JsonDict) { - if (checkRoomId(eventData)) { + private fun setPlumbingState(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { + if (checkRoomId(widgetPostAPIMediator, eventData)) { return } val description = "Received request to set plumbing state to status " + eventData["status"] + " in room " + roomId + " requested" @@ -315,7 +314,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo eventType = EventType.PLUMBING, stateKey = null, body = params, - callback = createWidgetAPICallback(eventData) + callback = createWidgetAPICallback(widgetPostAPIMediator, eventData) ) } @@ -325,8 +324,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo * @param eventData the modular data */ @Suppress("UNCHECKED_CAST") - private fun setBotOptions(eventData: JsonDict) { - if (checkRoomId(eventData) || checkUserId(eventData)) { + private fun setBotOptions(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { + if (checkRoomId(widgetPostAPIMediator, eventData) || checkUserId(widgetPostAPIMediator, eventData)) { return } val userId = eventData["user_id"] as String @@ -338,7 +337,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo eventType = EventType.BOT_OPTIONS, stateKey = stateKey, body = content, - callback = createWidgetAPICallback(eventData) + callback = createWidgetAPICallback(widgetPostAPIMediator, eventData) ) } @@ -347,8 +346,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo * * @param eventData the modular data */ - private fun setBotPower(eventData: JsonDict) { - if (checkRoomId(eventData) || checkUserId(eventData)) { + private fun setBotPower(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { + if (checkRoomId(widgetPostAPIMediator, eventData) || checkUserId(widgetPostAPIMediator, eventData)) { return } val userId = eventData["user_id"] as String @@ -369,8 +368,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo * * @param eventData the modular data */ - private fun inviteUser(eventData: JsonDict) { - if (checkRoomId(eventData) || checkUserId(eventData)) { + private fun inviteUser(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { + if (checkRoomId(widgetPostAPIMediator, eventData) || checkUserId(widgetPostAPIMediator, eventData)) { return } val userId = eventData["user_id"] as String @@ -380,7 +379,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo if (member != null && member.membership == Membership.JOIN) { widgetPostAPIMediator.sendSuccess(eventData) } else { - room.invite(userId = userId, callback = createWidgetAPICallback(eventData)) + room.invite(userId = userId, callback = createWidgetAPICallback(widgetPostAPIMediator, eventData)) } } @@ -389,8 +388,8 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo * * @param eventData the modular data */ - private fun getMembershipCount(eventData: JsonDict) { - if (checkRoomId(eventData)) { + private fun getMembershipCount(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { + if (checkRoomId(widgetPostAPIMediator, eventData)) { return } val numberOfJoinedMembers = room.getNumberOfJoinedMembers() @@ -398,7 +397,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo } @Suppress("UNCHECKED_CAST") - private fun pickStickerData(eventData: JsonDict) { + private fun pickStickerData(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) { Timber.d("Received request send sticker") val data = eventData["data"] if (data == null) { @@ -411,7 +410,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo return } widgetPostAPIMediator.sendSuccess(eventData) - navigationCallback.closeWithResult(content) + navigationCallback?.closeWithResult(content) } /** @@ -420,7 +419,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo * * @return true in case of error */ - private fun checkRoomId(eventData: JsonDict): Boolean { + private fun checkRoomId(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict): Boolean { val roomIdInEvent = eventData["room_id"] as String? // Check if param is present if (null == roomIdInEvent) { @@ -443,7 +442,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo * * @return true in case of error */ - private fun checkUserId(eventData: JsonDict): Boolean { + private fun checkUserId(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict): Boolean { val userIdInEvent = eventData["user_id"] as String? // Check if param is present if (null == userIdInEvent) { @@ -454,7 +453,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo return false } - private fun createWidgetAPICallback(eventData: JsonDict): WidgetAPICallback { + private fun createWidgetAPICallback(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict): WidgetAPICallback { return WidgetAPICallback(widgetPostAPIMediator, eventData, stringProvider) } } diff --git a/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewEvents.kt index 7750f2dd68..5b40e0441d 100644 --- a/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewEvents.kt @@ -23,6 +23,6 @@ sealed class WidgetViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable): WidgetViewEvents() data class Close(val content: Content? = null) : WidgetViewEvents() data class DisplayIntegrationManager(val integId: String?, val integType: String?) : WidgetViewEvents() - data class LoadFormattedURL(val formattedURL: String) : WidgetViewEvents() + data class OnURLFormatted(val formattedURL: String) : WidgetViewEvents() data class DisplayTerms(val url: String, val token: String) : WidgetViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewModel.kt index d81e1efea4..d516137bc5 100644 --- a/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/widgets/WidgetViewModel.kt @@ -76,13 +76,22 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi private val integrationManagerService = session.integrationManagerService() private val widgetURLFormatter = widgetService.getWidgetURLFormatter() private val postAPIMediator = widgetService.getWidgetPostAPIMediator() + private var widgetPostAPIHandler: WidgetPostAPIHandler? = null + + // Flag to avoid infinite loop + private var canRefreshToken = true init { integrationManagerService.addListener(this) if (initialState.widgetKind.isAdmin()) { - val widgetPostAPIHandler = widgetPostAPIHandlerFactory.create(initialState.roomId, this) + widgetPostAPIHandler = widgetPostAPIHandlerFactory.create(initialState.roomId).apply { + navigationCallback = this@WidgetViewModel + } postAPIMediator.setHandler(widgetPostAPIHandler) } + if (!integrationManagerService.isIntegrationEnabled()) { + _viewEvents.post(WidgetViewEvents.Close(null)) + } setupName() refreshPermissionStatus() observePowerLevel() @@ -139,10 +148,10 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi is WidgetAction.OnWebViewLoadingError -> handleWebViewLoadingError(action) is WidgetAction.OnWebViewLoadingSuccess -> handleWebViewLoadingSuccess(action) is WidgetAction.OnWebViewStartedToLoad -> handleWebViewStartLoading() - WidgetAction.LoadFormattedUrl -> loadFormattedUrl() + WidgetAction.LoadFormattedUrl -> loadFormattedUrl(forceFetchToken = false) WidgetAction.DeleteWidget -> handleDeleteWidget() WidgetAction.RevokeWidget -> handleRevokeWidget() - WidgetAction.OnTermsReviewed -> refreshPermissionStatus() + WidgetAction.OnTermsReviewed -> loadFormattedUrl(forceFetchToken = false) } } @@ -224,10 +233,10 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi ) setState { copy(formattedURL = Success(formattedUrl)) } Timber.v("Post load formatted url event: $formattedUrl") - _viewEvents.post(WidgetViewEvents.LoadFormattedURL(formattedUrl)) + _viewEvents.post(WidgetViewEvents.OnURLFormatted(formattedUrl)) } catch (failure: Throwable) { if (failure is WidgetManagementFailure.TermsNotSignedException) { - _viewEvents.post(WidgetViewEvents.DisplayTerms(failure.baseUrl, failure.token)) + _viewEvents.post(WidgetViewEvents.DisplayTerms(initialState.baseUrl, failure.token)) } setState { copy(formattedURL = Fail(failure)) } } @@ -251,7 +260,8 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi } if (action.isHttpError) { // In case of 403, try to refresh the scalar token - if (it.formattedURL is Success && action.errorCode == HttpsURLConnection.HTTP_FORBIDDEN) { + if (it.formattedURL is Success && action.errorCode == HttpsURLConnection.HTTP_FORBIDDEN && canRefreshToken) { + canRefreshToken = false loadFormattedUrl(true) } } else { @@ -261,17 +271,24 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi override fun onCleared() { integrationManagerService.removeListener(this) + widgetPostAPIHandler?.navigationCallback = null postAPIMediator.setHandler(null) super.onCleared() } -// IntegrationManagerService.Listener + // IntegrationManagerService.Listener override fun onWidgetPermissionsChanged(widgets: Map) { refreshPermissionStatus() } -// WidgetPostAPIHandler.NavigationCallback + override fun onIsEnabledChanged(enabled: Boolean) { + if (!enabled) { + _viewEvents.post(WidgetViewEvents.Close(null)) + } + } + + // WidgetPostAPIHandler.NavigationCallback override fun close() { _viewEvents.post(WidgetViewEvents.Close(null)) diff --git a/vector/src/main/java/im/vector/riotx/features/widgets/webview/WebviewPermissionUtils.kt b/vector/src/main/java/im/vector/riotx/features/widgets/webview/WebviewPermissionUtils.kt index 32f6a906e2..fa0f4e0a97 100644 --- a/vector/src/main/java/im/vector/riotx/features/widgets/webview/WebviewPermissionUtils.kt +++ b/vector/src/main/java/im/vector/riotx/features/widgets/webview/WebviewPermissionUtils.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.fragments.roomwidgets +package im.vector.riotx.features.widgets.webview import android.annotation.SuppressLint import android.content.Context diff --git a/vector/src/main/java/im/vector/riotx/features/widgets/webview/WidgetWebView.kt b/vector/src/main/java/im/vector/riotx/features/widgets/webview/WidgetWebView.kt index 68cbe76531..6b5ade06c4 100644 --- a/vector/src/main/java/im/vector/riotx/features/widgets/webview/WidgetWebView.kt +++ b/vector/src/main/java/im/vector/riotx/features/widgets/webview/WidgetWebView.kt @@ -24,7 +24,6 @@ import android.webkit.PermissionRequest import android.webkit.WebChromeClient import android.webkit.WebSettings import android.webkit.WebView -import im.vector.fragments.roomwidgets.WebviewPermissionUtils import im.vector.riotx.R import im.vector.riotx.features.themes.ThemeUtils import im.vector.riotx.features.webview.VectorWebViewClient @@ -81,22 +80,10 @@ fun WebView.clearAfterWidget() { webChromeClient = null webViewClient = null clearHistory() - // NOTE: clears RAM cache, if you pass true, it will also clear the disk cache. clearCache(true) - // Loading a blank page is optional, but will ensure that the WebView isn't doing anything when you destroy it. loadUrl("about:blank") - - onPause() removeAllViews() - - // NOTE: This pauses JavaScript execution for ALL WebViews, - // do not use if you have other WebViews still alive. - // If you create another WebView after calling this, - // make sure to call mWebView.resumeTimers(). - pauseTimers() - - // NOTE: This can occasionally cause a segfault below API 17 (4.2) destroy() } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 9cb99223b5..9b03ef1ee5 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1181,6 +1181,7 @@ Read DRM protected Media + Unable to create widget. Failed to send request. Power level must be positive integer.