Add support for /rainbow and /rainbowme command (#879)

This commit is contained in:
Benoit Marty 2020-01-23 23:25:26 +01:00
parent 75e39535bc
commit e9ea69f055
8 changed files with 154 additions and 3 deletions

View file

@ -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 🐛:
-

View file

@ -37,6 +37,8 @@ enum class Command(val command: String, val parameters: String, @StringRes val d
KICK_USER("/kick", "<user-id> [reason]", R.string.command_description_kick_user),
CHANGE_DISPLAY_NAME("/nick", "<display-name>", R.string.command_description_nick),
MARKDOWN("/markdown", "<on|off>", R.string.command_description_markdown),
RAINBOW("/rainbow", "<message>", R.string.command_description_rainbow),
RAINBOW_EMOTE("/rainbowme", "<message>", R.string.command_description_rainbow_emote),
CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token),
SPOILER("/spoiler", "<message>", R.string.command_description_spoiler);

View file

@ -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]

View file

@ -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()

View file

@ -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<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(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
}
}

View file

@ -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()
"<font color=\"$dashColor\">$letter</font>"
}
}
.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()
)
}
}

View file

@ -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')
}
}

View file

@ -37,6 +37,9 @@
<string name="room_list_sharing_header_recent_rooms">Recent rooms</string>
<string name="room_list_sharing_header_other_rooms">Other rooms</string>
<string name="command_description_rainbow">Sends the given message colored as a rainbow</string>
<string name="command_description_rainbow_emote">Sends the given emote colored as a rainbow</string>
<!-- Title for category in the settings which affect what is displayed in the timeline (ex: show read receipts, etc.) -->
<string name="settings_category_timeline">Timeline</string>