diff --git a/changelog.d/5767.bugfix b/changelog.d/5767.bugfix new file mode 100644 index 0000000000..2a34fbf4b3 --- /dev/null +++ b/changelog.d/5767.bugfix @@ -0,0 +1 @@ +Unignoring a user will perform an initial sync \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt index cd4fb216d3..063abdb5a0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt @@ -75,11 +75,14 @@ interface UserService { /** * Ignore users + * Note: once done, for the change to take effect, you have to request an initial sync. + * This may be improved in the future */ suspend fun ignoreUserIds(userIds: List) /** * Un-ignore some users + * Note: once done, for the change to take effect, you have to request an initial sync. */ suspend fun unIgnoreUserIds(userIds: List) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index a1163e0dd8..32f0e46eac 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -119,6 +119,8 @@ import im.vector.app.core.utils.startInstallFromSourceIntent import im.vector.app.core.utils.toast import im.vector.app.databinding.DialogReportContentBinding import im.vector.app.databinding.FragmentTimelineBinding +import im.vector.app.features.MainActivity +import im.vector.app.features.MainActivityArgs import im.vector.app.features.analytics.extensions.toAnalyticsInteraction import im.vector.app.features.analytics.plan.Interaction import im.vector.app.features.analytics.plan.MobileScreen @@ -136,6 +138,7 @@ import im.vector.app.features.call.conference.ConferenceEventObserver import im.vector.app.features.call.conference.JitsiCallViewModel import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.command.Command +import im.vector.app.features.command.ParsedCommand import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivity import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.home.AvatarRenderer @@ -438,6 +441,7 @@ class TimelineFragment @Inject constructor( messageComposerViewModel.observeViewEvents { when (it) { is MessageComposerViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it) + is MessageComposerViewEvents.SlashCommandConfirmationRequest -> handleSlashCommandConfirmationRequest(it) is MessageComposerViewEvents.SendMessageResult -> renderSendMessageResult(it) is MessageComposerViewEvents.ShowMessage -> showSnackWithMessage(it.message) is MessageComposerViewEvents.ShowRoomUpgradeDialog -> handleShowRoomUpgradeDialog(it) @@ -496,6 +500,25 @@ class TimelineFragment @Inject constructor( } } + private fun handleSlashCommandConfirmationRequest(action: MessageComposerViewEvents.SlashCommandConfirmationRequest) { + when (action.parsedCommand) { + is ParsedCommand.UnignoreUser -> promptUnignoreUser(action.parsedCommand) + else -> TODO("Add case for ${action.parsedCommand.javaClass.simpleName}") + } + lockSendButton = false + } + + private fun promptUnignoreUser(command: ParsedCommand.UnignoreUser) { + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.room_participants_action_unignore_title) + .setMessage(getString(R.string.settings_unignore_user, command.userId)) + .setPositiveButton(R.string.unignore) { _, _ -> + messageComposerViewModel.handle(MessageComposerAction.SlashCommandConfirmed(command)) + } + .setNegativeButton(R.string.action_cancel, null) + .show() + } + private fun renderVoiceMessageMode(content: String) { ContentAttachmentData.fromJsonString(content)?.let { audioAttachmentData -> views.voiceMessageRecorderView.isVisible = true @@ -1686,9 +1709,7 @@ class TimelineFragment @Inject constructor( displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command)) } is MessageComposerViewEvents.SlashCommandResultOk -> { - dismissLoadingDialog() - views.composerLayout.setTextIfDifferent("") - sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) } + handleSlashCommandResultOk(sendMessageResult.parsedCommand) } is MessageComposerViewEvents.SlashCommandResultError -> { dismissLoadingDialog() @@ -1705,6 +1726,21 @@ class TimelineFragment @Inject constructor( lockSendButton = false } + private fun handleSlashCommandResultOk(parsedCommand: ParsedCommand) { + dismissLoadingDialog() + views.composerLayout.setTextIfDifferent("") + when (parsedCommand) { + is ParsedCommand.SetMarkdown -> { + showSnackWithMessage(getString(if (parsedCommand.enable) R.string.markdown_has_been_enabled else R.string.markdown_has_been_disabled)) + } + is ParsedCommand.UnignoreUser -> { + // A user has been un-ignored, perform a initial sync + MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCache = true)) + } + else -> Unit + } + } + private fun displayCommandError(message: String) { MaterialAlertDialogBuilder(requireActivity()) .setTitle(R.string.command_error) @@ -2418,23 +2454,23 @@ class TimelineFragment @Inject constructor( } private fun displayThreadsBetaOptInDialog() { - activity?.let { - MaterialAlertDialogBuilder(it) - .setTitle(R.string.threads_beta_enable_notice_title) - .setMessage(threadsManager.getBetaEnableThreadsMessage()) - .setCancelable(true) - .setNegativeButton(R.string.action_not_now) { _, _ -> } - .setPositiveButton(R.string.action_try_it_out) { _, _ -> - threadsManager.enableThreadsAndRestart(it) - } - .show() - ?.findViewById(android.R.id.message) - ?.apply { - linksClickable = true - movementMethod = LinkMovementMethod.getInstance() - } - } + activity?.let { + MaterialAlertDialogBuilder(it) + .setTitle(R.string.threads_beta_enable_notice_title) + .setMessage(threadsManager.getBetaEnableThreadsMessage()) + .setCancelable(true) + .setNegativeButton(R.string.action_not_now) { _, _ -> } + .setPositiveButton(R.string.action_try_it_out) { _, _ -> + threadsManager.enableThreadsAndRestart(it) + } + .show() + ?.findViewById(android.R.id.message) + ?.apply { + linksClickable = true + movementMethod = LinkMovementMethod.getInstance() + } } + } /** * Navigate to Threads list for the current room diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt index dca698ee52..0da324ffc2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt @@ -17,6 +17,7 @@ package im.vector.app.features.home.room.detail.composer import im.vector.app.core.platform.VectorViewModelAction +import im.vector.app.features.command.ParsedCommand import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent @@ -30,6 +31,7 @@ sealed class MessageComposerAction : VectorViewModelAction { data class UserIsTyping(val isTyping: Boolean) : MessageComposerAction() data class OnTextChanged(val text: CharSequence) : MessageComposerAction() data class OnEntersBackground(val composerText: String) : MessageComposerAction() + data class SlashCommandConfirmed(val parsedCommand: ParsedCommand) : MessageComposerAction() // Voice Message data class InitializeVoiceRecorder(val attachmentData: ContentAttachmentData) : MessageComposerAction() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewEvents.kt index c1af838795..e1f6923d21 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewEvents.kt @@ -16,9 +16,9 @@ package im.vector.app.features.home.room.detail.composer -import androidx.annotation.StringRes import im.vector.app.core.platform.VectorViewEvents import im.vector.app.features.command.Command +import im.vector.app.features.command.ParsedCommand sealed class MessageComposerViewEvents : VectorViewEvents { @@ -30,13 +30,14 @@ sealed class MessageComposerViewEvents : VectorViewEvents { object MessageSent : SendMessageResult() data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult() - class SlashCommandError(val command: Command) : SendMessageResult() - class SlashCommandUnknown(val command: String) : SendMessageResult() - class SlashCommandNotSupportedInThreads(val command: Command) : SendMessageResult() - data class SlashCommandHandled(@StringRes val messageRes: Int? = null) : SendMessageResult() + data class SlashCommandError(val command: Command) : SendMessageResult() + data class SlashCommandUnknown(val command: String) : SendMessageResult() + data class SlashCommandNotSupportedInThreads(val command: Command) : SendMessageResult() object SlashCommandLoading : SendMessageResult() - data class SlashCommandResultOk(@StringRes val messageRes: Int? = null) : SendMessageResult() - class SlashCommandResultError(val throwable: Throwable) : SendMessageResult() + data class SlashCommandResultOk(val parsedCommand: ParsedCommand) : SendMessageResult() + data class SlashCommandResultError(val throwable: Throwable) : SendMessageResult() + + data class SlashCommandConfirmationRequest(val parsedCommand: ParsedCommand) : MessageComposerViewEvents() data class OpenRoomMemberProfile(val userId: String) : MessageComposerViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index aabc319ee2..9c81a39941 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -110,6 +110,7 @@ class MessageComposerViewModel @AssistedInject constructor( is MessageComposerAction.VoiceWaveformTouchedUp -> handleVoiceWaveformTouchedUp(action) is MessageComposerAction.VoiceWaveformMovedTo -> handleVoiceWaveformMovedTo(action) is MessageComposerAction.AudioSeekBarMovedTo -> handleAudioSeekBarMovedTo(action) + is MessageComposerAction.SlashCommandConfirmed -> handleSlashCommandConfirmed(action) } } @@ -195,7 +196,7 @@ class MessageComposerViewModel @AssistedInject constructor( } when (state.sendMode) { is SendMode.Regular -> { - when (val slashCommandResult = commandParser.parseSlashCommand( + when (val parsedCommand = commandParser.parseSlashCommand( textMessage = action.text, isInThreadTimeline = state.isInThreadTimeline())) { is ParsedCommand.ErrorNotACommand -> { @@ -213,118 +214,117 @@ class MessageComposerViewModel @AssistedInject constructor( popDraft() } is ParsedCommand.ErrorSyntax -> { - _viewEvents.post(MessageComposerViewEvents.SlashCommandError(slashCommandResult.command)) + _viewEvents.post(MessageComposerViewEvents.SlashCommandError(parsedCommand.command)) } is ParsedCommand.ErrorEmptySlashCommand -> { _viewEvents.post(MessageComposerViewEvents.SlashCommandUnknown("/")) } is ParsedCommand.ErrorUnknownSlashCommand -> { - _viewEvents.post(MessageComposerViewEvents.SlashCommandUnknown(slashCommandResult.slashCommand)) + _viewEvents.post(MessageComposerViewEvents.SlashCommandUnknown(parsedCommand.slashCommand)) } is ParsedCommand.ErrorCommandNotSupportedInThreads -> { - _viewEvents.post(MessageComposerViewEvents.SlashCommandNotSupportedInThreads(slashCommandResult.command)) + _viewEvents.post(MessageComposerViewEvents.SlashCommandNotSupportedInThreads(parsedCommand.command)) } is ParsedCommand.SendPlainText -> { // Send the text message to the room, without markdown if (state.rootThreadEventId != null) { room.replyInThread( rootThreadEventId = state.rootThreadEventId, - replyInThreadText = slashCommandResult.message, + replyInThreadText = parsedCommand.message, autoMarkdown = false) } else { - room.sendTextMessage(slashCommandResult.message, autoMarkdown = false) + room.sendTextMessage(parsedCommand.message, autoMarkdown = false) } _viewEvents.post(MessageComposerViewEvents.MessageSent) popDraft() } is ParsedCommand.ChangeRoomName -> { - handleChangeRoomNameSlashCommand(slashCommandResult) + handleChangeRoomNameSlashCommand(parsedCommand) } is ParsedCommand.Invite -> { - handleInviteSlashCommand(slashCommandResult) + handleInviteSlashCommand(parsedCommand) } is ParsedCommand.Invite3Pid -> { - handleInvite3pidSlashCommand(slashCommandResult) + handleInvite3pidSlashCommand(parsedCommand) } is ParsedCommand.SetUserPowerLevel -> { - handleSetUserPowerLevel(slashCommandResult) + handleSetUserPowerLevel(parsedCommand) } is ParsedCommand.ClearScalarToken -> { // TODO _viewEvents.post(MessageComposerViewEvents.SlashCommandNotImplemented) } is ParsedCommand.SetMarkdown -> { - vectorPreferences.setMarkdownEnabled(slashCommandResult.enable) - _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk( - if (slashCommandResult.enable) R.string.markdown_has_been_enabled else R.string.markdown_has_been_disabled)) + vectorPreferences.setMarkdownEnabled(parsedCommand.enable) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) popDraft() } is ParsedCommand.BanUser -> { - handleBanSlashCommand(slashCommandResult) + handleBanSlashCommand(parsedCommand) } is ParsedCommand.UnbanUser -> { - handleUnbanSlashCommand(slashCommandResult) + handleUnbanSlashCommand(parsedCommand) } is ParsedCommand.IgnoreUser -> { - handleIgnoreSlashCommand(slashCommandResult) + handleIgnoreSlashCommand(parsedCommand) } is ParsedCommand.UnignoreUser -> { - handleUnignoreSlashCommand(slashCommandResult) + handleUnignoreSlashCommand(parsedCommand) } is ParsedCommand.RemoveUser -> { - handleRemoveSlashCommand(slashCommandResult) + handleRemoveSlashCommand(parsedCommand) } is ParsedCommand.JoinRoom -> { - handleJoinToAnotherRoomSlashCommand(slashCommandResult) + handleJoinToAnotherRoomSlashCommand(parsedCommand) popDraft() } is ParsedCommand.PartRoom -> { - handlePartSlashCommand(slashCommandResult) + handlePartSlashCommand(parsedCommand) } is ParsedCommand.SendEmote -> { if (state.rootThreadEventId != null) { room.replyInThread( rootThreadEventId = state.rootThreadEventId, - replyInThreadText = slashCommandResult.message, + replyInThreadText = parsedCommand.message, msgType = MessageType.MSGTYPE_EMOTE, autoMarkdown = action.autoMarkdown) } else { - room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE, autoMarkdown = action.autoMarkdown) + room.sendTextMessage(parsedCommand.message, msgType = MessageType.MSGTYPE_EMOTE, autoMarkdown = action.autoMarkdown) } - _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) popDraft() } is ParsedCommand.SendRainbow -> { - val message = slashCommandResult.message.toString() + val message = parsedCommand.message.toString() if (state.rootThreadEventId != null) { room.replyInThread( rootThreadEventId = state.rootThreadEventId, - replyInThreadText = slashCommandResult.message, + replyInThreadText = parsedCommand.message, formattedText = rainbowGenerator.generate(message)) } else { room.sendFormattedTextMessage(message, rainbowGenerator.generate(message)) } - _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) popDraft() } is ParsedCommand.SendRainbowEmote -> { - val message = slashCommandResult.message.toString() + val message = parsedCommand.message.toString() if (state.rootThreadEventId != null) { room.replyInThread( rootThreadEventId = state.rootThreadEventId, - replyInThreadText = slashCommandResult.message, + replyInThreadText = parsedCommand.message, msgType = MessageType.MSGTYPE_EMOTE, formattedText = rainbowGenerator.generate(message)) } else { room.sendFormattedTextMessage(message, rainbowGenerator.generate(message), MessageType.MSGTYPE_EMOTE) } - _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) popDraft() } is ParsedCommand.SendSpoiler -> { - val text = "[${stringProvider.getString(R.string.spoiler)}](${slashCommandResult.message})" - val formattedText = "${slashCommandResult.message}" + val text = "[${stringProvider.getString(R.string.spoiler)}](${parsedCommand.message})" + val formattedText = "${parsedCommand.message}" if (state.rootThreadEventId != null) { room.replyInThread( rootThreadEventId = state.rootThreadEventId, @@ -335,51 +335,51 @@ class MessageComposerViewModel @AssistedInject constructor( text, formattedText) } - _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) popDraft() } is ParsedCommand.SendShrug -> { - sendPrefixedMessage("¯\\_(ツ)_/¯", slashCommandResult.message, state.rootThreadEventId) - _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) + sendPrefixedMessage("¯\\_(ツ)_/¯", parsedCommand.message, state.rootThreadEventId) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) popDraft() } is ParsedCommand.SendLenny -> { - sendPrefixedMessage("( ͡° ͜ʖ ͡°)", slashCommandResult.message, state.rootThreadEventId) - _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) + sendPrefixedMessage("( ͡° ͜ʖ ͡°)", parsedCommand.message, state.rootThreadEventId) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) popDraft() } is ParsedCommand.SendChatEffect -> { - sendChatEffect(slashCommandResult) - _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) + sendChatEffect(parsedCommand) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) popDraft() } is ParsedCommand.ChangeTopic -> { - handleChangeTopicSlashCommand(slashCommandResult) + handleChangeTopicSlashCommand(parsedCommand) } is ParsedCommand.ChangeDisplayName -> { - handleChangeDisplayNameSlashCommand(slashCommandResult) + handleChangeDisplayNameSlashCommand(parsedCommand) } is ParsedCommand.ChangeDisplayNameForRoom -> { - handleChangeDisplayNameForRoomSlashCommand(slashCommandResult) + handleChangeDisplayNameForRoomSlashCommand(parsedCommand) } is ParsedCommand.ChangeRoomAvatar -> { - handleChangeRoomAvatarSlashCommand(slashCommandResult) + handleChangeRoomAvatarSlashCommand(parsedCommand) } is ParsedCommand.ChangeAvatarForRoom -> { - handleChangeAvatarForRoomSlashCommand(slashCommandResult) + handleChangeAvatarForRoomSlashCommand(parsedCommand) } is ParsedCommand.ShowUser -> { - _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) - handleWhoisSlashCommand(slashCommandResult) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) + handleWhoisSlashCommand(parsedCommand) popDraft() } is ParsedCommand.DiscardSession -> { if (room.isEncrypted()) { session.cryptoService().discardOutboundSession(room.roomId) - _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) popDraft() } else { - _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) _viewEvents.post( MessageComposerViewEvents .ShowMessage(stringProvider.getString(R.string.command_description_discard_session_not_handled)) @@ -391,8 +391,8 @@ class MessageComposerViewModel @AssistedInject constructor( viewModelScope.launch(Dispatchers.IO) { try { val params = CreateSpaceParams().apply { - name = slashCommandResult.name - invitedUserIds.addAll(slashCommandResult.invitees) + name = parsedCommand.name + invitedUserIds.addAll(parsedCommand.invitees) } val spaceId = session.spaceService().createSpace(params) session.spaceService().getSpace(spaceId) @@ -403,7 +403,7 @@ class MessageComposerViewModel @AssistedInject constructor( true ) popDraft() - _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) } catch (failure: Throwable) { _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure)) } @@ -414,7 +414,7 @@ class MessageComposerViewModel @AssistedInject constructor( _viewEvents.post(MessageComposerViewEvents.SlashCommandLoading) viewModelScope.launch(Dispatchers.IO) { try { - session.spaceService().getSpace(slashCommandResult.spaceId) + session.spaceService().getSpace(parsedCommand.spaceId) ?.addChildren( room.roomId, null, @@ -422,7 +422,7 @@ class MessageComposerViewModel @AssistedInject constructor( false ) popDraft() - _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) } catch (failure: Throwable) { _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure)) } @@ -433,9 +433,9 @@ class MessageComposerViewModel @AssistedInject constructor( _viewEvents.post(MessageComposerViewEvents.SlashCommandLoading) viewModelScope.launch(Dispatchers.IO) { try { - session.spaceService().joinSpace(slashCommandResult.spaceIdOrAlias) + session.spaceService().joinSpace(parsedCommand.spaceIdOrAlias) popDraft() - _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) } catch (failure: Throwable) { _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure)) } @@ -445,9 +445,9 @@ class MessageComposerViewModel @AssistedInject constructor( is ParsedCommand.LeaveRoom -> { viewModelScope.launch(Dispatchers.IO) { try { - session.leaveRoom(slashCommandResult.roomId) + session.leaveRoom(parsedCommand.roomId) popDraft() - _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) } catch (failure: Throwable) { _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure)) } @@ -457,11 +457,11 @@ class MessageComposerViewModel @AssistedInject constructor( is ParsedCommand.UpgradeRoom -> { _viewEvents.post( MessageComposerViewEvents.ShowRoomUpgradeDialog( - slashCommandResult.newVersion, + parsedCommand.newVersion, room.roomSummary()?.isPublic ?: false ) ) - _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) popDraft() } } @@ -644,19 +644,19 @@ class MessageComposerViewModel @AssistedInject constructor( } private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) { - launchSlashCommandFlowSuspendable { + launchSlashCommandFlowSuspendable(changeTopic) { room.updateTopic(changeTopic.topic) } } private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) { - launchSlashCommandFlowSuspendable { + launchSlashCommandFlowSuspendable(invite) { room.invite(invite.userId, invite.reason) } } private fun handleInvite3pidSlashCommand(invite: ParsedCommand.Invite3Pid) { - launchSlashCommandFlowSuspendable { + launchSlashCommandFlowSuspendable(invite) { room.invite3pid(invite.threePid) } } @@ -669,19 +669,19 @@ class MessageComposerViewModel @AssistedInject constructor( ?.toContent() ?: return - launchSlashCommandFlowSuspendable { + launchSlashCommandFlowSuspendable(setUserPowerLevel) { room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent) } } private fun handleChangeDisplayNameSlashCommand(changeDisplayName: ParsedCommand.ChangeDisplayName) { - launchSlashCommandFlowSuspendable { + launchSlashCommandFlowSuspendable(changeDisplayName) { session.setDisplayName(session.myUserId, changeDisplayName.displayName) } } private fun handlePartSlashCommand(command: ParsedCommand.PartRoom) { - launchSlashCommandFlowSuspendable { + launchSlashCommandFlowSuspendable(command) { if (command.roomAlias == null) { // Leave the current room room @@ -697,25 +697,25 @@ class MessageComposerViewModel @AssistedInject constructor( } private fun handleRemoveSlashCommand(removeUser: ParsedCommand.RemoveUser) { - launchSlashCommandFlowSuspendable { + launchSlashCommandFlowSuspendable(removeUser) { room.remove(removeUser.userId, removeUser.reason) } } private fun handleBanSlashCommand(ban: ParsedCommand.BanUser) { - launchSlashCommandFlowSuspendable { + launchSlashCommandFlowSuspendable(ban) { room.ban(ban.userId, ban.reason) } } private fun handleUnbanSlashCommand(unban: ParsedCommand.UnbanUser) { - launchSlashCommandFlowSuspendable { + launchSlashCommandFlowSuspendable(unban) { room.unban(unban.userId, unban.reason) } } private fun handleChangeRoomNameSlashCommand(changeRoomName: ParsedCommand.ChangeRoomName) { - launchSlashCommandFlowSuspendable { + launchSlashCommandFlowSuspendable(changeRoomName) { room.updateName(changeRoomName.name) } } @@ -727,7 +727,7 @@ class MessageComposerViewModel @AssistedInject constructor( } private fun handleChangeDisplayNameForRoomSlashCommand(changeDisplayName: ParsedCommand.ChangeDisplayNameForRoom) { - launchSlashCommandFlowSuspendable { + launchSlashCommandFlowSuspendable(changeDisplayName) { getMyRoomMemberContent() ?.copy(displayName = changeDisplayName.displayName) ?.toContent() @@ -738,13 +738,13 @@ class MessageComposerViewModel @AssistedInject constructor( } private fun handleChangeRoomAvatarSlashCommand(changeAvatar: ParsedCommand.ChangeRoomAvatar) { - launchSlashCommandFlowSuspendable { + launchSlashCommandFlowSuspendable(changeAvatar) { room.sendStateEvent(EventType.STATE_ROOM_AVATAR, stateKey = "", RoomAvatarContent(changeAvatar.url).toContent()) } } private fun handleChangeAvatarForRoomSlashCommand(changeAvatar: ParsedCommand.ChangeAvatarForRoom) { - launchSlashCommandFlowSuspendable { + launchSlashCommandFlowSuspendable(changeAvatar) { getMyRoomMemberContent() ?.copy(avatarUrl = changeAvatar.url) ?.toContent() @@ -755,13 +755,24 @@ class MessageComposerViewModel @AssistedInject constructor( } private fun handleIgnoreSlashCommand(ignore: ParsedCommand.IgnoreUser) { - launchSlashCommandFlowSuspendable { + launchSlashCommandFlowSuspendable(ignore) { session.ignoreUserIds(listOf(ignore.userId)) } } private fun handleUnignoreSlashCommand(unignore: ParsedCommand.UnignoreUser) { - launchSlashCommandFlowSuspendable { + _viewEvents.post(MessageComposerViewEvents.SlashCommandConfirmationRequest(unignore)) + } + + private fun handleSlashCommandConfirmed(action: MessageComposerAction.SlashCommandConfirmed) { + when (action.parsedCommand) { + is ParsedCommand.UnignoreUser -> handleUnignoreSlashCommandConfirmed(action.parsedCommand) + else -> TODO("Not handled yet") + } + } + + private fun handleUnignoreSlashCommandConfirmed(unignore: ParsedCommand.UnignoreUser) { + launchSlashCommandFlowSuspendable(unignore) { session.unIgnoreUserIds(listOf(unignore.userId)) } } @@ -900,13 +911,13 @@ class MessageComposerViewModel @AssistedInject constructor( } } - private fun launchSlashCommandFlowSuspendable(block: suspend () -> Unit) { + private fun launchSlashCommandFlowSuspendable(parsedCommand: ParsedCommand, block: suspend () -> Unit) { _viewEvents.post(MessageComposerViewEvents.SlashCommandLoading) viewModelScope.launch { val event = try { block() popDraft() - MessageComposerViewEvents.SlashCommandResultOk() + MessageComposerViewEvents.SlashCommandResultOk(parsedCommand) } catch (failure: Throwable) { MessageComposerViewEvents.SlashCommandResultError(failure) } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt index 760bbe9353..5fee420b21 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -47,6 +47,8 @@ import im.vector.app.databinding.DialogBaseEditTextBinding import im.vector.app.databinding.DialogShareQrCodeBinding import im.vector.app.databinding.FragmentMatrixProfileBinding import im.vector.app.databinding.ViewStubRoomMemberProfileHeaderBinding +import im.vector.app.features.MainActivity +import im.vector.app.features.MainActivityArgs import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.displayname.getBestName @@ -131,13 +133,20 @@ class RoomMemberProfileFragment @Inject constructor( is RoomMemberProfileViewEvents.OnKickActionSuccess -> Unit is RoomMemberProfileViewEvents.OnSetPowerLevelSuccess -> Unit is RoomMemberProfileViewEvents.OnBanActionSuccess -> Unit - is RoomMemberProfileViewEvents.OnIgnoreActionSuccess -> Unit + is RoomMemberProfileViewEvents.OnIgnoreActionSuccess -> handleOnIgnoreActionSuccess(it) is RoomMemberProfileViewEvents.OnInviteActionSuccess -> Unit } } setupLongClicks() } + private fun handleOnIgnoreActionSuccess(action: RoomMemberProfileViewEvents.OnIgnoreActionSuccess) { + if (action.shouldPerformInitialSync) { + // A user has been un-ignored, perform a initial sync + MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCache = true)) + } + } + private fun setupLongClicks() { headerViews.memberProfileNameView.copyOnLongClick() headerViews.memberProfileIdView.copyOnLongClick() diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewEvents.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewEvents.kt index efe23eeff0..1857f5cdc4 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewEvents.kt @@ -25,7 +25,7 @@ sealed class RoomMemberProfileViewEvents : VectorViewEvents { data class Loading(val message: CharSequence? = null) : RoomMemberProfileViewEvents() data class Failure(val throwable: Throwable) : RoomMemberProfileViewEvents() - object OnIgnoreActionSuccess : RoomMemberProfileViewEvents() + data class OnIgnoreActionSuccess(val shouldPerformInitialSync: Boolean) : RoomMemberProfileViewEvents() object OnSetPowerLevelSuccess : RoomMemberProfileViewEvents() object OnInviteActionSuccess : RoomMemberProfileViewEvents() object OnKickActionSuccess : RoomMemberProfileViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index 4bcf9ef55d..addc2c71ef 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -390,7 +390,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor( } else { session.ignoreUserIds(listOf(state.userId)) } - RoomMemberProfileViewEvents.OnIgnoreActionSuccess + RoomMemberProfileViewEvents.OnIgnoreActionSuccess(isIgnored) } catch (failure: Throwable) { RoomMemberProfileViewEvents.Failure(failure) } diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersAction.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersAction.kt new file mode 100644 index 0000000000..48199e557b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersAction.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.ignored + +import im.vector.app.core.platform.VectorViewModelAction + +sealed class IgnoredUsersAction : VectorViewModelAction { + data class UnIgnore(val userId: String) : IgnoredUsersAction() +} diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewEvents.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewEvents.kt index 2b2c3eb49d..8d597a9189 100644 --- a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewEvents.kt @@ -25,4 +25,5 @@ import im.vector.app.core.platform.VectorViewEvents sealed class IgnoredUsersViewEvents : VectorViewEvents { data class Loading(val message: CharSequence? = null) : IgnoredUsersViewEvents() data class Failure(val throwable: Throwable) : IgnoredUsersViewEvents() + object Success : IgnoredUsersViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt index b2a7b2cbd1..14f7cd9230 100644 --- a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt @@ -16,37 +16,21 @@ package im.vector.app.features.settings.ignored -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksViewModelFactory -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.platform.VectorViewModelAction import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.flow.flow -data class IgnoredUsersViewState( - val ignoredUsers: List = emptyList(), - val unIgnoreRequest: Async = Uninitialized -) : MavericksState - -sealed class IgnoredUsersAction : VectorViewModelAction { - data class UnIgnore(val userId: String) : IgnoredUsersAction() -} - -class IgnoredUsersViewModel @AssistedInject constructor(@Assisted initialState: IgnoredUsersViewState, - private val session: Session) : - VectorViewModel(initialState) { +class IgnoredUsersViewModel @AssistedInject constructor( + @Assisted initialState: IgnoredUsersViewState, + private val session: Session +) : VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -76,20 +60,16 @@ class IgnoredUsersViewModel @AssistedInject constructor(@Assisted initialState: } private fun handleUnIgnore(action: IgnoredUsersAction.UnIgnore) { - setState { - copy( - unIgnoreRequest = Loading() - ) - } - + setState { copy(isLoading = true) } viewModelScope.launch { - val result = runCatching { session.unIgnoreUserIds(listOf(action.userId)) } - setState { - copy( - unIgnoreRequest = result.fold(::Success, ::Fail) - ) + val viewEvent = try { + session.unIgnoreUserIds(listOf(action.userId)) + IgnoredUsersViewEvents.Success + } catch (throwable: Throwable) { + IgnoredUsersViewEvents.Failure(throwable) } - result.onFailure { _viewEvents.post(IgnoredUsersViewEvents.Failure(it)) } + setState { copy(isLoading = false) } + _viewEvents.post(viewEvent) } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewState.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewState.kt new file mode 100644 index 0000000000..3dc1bfe795 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewState.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.ignored + +import com.airbnb.mvrx.MavericksState +import org.matrix.android.sdk.api.session.user.model.User + +data class IgnoredUsersViewState( + val ignoredUsers: List = emptyList(), + val isLoading: Boolean = false +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt index 7128639c81..66fa690b82 100644 --- a/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt @@ -22,8 +22,6 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.Loading import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -32,6 +30,8 @@ import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentGenericRecyclerBinding +import im.vector.app.features.MainActivity +import im.vector.app.features.MainActivityArgs import im.vector.app.features.analytics.plan.MobileScreen import javax.inject.Inject @@ -62,10 +62,16 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( when (it) { is IgnoredUsersViewEvents.Loading -> showLoading(it.message) is IgnoredUsersViewEvents.Failure -> showFailure(it.throwable) + IgnoredUsersViewEvents.Success -> handleSuccess() } } } + private fun handleSuccess() { + // A user has been un-ignored, perform a initial sync + MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCache = true)) + } + override fun onDestroyView() { ignoredUsersController.callback = null views.genericRecyclerView.cleanup() @@ -80,11 +86,12 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( override fun onUserIdClicked(userId: String) { MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.room_participants_action_unignore_title) .setMessage(getString(R.string.settings_unignore_user, userId)) - .setPositiveButton(R.string.yes) { _, _ -> + .setPositiveButton(R.string.unignore) { _, _ -> viewModel.handle(IgnoredUsersAction.UnIgnore(userId)) } - .setNegativeButton(R.string.no, null) + .setNegativeButton(R.string.action_cancel, null) .show() } @@ -94,14 +101,6 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( override fun invalidate() = withState(viewModel) { state -> ignoredUsersController.update(state) - - handleUnIgnoreRequestStatus(state.unIgnoreRequest) - } - - private fun handleUnIgnoreRequestStatus(unIgnoreRequest: Async) { - views.waitingView.root.isVisible = when (unIgnoreRequest) { - is Loading -> true - else -> false - } + views.waitingView.root.isVisible = state.isLoading } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 551983637c..ffd4333071 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -634,7 +634,7 @@ Ignore Unignore user - Unignoring this user will show all messages from them again. + Unignoring this user will show all messages from them again.\n\nNote that this action will restart the app and it may take some time. Unignore Cancel invite