diff --git a/CHANGES.md b/CHANGES.md index 54dd5e5247..b8b5dd4b36 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,7 @@ Improvements 🙌: - Sharing things to RiotX: sort list by recent room first (#771) Other changes: - - + - Add support for /rainbow and /rainbowme commands (#879) Bugfix 🐛: - 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 8b72ffa4a6..6151ae0d66 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 @@ -37,6 +37,8 @@ enum class Command(val command: String, val parameters: String, @StringRes val d 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), + RAINBOW("/rainbow", "", R.string.command_description_rainbow), + RAINBOW_EMOTE("/rainbowme", "", R.string.command_description_rainbow_emote), CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token), SPOILER("/spoiler", "", R.string.command_description_spoiler); 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 359f2c1f13..58671df539 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 @@ -80,6 +80,16 @@ object CommandParser { ParsedCommand.SendEmote(message) } + Command.RAINBOW.command -> { + val message = textMessage.subSequence(Command.RAINBOW.command.length, textMessage.length).trim() + + ParsedCommand.SendRainbow(message) + } + Command.RAINBOW_EMOTE.command -> { + val message = textMessage.subSequence(Command.RAINBOW_EMOTE.command.length, textMessage.length).trim() + + ParsedCommand.SendRainbowEmote(message) + } Command.JOIN_ROOM.command -> { if (messageParts.size >= 2) { val roomAlias = messageParts[1] 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 dd7c0c7e86..c43b78d71c 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,6 +34,8 @@ sealed class ParsedCommand { // Valid commands: class SendEmote(val message: CharSequence) : ParsedCommand() + class SendRainbow(val message: CharSequence) : ParsedCommand() + class SendRainbowEmote(val message: CharSequence) : 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() 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 36cbdcaa75..867a382bec 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 @@ -56,6 +56,7 @@ import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventConten import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap import im.vector.riotx.R +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.StringProvider @@ -64,6 +65,7 @@ import im.vector.riotx.core.utils.LiveEvent import im.vector.riotx.core.utils.subscribeLogError import im.vector.riotx.features.command.CommandParser import im.vector.riotx.features.command.ParsedCommand +import im.vector.riotx.features.home.room.detail.composer.rainbow.RainbowGenerator import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents import im.vector.riotx.features.home.room.typing.TypingHelper import im.vector.riotx.features.settings.VectorPreferences @@ -83,6 +85,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro private val vectorPreferences: VectorPreferences, private val stringProvider: StringProvider, private val typingHelper: TypingHelper, + private val rainbowGenerator: RainbowGenerator, private val session: Session ) : VectorViewModel(initialState), Timeline.Listener { @@ -385,6 +388,20 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) popDraft() } + is ParsedCommand.SendRainbow -> { + slashCommandResult.message.toString().let { + room.sendFormattedTextMessage(it, rainbowGenerator.generate(it)) + } + _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) + popDraft() + } + is ParsedCommand.SendRainbowEmote -> { + slashCommandResult.message.toString().let { + room.sendFormattedTextMessage(it, rainbowGenerator.generate(it), MessageType.MSGTYPE_EMOTE) + } + _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) + popDraft() + } is ParsedCommand.SendSpoiler -> { room.sendFormattedTextMessage( "[${stringProvider.getString(R.string.spoiler)}](${slashCommandResult.message})", @@ -401,7 +418,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro // TODO _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented) } - } + }.exhaustive } is SendMode.EDIT -> { // is original event a reply? @@ -459,7 +476,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro popDraft() } } - } + }.exhaustive } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGenerator.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGenerator.kt new file mode 100644 index 0000000000..c9defac8e8 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGenerator.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.home.room.detail.composer.rainbow + +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.roundToInt + +/** + * Inspired from React-Sdk + * Ref: https://github.com/matrix-org/matrix-react-sdk/blob/develop/src/utils/colour.js + */ +class RainbowGenerator @Inject constructor() { + + fun generate(text: String): String { + val frequency = 360f / text.length + + return text + .mapIndexed { idx, letter -> + // Do better than React-Sdk: Avoid adding font color for spaces + if (letter == ' ') { + "$letter" + } else { + val dashColor = hueToRGB(idx * frequency, 1.0f, 0.5f).toDashColor() + "$letter" + } + } + .joinToString(separator = "") + } + + private fun hueToRGB(h: Float, s: Float, l: Float): RgbColor { + val c = s * (1 - abs(2 * l - 1)) + val x = c * (1 - abs((h / 60) % 2 - 1)) + val m = l - c / 2 + + var r = 0f + var g = 0f + var b = 0f + + when { + h < 60f -> { + r = c + g = x + } + h < 120f -> { + r = x + g = c + } + h < 180f -> { + g = c + b = x + } + h < 240f -> { + g = x + b = c + } + h < 300f -> { + r = x + b = c + } + else -> { + r = c + b = x + } + } + + return RgbColor( + ((r + m) * 255).roundToInt(), + ((g + m) * 255).roundToInt(), + ((b + m) * 255).roundToInt() + ) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RgbColor.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RgbColor.kt new file mode 100644 index 0000000000..bf2e808a36 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RgbColor.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.home.room.detail.composer.rainbow + +data class RgbColor( + val r: Int, + val g: Int, + val b: Int +) + +fun RgbColor.toDashColor(): String { + return listOf(r, g, b) + .joinToString(separator = "", prefix = "#") { + it.toString(16).padStart(2, '0') + } +} diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 781912fbe5..98064c0148 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -37,6 +37,9 @@ Recent rooms Other rooms + Sends the given message colored as a rainbow + Sends the given emote colored as a rainbow + Timeline