diff --git a/CHANGES.md b/CHANGES.md index 7e386e01945..60950d9d876 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,7 @@ Improvements 🙌: - Other changes: - - + - Add reason for all membership events (https://github.com/matrix-org/matrix-doc/pull/2367) Bugfix 🐛: - When automardown is ON, pills are sent as MD in body (#739) @@ -17,7 +17,7 @@ Translations 🗣: - Build 🧱: - - + - "ban" event are not rendered correctly (#716) Changes in RiotX 0.9.1 (2019-12-05) =================================================== diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt index 6793d6249d6..bc0a8661175 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt @@ -57,8 +57,9 @@ class RxRoom(private val room: Room) { room.loadRoomMembersIfNeeded(MatrixCallbackSingle(it)).toSingle(it) } - fun joinRoom(viaServers: List = emptyList()): Single = Single.create { - room.join(viaServers, MatrixCallbackSingle(it)).toSingle(it) + fun joinRoom(reason: String? = null, + viaServers: List = emptyList()): Single = Single.create { + room.join(reason, viaServers, MatrixCallbackSingle(it)).toSingle(it) } fun liveEventReadReceipts(eventId: String): Observable> { diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt index 1964d05a1b9..5a42dbb8049 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt @@ -76,8 +76,10 @@ class RxSession(private val session: Session) { session.searchUsersDirectory(search, limit, excludedUserIds, MatrixCallbackSingle(it)).toSingle(it) } - fun joinRoom(roomId: String, viaServers: List = emptyList()): Single = Single.create { - session.joinRoom(roomId, viaServers, MatrixCallbackSingle(it)).toSingle(it) + fun joinRoom(roomId: String, + reason: String? = null, + viaServers: List = emptyList()): Single = Single.create { + session.joinRoom(roomId, reason, viaServers, MatrixCallbackSingle(it)).toSingle(it) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt index 930320d9766..c0e413f83b3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt @@ -30,12 +30,16 @@ interface RoomDirectoryService { /** * Get rooms from directory */ - fun getPublicRooms(server: String?, publicRoomsParams: PublicRoomsParams, callback: MatrixCallback): Cancelable + fun getPublicRooms(server: String?, + publicRoomsParams: PublicRoomsParams, + callback: MatrixCallback): Cancelable /** * Join a room by id */ - fun joinRoom(roomId: String, callback: MatrixCallback): Cancelable + fun joinRoom(roomId: String, + reason: String? = null, + callback: MatrixCallback): Cancelable /** * Fetches the overall metadata about protocols supported by the homeserver. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt index 2dbb580a4fe..98abce5898d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt @@ -30,14 +30,17 @@ interface RoomService { /** * Create a room asynchronously */ - fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback): Cancelable + fun createRoom(createRoomParams: CreateRoomParams, + callback: MatrixCallback): Cancelable /** * Join a room by id * @param roomId the roomId of the room to join + * @param reason optional reason for joining the room * @param viaServers the servers to attempt to join the room through. One of the servers must be participating in the room. */ fun joinRoom(roomId: String, + reason: String? = null, viaServers: List = emptyList(), callback: MatrixCallback): Cancelable @@ -69,5 +72,6 @@ interface RoomService { /** * Mark all rooms as read */ - fun markAllAsRead(roomIds: List, callback: MatrixCallback): Cancelable + fun markAllAsRead(roomIds: List, + callback: MatrixCallback): Cancelable } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt index 8d60bee9da8..b750c5347e5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt @@ -52,16 +52,21 @@ interface MembershipService { /** * Invite a user in the room */ - fun invite(userId: String, callback: MatrixCallback): Cancelable + fun invite(userId: String, + reason: String? = null, + callback: MatrixCallback): Cancelable /** * Join the room, or accept an invitation. */ - fun join(viaServers: List = emptyList(), callback: MatrixCallback): Cancelable + fun join(reason: String? = null, + viaServers: List = emptyList(), + callback: MatrixCallback): Cancelable /** * Leave the room, or reject an invitation. */ - fun leave(callback: MatrixCallback): Cancelable + fun leave(reason: String? = null, + callback: MatrixCallback): Cancelable } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt index aa73727685a..6a4d8e3c94a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt @@ -26,9 +26,13 @@ import im.vector.matrix.android.api.session.events.model.UnsignedData @JsonClass(generateAdapter = true) data class RoomMember( @Json(name = "membership") val membership: Membership, + @Json(name = "reason") val reason: String? = null, @Json(name = "displayname") val displayName: String? = null, @Json(name = "avatar_url") val avatarUrl: String? = null, @Json(name = "is_direct") val isDirect: Boolean = false, @Json(name = "third_party_invite") val thirdPartyInvite: Invite? = null, @Json(name = "unsigned") val unsignedData: UnsignedData? = null -) +) { + val safeReason + get() = reason?.takeIf { it.isNotBlank() } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt index 4251a66304f..711e2bd97ce 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt @@ -44,9 +44,9 @@ internal class DefaultRoomDirectoryService @Inject constructor(private val getPu .executeBy(taskExecutor) } - override fun joinRoom(roomId: String, callback: MatrixCallback): Cancelable { + override fun joinRoom(roomId: String, reason: String?, callback: MatrixCallback): Cancelable { return joinRoomTask - .configureWith(JoinRoomTask.Params(roomId)) { + .configureWith(JoinRoomTask.Params(roomId, reason)) { this.callback = callback } .executeBy(taskExecutor) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt index be2a588510f..22caf76eaf7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt @@ -96,9 +96,9 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona .executeBy(taskExecutor) } - override fun joinRoom(roomId: String, viaServers: List, callback: MatrixCallback): Cancelable { + override fun joinRoom(roomId: String, reason: String?, viaServers: List, callback: MatrixCallback): Cancelable { return joinRoomTask - .configureWith(JoinRoomTask.Params(roomId, viaServers)) { + .configureWith(JoinRoomTask.Params(roomId, reason, viaServers)) { this.callback = callback } .executeBy(taskExecutor) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt index 797dbed31c9..40164d16978 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt @@ -217,7 +217,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/join") fun join(@Path("roomId") roomId: String, @Query("server_name") viaServers: List, - @Body params: Map): Call + @Body params: Map): Call /** * Leave the given room. @@ -227,7 +227,7 @@ internal interface RoomAPI { */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/leave") fun leave(@Path("roomId") roomId: String, - @Body params: Map): Call + @Body params: Map): Call /** * Strips all information out of an event which isn't critical to the integrity of the server-side representation of the room. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt index 3490fed30f0..00c1c2c4ca5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt @@ -83,8 +83,8 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr return result } - override fun invite(userId: String, callback: MatrixCallback): Cancelable { - val params = InviteTask.Params(roomId, userId) + override fun invite(userId: String, reason: String?, callback: MatrixCallback): Cancelable { + val params = InviteTask.Params(roomId, userId, reason) return inviteTask .configureWith(params) { this.callback = callback @@ -92,8 +92,8 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr .executeBy(taskExecutor) } - override fun join(viaServers: List, callback: MatrixCallback): Cancelable { - val params = JoinRoomTask.Params(roomId, viaServers) + override fun join(reason: String?, viaServers: List, callback: MatrixCallback): Cancelable { + val params = JoinRoomTask.Params(roomId, reason, viaServers) return joinTask .configureWith(params) { this.callback = callback @@ -101,8 +101,8 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr .executeBy(taskExecutor) } - override fun leave(callback: MatrixCallback): Cancelable { - val params = LeaveRoomTask.Params(roomId) + override fun leave(reason: String?, callback: MatrixCallback): Cancelable { + val params = LeaveRoomTask.Params(roomId, reason) return leaveRoomTask .configureWith(params) { this.callback = callback diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteBody.kt index 4529a17ab80..2d721971985 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteBody.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteBody.kt @@ -21,5 +21,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class InviteBody( - @Json(name = "user_id") val userId: String + @Json(name = "user_id") val userId: String, + @Json(name = "reason") val reason: String? ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt index a41e8d3ca39..6bc453a0f3c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt @@ -24,7 +24,8 @@ import javax.inject.Inject internal interface InviteTask : Task { data class Params( val roomId: String, - val userId: String + val userId: String, + val reason: String? ) } @@ -32,7 +33,7 @@ internal class DefaultInviteTask @Inject constructor(private val roomAPI: RoomAP override suspend fun execute(params: InviteTask.Params) { return executeRequest { - val body = InviteBody(params.userId) + val body = InviteBody(params.userId, params.reason) apiCall = roomAPI.invite(params.roomId, body) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt index 2555d802098..7304c09d579 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt @@ -32,6 +32,7 @@ import javax.inject.Inject internal interface JoinRoomTask : Task { data class Params( val roomId: String, + val reason: String?, val viaServers: List = emptyList() ) } @@ -43,7 +44,7 @@ internal class DefaultJoinRoomTask @Inject constructor(private val roomAPI: Room override suspend fun execute(params: JoinRoomTask.Params) { executeRequest { - apiCall = roomAPI.join(params.roomId, params.viaServers, HashMap()) + apiCall = roomAPI.join(params.roomId, params.viaServers, mapOf("reason" to params.reason)) } val roomId = params.roomId // Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt index be9a421e956..01198c47de6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt @@ -23,7 +23,8 @@ import javax.inject.Inject internal interface LeaveRoomTask : Task { data class Params( - val roomId: String + val roomId: String, + val reason: String? ) } @@ -31,7 +32,7 @@ internal class DefaultLeaveRoomTask @Inject constructor(private val roomAPI: Roo override suspend fun execute(params: LeaveRoomTask.Params) { return executeRequest { - apiCall = roomAPI.leave(params.roomId, HashMap()) + apiCall = roomAPI.leave(params.roomId, mapOf("reason" to params.reason)) } } } diff --git a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml index a588bb36fd6..a22533c6d13 100644 --- a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml +++ b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml @@ -2,8 +2,19 @@ - - + %1$s\'s invitation. Reason: %2$s + %1$s invited %2$s. Reason: %3$s + %1$s invited you. Reason: %2$s + %1$s joined. Reason: %2$s + %1$s left. Reason: %2$s + %1$s rejected the invitation. Reason: %2$s + %1$s kicked %2$s. Reason: %3$s + %1$s unbanned %2$s. Reason: %3$s + %1$s banned %2$s. Reason: %3$s + %1$s sent an invitation to %2$s to join the room. Reason: %3$s + %1$s revoked the invitation for %2$s to join the room. Reason: %3$s + %1$s accepted the invitation for %2$s. Reason: %3$s + %1$s withdrew %2$s\'s invitation. Reason: %3$s There is no network connection right now \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/command/Command.kt b/vector/src/main/java/im/vector/riotx/features/command/Command.kt index 7d745b925b1..8b72ffa4a6e 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/Command.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/Command.kt @@ -27,16 +27,19 @@ import im.vector.riotx.R enum class Command(val command: String, val parameters: String, @StringRes val description: Int) { EMOTE("/me", "", R.string.command_description_emote), BAN_USER("/ban", " [reason]", R.string.command_description_ban_user), - UNBAN_USER("/unban", "", R.string.command_description_unban_user), + UNBAN_USER("/unban", " [reason]", R.string.command_description_unban_user), SET_USER_POWER_LEVEL("/op", " []", R.string.command_description_op_user), RESET_USER_POWER_LEVEL("/deop", "", R.string.command_description_deop_user), - INVITE("/invite", "", R.string.command_description_invite_user), - JOIN_ROOM("/join", "", R.string.command_description_join_room), - PART("/part", "", R.string.command_description_part_room), + INVITE("/invite", " [reason]", R.string.command_description_invite_user), + JOIN_ROOM("/join", " [reason]", R.string.command_description_join_room), + PART("/part", " [reason]", R.string.command_description_part_room), TOPIC("/topic", "", R.string.command_description_topic), KICK_USER("/kick", " [reason]", R.string.command_description_kick_user), CHANGE_DISPLAY_NAME("/nick", "", R.string.command_description_nick), MARKDOWN("/markdown", "", R.string.command_description_markdown), CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token), SPOILER("/spoiler", "", R.string.command_description_spoiler); + + val length + get() = command.length + 1 } diff --git a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt index bc451f8e844..359f2c1f138 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt @@ -81,29 +81,52 @@ object CommandParser { ParsedCommand.SendEmote(message) } Command.JOIN_ROOM.command -> { - val roomAlias = textMessage.substring(Command.JOIN_ROOM.command.length).trim() - - if (roomAlias.isNotEmpty()) { - ParsedCommand.JoinRoom(roomAlias) + if (messageParts.size >= 2) { + val roomAlias = messageParts[1] + + if (roomAlias.isNotEmpty()) { + ParsedCommand.JoinRoom( + roomAlias, + textMessage.substring(Command.JOIN_ROOM.length + roomAlias.length) + .trim() + .takeIf { it.isNotBlank() } + ) + } else { + ParsedCommand.ErrorSyntax(Command.JOIN_ROOM) + } } else { ParsedCommand.ErrorSyntax(Command.JOIN_ROOM) } } Command.PART.command -> { - val roomAlias = textMessage.substring(Command.PART.command.length).trim() - - if (roomAlias.isNotEmpty()) { - ParsedCommand.PartRoom(roomAlias) + if (messageParts.size >= 2) { + val roomAlias = messageParts[1] + + if (roomAlias.isNotEmpty()) { + ParsedCommand.PartRoom( + roomAlias, + textMessage.substring(Command.PART.length + roomAlias.length) + .trim() + .takeIf { it.isNotBlank() } + ) + } else { + ParsedCommand.ErrorSyntax(Command.PART) + } } else { ParsedCommand.ErrorSyntax(Command.PART) } } Command.INVITE.command -> { - if (messageParts.size == 2) { + if (messageParts.size >= 2) { val userId = messageParts[1] if (MatrixPatterns.isUserId(userId)) { - ParsedCommand.Invite(userId) + ParsedCommand.Invite( + userId, + textMessage.substring(Command.INVITE.length + userId.length) + .trim() + .takeIf { it.isNotBlank() } + ) } else { ParsedCommand.ErrorSyntax(Command.INVITE) } @@ -114,12 +137,14 @@ object CommandParser { Command.KICK_USER.command -> { if (messageParts.size >= 2) { val userId = messageParts[1] - if (MatrixPatterns.isUserId(userId)) { - val reason = textMessage.substring(Command.KICK_USER.command.length - + 1 - + userId.length).trim() - ParsedCommand.KickUser(userId, reason) + if (MatrixPatterns.isUserId(userId)) { + ParsedCommand.KickUser( + userId, + textMessage.substring(Command.KICK_USER.length + userId.length) + .trim() + .takeIf { it.isNotBlank() } + ) } else { ParsedCommand.ErrorSyntax(Command.KICK_USER) } @@ -130,12 +155,14 @@ object CommandParser { Command.BAN_USER.command -> { if (messageParts.size >= 2) { val userId = messageParts[1] - if (MatrixPatterns.isUserId(userId)) { - val reason = textMessage.substring(Command.BAN_USER.command.length - + 1 - + userId.length).trim() - ParsedCommand.BanUser(userId, reason) + if (MatrixPatterns.isUserId(userId)) { + ParsedCommand.BanUser( + userId, + textMessage.substring(Command.BAN_USER.length + userId.length) + .trim() + .takeIf { it.isNotBlank() } + ) } else { ParsedCommand.ErrorSyntax(Command.BAN_USER) } @@ -144,11 +171,16 @@ object CommandParser { } } Command.UNBAN_USER.command -> { - if (messageParts.size == 2) { + if (messageParts.size >= 2) { val userId = messageParts[1] if (MatrixPatterns.isUserId(userId)) { - ParsedCommand.UnbanUser(userId) + ParsedCommand.UnbanUser( + userId, + textMessage.substring(Command.UNBAN_USER.length + userId.length) + .trim() + .takeIf { it.isNotBlank() } + ) } else { ParsedCommand.ErrorSyntax(Command.UNBAN_USER) } diff --git a/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt b/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt index 89438c8a9dd..dd7c0c7e865 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt @@ -34,14 +34,14 @@ sealed class ParsedCommand { // Valid commands: class SendEmote(val message: CharSequence) : ParsedCommand() - class BanUser(val userId: String, val reason: String) : ParsedCommand() - class UnbanUser(val userId: String) : ParsedCommand() + class BanUser(val userId: String, val reason: String?) : ParsedCommand() + class UnbanUser(val userId: String, val reason: String?) : ParsedCommand() class SetUserPowerLevel(val userId: String, val powerLevel: Int) : ParsedCommand() - class Invite(val userId: String) : ParsedCommand() - class JoinRoom(val roomAlias: String) : ParsedCommand() - class PartRoom(val roomAlias: String) : ParsedCommand() + class Invite(val userId: String, val reason: String?) : ParsedCommand() + class JoinRoom(val roomAlias: String, val reason: String?) : ParsedCommand() + class PartRoom(val roomAlias: String, val reason: String?) : ParsedCommand() class ChangeTopic(val topic: String) : ParsedCommand() - class KickUser(val userId: String, val reason: String) : ParsedCommand() + class KickUser(val userId: String, val reason: String?) : ParsedCommand() class ChangeDisplayName(val displayName: String) : ParsedCommand() class SetMarkdown(val enable: Boolean) : ParsedCommand() object ClearScalarToken : ParsedCommand() 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 d50b0c9f68e..19ba1a85ce2 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 @@ -1181,7 +1181,7 @@ class RoomDetailFragment @Inject constructor( && userId == session.myUserId) { // Empty composer, current user: start an emote composerLayout.composerEditText.setText(Command.EMOTE.command + " ") - composerLayout.composerEditText.setSelection(Command.EMOTE.command.length + 1) + composerLayout.composerEditText.setSelection(Command.EMOTE.length) } else { val roomMember = roomDetailViewModel.getMember(userId) // TODO move logic outside of fragment 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 d0bff31f790..00a31db455a 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 @@ -200,9 +200,10 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro invisibleEventsObservable.accept(action) } - fun getMember(userId: String) : RoomMember? { - return room.getRoomMember(userId) + fun getMember(userId: String): RoomMember? { + return room.getRoomMember(userId) } + /** * Convert a send mode to a draft and save the draft */ @@ -266,7 +267,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } } session.rx() - .joinRoom(roomId, viaServer) + .joinRoom(roomId, viaServers = viaServer) .map { roomId } .execute { copy(tombstoneEventHandling = it) @@ -487,7 +488,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) { _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) - room.invite(invite.userId, object : MatrixCallback { + room.invite(invite.userId, invite.reason, object : MatrixCallback { override fun onSuccess(data: Unit) { _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultOk) } @@ -553,7 +554,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } private fun handleRejectInvite() { - room.leave(object : MatrixCallback {}) + room.leave(null, object : MatrixCallback {}) } private fun handleAcceptInvite() { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index a3910664a2a..75100e6c038 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -19,14 +19,7 @@ package im.vector.riotx.features.home.room.detail.timeline.format import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.Membership -import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility -import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent -import im.vector.matrix.android.api.session.room.model.RoomJoinRules -import im.vector.matrix.android.api.session.room.model.RoomJoinRulesContent -import im.vector.matrix.android.api.session.room.model.RoomMember -import im.vector.matrix.android.api.session.room.model.RoomNameContent -import im.vector.matrix.android.api.session.room.model.RoomTopicContent +import im.vector.matrix.android.api.session.room.model.* import im.vector.matrix.android.api.session.room.model.call.CallInviteContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotx.R @@ -36,7 +29,7 @@ import timber.log.Timber import javax.inject.Inject class NoticeEventFormatter @Inject constructor(private val sessionHolder: ActiveSessionHolder, - private val stringProvider: StringProvider) { + private val sp: StringProvider) { fun format(timelineEvent: TimelineEvent): CharSequence? { return when (val type = timelineEvent.root.getClearType()) { @@ -84,36 +77,35 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active private fun formatRoomNameEvent(event: Event, senderName: String?): CharSequence? { val content = event.getClearContent().toModel() ?: return null return if (content.name.isNullOrBlank()) { - stringProvider.getString(R.string.notice_room_name_removed, senderName) + sp.getString(R.string.notice_room_name_removed, senderName) } else { - stringProvider.getString(R.string.notice_room_name_changed, senderName, content.name) + sp.getString(R.string.notice_room_name_changed, senderName, content.name) } } private fun formatRoomTombstoneEvent(senderName: String?): CharSequence? { - return stringProvider.getString(R.string.notice_room_update, senderName) + return sp.getString(R.string.notice_room_update, senderName) } private fun formatRoomTopicEvent(event: Event, senderName: String?): CharSequence? { val content = event.getClearContent().toModel() ?: return null return if (content.topic.isNullOrEmpty()) { - stringProvider.getString(R.string.notice_room_topic_removed, senderName) + sp.getString(R.string.notice_room_topic_removed, senderName) } else { - stringProvider.getString(R.string.notice_room_topic_changed, senderName, content.topic) + sp.getString(R.string.notice_room_topic_changed, senderName, content.topic) } } private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? { - val historyVisibility = event.getClearContent().toModel()?.historyVisibility - ?: return null + val historyVisibility = event.getClearContent().toModel()?.historyVisibility ?: return null val formattedVisibility = when (historyVisibility) { - RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared) - RoomHistoryVisibility.INVITED -> stringProvider.getString(R.string.notice_room_visibility_invited) - RoomHistoryVisibility.JOINED -> stringProvider.getString(R.string.notice_room_visibility_joined) - RoomHistoryVisibility.WORLD_READABLE -> stringProvider.getString(R.string.notice_room_visibility_world_readable) + RoomHistoryVisibility.SHARED -> sp.getString(R.string.notice_room_visibility_shared) + RoomHistoryVisibility.INVITED -> sp.getString(R.string.notice_room_visibility_invited) + RoomHistoryVisibility.JOINED -> sp.getString(R.string.notice_room_visibility_joined) + RoomHistoryVisibility.WORLD_READABLE -> sp.getString(R.string.notice_room_visibility_world_readable) } - return stringProvider.getString(R.string.notice_made_future_room_visibility, senderName, formattedVisibility) + return sp.getString(R.string.notice_made_future_room_visibility, senderName, formattedVisibility) } private fun formatCallEvent(event: Event, senderName: String?): CharSequence? { @@ -122,13 +114,13 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active val content = event.getClearContent().toModel() ?: return null val isVideoCall = content.offer.sdp == CallInviteContent.Offer.SDP_VIDEO return if (isVideoCall) { - stringProvider.getString(R.string.notice_placed_video_call, senderName) + sp.getString(R.string.notice_placed_video_call, senderName) } else { - stringProvider.getString(R.string.notice_placed_voice_call, senderName) + sp.getString(R.string.notice_placed_voice_call, senderName) } } - EventType.CALL_ANSWER == event.type -> stringProvider.getString(R.string.notice_answered_call, senderName) - EventType.CALL_HANGUP == event.type -> stringProvider.getString(R.string.notice_ended_call, senderName) + EventType.CALL_ANSWER == event.type -> sp.getString(R.string.notice_answered_call, senderName) + EventType.CALL_HANGUP == event.type -> sp.getString(R.string.notice_ended_call, senderName) else -> null } } @@ -150,12 +142,11 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active if (eventContent?.displayName != prevEventContent?.displayName) { val displayNameText = when { prevEventContent?.displayName.isNullOrEmpty() -> - stringProvider.getString(R.string.notice_display_name_set, event.senderId, eventContent?.displayName) + sp.getString(R.string.notice_display_name_set, event.senderId, eventContent?.displayName) eventContent?.displayName.isNullOrEmpty() -> - stringProvider.getString(R.string.notice_display_name_removed, event.senderId, prevEventContent?.displayName) + sp.getString(R.string.notice_display_name_removed, event.senderId, prevEventContent?.displayName) else -> - stringProvider.getString(R.string.notice_display_name_changed_from, - event.senderId, prevEventContent?.displayName, eventContent?.displayName) + sp.getString(R.string.notice_display_name_changed_from, event.senderId, prevEventContent?.displayName, eventContent?.displayName) } displayText.append(displayNameText) } @@ -163,73 +154,96 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active if (eventContent?.avatarUrl != prevEventContent?.avatarUrl) { val displayAvatarText = if (displayText.isNotEmpty()) { displayText.append(" ") - stringProvider.getString(R.string.notice_avatar_changed_too) + sp.getString(R.string.notice_avatar_changed_too) } else { - stringProvider.getString(R.string.notice_avatar_url_changed, senderName) + sp.getString(R.string.notice_avatar_url_changed, senderName) } displayText.append(displayAvatarText) } if (displayText.isEmpty()) { displayText.append( - stringProvider.getString(R.string.notice_member_no_changes, senderName) + sp.getString(R.string.notice_member_no_changes, senderName) ) } return displayText.toString() } private fun buildMembershipNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String? { - val senderDisplayName = senderName ?: event.senderId - val targetDisplayName = eventContent?.displayName ?: prevEventContent?.displayName ?: "" - return when { - Membership.INVITE == eventContent?.membership -> { + val senderDisplayName = senderName ?: event.senderId ?: "" + val targetDisplayName = eventContent?.displayName ?: prevEventContent?.displayName ?: event.stateKey ?: "" + return when (eventContent?.membership) { + Membership.INVITE -> { val selfUserId = sessionHolder.getSafeActiveSession()?.myUserId when { eventContent.thirdPartyInvite != null -> { - val userWhoHasAccepted = eventContent.thirdPartyInvite?.signed?.mxid - ?: event.stateKey - stringProvider.getString(R.string.notice_room_third_party_registered_invite, - userWhoHasAccepted, eventContent.thirdPartyInvite?.displayName) + val userWhoHasAccepted = eventContent.thirdPartyInvite?.signed?.mxid ?: event.stateKey + val threePidDisplayName = eventContent.thirdPartyInvite?.displayName ?: "" + eventContent.safeReason?.let { reason -> + sp.getString(R.string.notice_room_third_party_registered_invite_with_reason, userWhoHasAccepted, threePidDisplayName, reason) + } ?: sp.getString(R.string.notice_room_third_party_registered_invite, userWhoHasAccepted, threePidDisplayName) } event.stateKey == selfUserId -> - stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName) + eventContent.safeReason?.let { reason -> + sp.getString(R.string.notice_room_invite_you_with_reason, senderDisplayName, reason) + } ?: sp.getString(R.string.notice_room_invite_you, senderDisplayName) event.stateKey.isNullOrEmpty() -> - stringProvider.getString(R.string.notice_room_invite_no_invitee, senderDisplayName) + eventContent.safeReason?.let { reason -> + sp.getString(R.string.notice_room_invite_no_invitee_with_reason, senderDisplayName, reason) + } ?: sp.getString(R.string.notice_room_invite_no_invitee, senderDisplayName) else -> - stringProvider.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName) + eventContent.safeReason?.let { reason -> + sp.getString(R.string.notice_room_invite_with_reason, senderDisplayName, targetDisplayName, reason) + } ?: sp.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName) } } - Membership.JOIN == eventContent?.membership -> - stringProvider.getString(R.string.notice_room_join, senderDisplayName) - Membership.LEAVE == eventContent?.membership -> + Membership.JOIN -> + eventContent.safeReason?.let { reason -> + sp.getString(R.string.notice_room_join_with_reason, senderDisplayName, reason) + } ?: sp.getString(R.string.notice_room_join, senderDisplayName) + Membership.LEAVE -> // 2 cases here: this member may have left voluntarily or they may have been "left" by someone else ie. kicked - return if (event.senderId == event.stateKey) { + if (event.senderId == event.stateKey) { if (prevEventContent?.membership == Membership.INVITE) { - stringProvider.getString(R.string.notice_room_reject, senderDisplayName) + eventContent.safeReason?.let { reason -> + sp.getString(R.string.notice_room_reject_with_reason, senderDisplayName, reason) + } ?: sp.getString(R.string.notice_room_reject, senderDisplayName) } else { - stringProvider.getString(R.string.notice_room_leave, senderDisplayName) + eventContent.safeReason?.let { reason -> + sp.getString(R.string.notice_room_leave_with_reason, senderDisplayName, reason) + } ?: sp.getString(R.string.notice_room_leave, senderDisplayName) } } else if (prevEventContent?.membership == Membership.INVITE) { - stringProvider.getString(R.string.notice_room_withdraw, senderDisplayName, targetDisplayName) + eventContent.safeReason?.let { reason -> + sp.getString(R.string.notice_room_withdraw_with_reason, senderDisplayName, targetDisplayName, reason) + } ?: sp.getString(R.string.notice_room_withdraw, senderDisplayName, targetDisplayName) } else if (prevEventContent?.membership == Membership.JOIN) { - stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) + eventContent.safeReason?.let { reason -> + sp.getString(R.string.notice_room_kick_with_reason, senderDisplayName, targetDisplayName, reason) + } ?: sp.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) } else if (prevEventContent?.membership == Membership.BAN) { - stringProvider.getString(R.string.notice_room_unban, senderDisplayName, targetDisplayName) + eventContent.safeReason?.let { reason -> + sp.getString(R.string.notice_room_unban_with_reason, senderDisplayName, targetDisplayName, reason) + } ?: sp.getString(R.string.notice_room_unban, senderDisplayName, targetDisplayName) } else { null } - Membership.BAN == eventContent?.membership -> - stringProvider.getString(R.string.notice_room_ban, senderDisplayName, targetDisplayName) - Membership.KNOCK == eventContent?.membership -> - stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) - else -> null + Membership.BAN -> + eventContent.safeReason?.let { + sp.getString(R.string.notice_room_ban_with_reason, senderDisplayName, targetDisplayName, it) + } ?: sp.getString(R.string.notice_room_ban, senderDisplayName, targetDisplayName) + Membership.KNOCK -> + eventContent.safeReason?.let { reason -> + sp.getString(R.string.notice_room_kick_with_reason, senderDisplayName, targetDisplayName, reason) + } ?: sp.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) + else -> null } } private fun formatJoinRulesEvent(event: Event, senderName: String?): CharSequence? { val content = event.getClearContent().toModel() ?: return null return when (content.joinRules) { - RoomJoinRules.INVITE -> stringProvider.getString(R.string.room_join_rules_invite, senderName) - RoomJoinRules.PUBLIC -> stringProvider.getString(R.string.room_join_rules_public, senderName) + RoomJoinRules.INVITE -> sp.getString(R.string.room_join_rules_invite, senderName) + RoomJoinRules.PUBLIC -> sp.getString(R.string.room_join_rules_public, senderName) else -> null } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt index e5924d9f2a6..a9ea8317235 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt @@ -123,7 +123,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, ) } - session.getRoom(roomId)?.join(emptyList(), object : MatrixCallback { + session.getRoom(roomId)?.join(callback = object : MatrixCallback { override fun onSuccess(data: Unit) { // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // Instead, we wait for the room to be joined @@ -158,7 +158,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, ) } - session.getRoom(roomId)?.leave(object : MatrixCallback { + session.getRoom(roomId)?.leave(null, object : MatrixCallback { override fun onSuccess(data: Unit) { // We do not update the rejectingRoomsIds here, because, the room is not rejected yet regarding the sync data. // Instead, we wait for the room to be rejected @@ -197,7 +197,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } private fun handleLeaveRoom(action: RoomListAction.LeaveRoom) { - session.getRoom(action.roomId)?.leave(object : MatrixCallback { + session.getRoom(action.roomId)?.leave(null, object : MatrixCallback { override fun onFailure(failure: Throwable) { _viewEvents.post(RoomListViewEvents.Failure(failure)) } diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt index 63cd1c5ce6d..c9dc131b42f 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt @@ -74,14 +74,14 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { private fun handleJoinRoom(roomId: String) { activeSessionHolder.getSafeActiveSession()?.let { session -> session.getRoom(roomId) - ?.join(emptyList(), object : MatrixCallback {}) + ?.join(callback = object : MatrixCallback {}) } } private fun handleRejectRoom(roomId: String) { activeSessionHolder.getSafeActiveSession()?.let { session -> session.getRoom(roomId) - ?.leave(object : MatrixCallback {}) + ?.leave(callback = object : MatrixCallback {}) } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt index 685e1aa2829..d89f0e2b99a 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt @@ -214,7 +214,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: ) } - session.joinRoom(action.roomId, emptyList(), object : MatrixCallback { + session.joinRoom(action.roomId, callback = object : MatrixCallback { override fun onSuccess(data: Unit) { // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // Instead, we wait for the room to be joined diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt index 9ffb64556f2..54c86537d23 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt @@ -97,7 +97,7 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted initialState: R ) } - session.joinRoom(state.roomId, emptyList(), object : MatrixCallback { + session.joinRoom(state.roomId, callback = object : MatrixCallback { override fun onSuccess(data: Unit) { // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // Instead, we wait for the room to be joined