From 426e291ce98888116034cf707edc3229c74e127e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 23 Jan 2020 14:46:36 +0100 Subject: [PATCH 01/15] i18n for RiotX limitation messages --- .../timeline/factory/DefaultItemFactory.kt | 9 +++-- .../timeline/factory/MessageItemFactory.kt | 33 ++++++++++++++++--- vector/src/main/res/values/strings_riotX.xml | 4 +++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt index d9bed98b1f..377975554a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt @@ -17,9 +17,11 @@ package im.vector.riotx.features.home.room.detail.timeline.factory import im.vector.matrix.android.api.session.room.timeline.TimelineEvent -import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider +import im.vector.riotx.R +import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController +import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem_ @@ -28,6 +30,7 @@ import javax.inject.Inject class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: AvatarSizeProvider, private val avatarRenderer: AvatarRenderer, + private val stringProvider: StringProvider, private val informationDataFactory: MessageInformationDataFactory) { fun create(text: String, @@ -49,9 +52,9 @@ class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: Ava callback: TimelineEventController.Callback?, throwable: Throwable? = null): DefaultItem { val text = if (throwable == null) { - "${event.root.getClearType()} events are not yet handled" + stringProvider.getString(R.string.rendering_event_error_type_of_event_not_handled, event.root.getClearType()) } else { - "an exception occurred when rendering the event ${event.root.eventId}" + stringProvider.getString(R.string.rendering_event_error_exception, event.root.eventId) } val informationData = informationDataFactory.create(event, null) return create(text, informationData, highlight, callback) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 9e05cdcc18..3febf19208 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -26,7 +26,16 @@ import android.view.View import dagger.Lazy import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.message.* +import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent +import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent +import im.vector.matrix.android.api.session.room.model.message.MessageFileContent +import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent +import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent +import im.vector.matrix.android.api.session.room.model.message.MessageTextContent +import im.vector.matrix.android.api.session.room.model.message.MessageType +import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent +import im.vector.matrix.android.api.session.room.model.message.getFileUrl import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt @@ -40,8 +49,24 @@ import im.vector.riotx.core.utils.DimensionConverter import im.vector.riotx.core.utils.containsOnlyEmojis import im.vector.riotx.core.utils.isLocalFile import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController -import im.vector.riotx.features.home.room.detail.timeline.helper.* -import im.vector.riotx.features.home.room.detail.timeline.item.* +import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider +import im.vector.riotx.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder +import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory +import im.vector.riotx.features.home.room.detail.timeline.helper.MessageItemAttributesFactory +import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider +import im.vector.riotx.features.home.room.detail.timeline.item.AbsMessageItem +import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem +import im.vector.riotx.features.home.room.detail.timeline.item.MessageBlockCodeItem +import im.vector.riotx.features.home.room.detail.timeline.item.MessageBlockCodeItem_ +import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem +import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem_ +import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem +import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem_ +import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData +import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem +import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem_ +import im.vector.riotx.features.home.room.detail.timeline.item.RedactedMessageItem +import im.vector.riotx.features.home.room.detail.timeline.item.RedactedMessageItem_ import im.vector.riotx.features.home.room.detail.timeline.tools.createLinkMovementMethod import im.vector.riotx.features.home.room.detail.timeline.tools.linkify import im.vector.riotx.features.html.CodeVisitor @@ -153,7 +178,7 @@ class MessageItemFactory @Inject constructor( informationData: MessageInformationData, highlight: Boolean, callback: TimelineEventController.Callback?): DefaultItem? { - val text = "${messageContent.type} message events are not yet handled" + val text = stringProvider.getString(R.string.rendering_event_error_type_of_message_not_handled, messageContent.type) return defaultItemFactory.create(text, informationData, highlight, callback) } diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 781912fbe5..89127d4e25 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -32,6 +32,10 @@ Jump to read receipt + "RiotX does not handle events of type '%1$s' (yet)" + "RiotX does not handle message of type '%1$s' (yet)" + "RiotX encountered an issue when rendering content of event with id '%1$s'" + Unignore Recent rooms From d530c64a84572d153e427b67177375daf462950b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 23 Jan 2020 15:35:46 +0100 Subject: [PATCH 02/15] Render defaultItem as other item: display user avatar Also ensure bottom sheet always has a header, for user avatar and date --- .../action/MessageActionsEpoxyController.kt | 19 +++++----- .../action/MessageActionsViewModel.kt | 25 ++++++++----- .../detail/timeline/item/BaseEventItem.kt | 2 ++ .../room/detail/timeline/item/DefaultItem.kt | 8 +++-- .../item_timeline_event_base_noinfo.xml | 9 +++-- .../item_timeline_event_default_stub.xml | 35 ++++++++++++++----- .../item_timeline_event_notice_stub.xml | 8 ++--- 7 files changed, 70 insertions(+), 36 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt index f90dbed95e..bdfdb02be1 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt @@ -46,17 +46,14 @@ class MessageActionsEpoxyController @Inject constructor( override fun buildModels(state: MessageActionState) { // Message preview - val body = state.messageBody - if (body != null) { - bottomSheetMessagePreviewItem { - id("preview") - avatarRenderer(avatarRenderer) - matrixItem(state.informationData.matrixItem) - movementMethod(createLinkMovementMethod(listener)) - userClicked { listener?.didSelectMenuAction(EventSharedAction.OpenUserProfile(state.informationData.senderId)) } - body(body.linkify(listener)) - time(state.time()) - } + bottomSheetMessagePreviewItem { + id("preview") + avatarRenderer(avatarRenderer) + matrixItem(state.informationData.matrixItem) + movementMethod(createLinkMovementMethod(listener)) + userClicked { listener?.didSelectMenuAction(EventSharedAction.OpenUserProfile(state.informationData.senderId)) } + body(state.messageBody.linkify(listener)) + time(state.time()) } // Send state diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 29068b07df..936bc263e7 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -15,7 +15,12 @@ */ package im.vector.riotx.features.home.room.detail.timeline.action -import com.airbnb.mvrx.* +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import dagger.Lazy @@ -45,7 +50,8 @@ import im.vector.riotx.features.html.VectorHtmlCompressor import im.vector.riotx.features.reactions.data.EmojiDataSource import im.vector.riotx.features.settings.VectorPreferences import java.text.SimpleDateFormat -import java.util.* +import java.util.Date +import java.util.Locale /** * Quick reactions state @@ -60,7 +66,7 @@ data class MessageActionState( val eventId: String, val informationData: MessageInformationData, val timelineEvent: Async = Uninitialized, - val messageBody: CharSequence? = null, + val messageBody: CharSequence = "", // For quick reactions val quickStates: Async> = Uninitialized, // For actions @@ -155,13 +161,16 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted private fun observeTimelineEventState() { asyncSubscribe(MessageActionState::timelineEvent) { timelineEvent -> - val computedMessage = computeMessageBody(timelineEvent) - val actions = actionsForEvent(timelineEvent) - setState { copy(messageBody = computedMessage, actions = actions) } + setState { + copy( + messageBody = computeMessageBody(timelineEvent), + actions = actionsForEvent(timelineEvent) + ) + } } } - private fun computeMessageBody(timelineEvent: TimelineEvent): CharSequence? { + private fun computeMessageBody(timelineEvent: TimelineEvent): CharSequence { return when (timelineEvent.root.getClearType()) { EventType.MESSAGE, EventType.STICKER -> { @@ -189,7 +198,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted noticeEventFormatter.format(timelineEvent) } else -> null - } + } ?: "" } private fun actionsForEvent(timelineEvent: TimelineEvent): List { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt index 02b7341c72..f674cfa0f4 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt @@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail.timeline.item import android.view.View import android.view.ViewStub import android.widget.RelativeLayout +import androidx.annotation.CallSuper import androidx.annotation.IdRes import androidx.core.view.updateLayoutParams import com.airbnb.epoxy.EpoxyAttribute @@ -42,6 +43,7 @@ abstract class BaseEventItem : VectorEpoxyModel @EpoxyAttribute lateinit var dimensionConverter: DimensionConverter + @CallSuper override fun bind(holder: H) { super.bind(holder) holder.leftGuideline.updateLayoutParams { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt index dc52293292..3bf564577f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt @@ -17,6 +17,7 @@ package im.vector.riotx.features.home.room.detail.timeline.item import android.view.View +import android.widget.ImageView import android.widget.TextView import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass @@ -50,8 +51,10 @@ abstract class DefaultItem : BaseEventItem() { var text: CharSequence? = null override fun bind(holder: Holder) { - holder.messageView.text = text + super.bind(holder) holder.view.setOnLongClickListener(longClickListener) + avatarRenderer.render(informationData.matrixItem, holder.avatarView) + holder.messageView.text = text holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener) } @@ -62,7 +65,8 @@ abstract class DefaultItem : BaseEventItem() { override fun getViewType() = STUB_ID class Holder : BaseHolder(STUB_ID) { - val messageView by bind(R.id.stateMessageView) + val avatarView by bind(R.id.itemDefaultAvatarView) + val messageView by bind(R.id.itemDefaultTextView) } companion object { diff --git a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml index c1987dccb2..ebc32baa57 100644 --- a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml +++ b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml @@ -36,8 +36,9 @@ + android:layout="@layout/item_timeline_event_default_stub" + tools:layout_marginTop="80dp" + tools:visibility="visible" /> + android:layout="@layout/item_timeline_event_merged_header_stub" + tools:layout_marginTop="160dp" + tools:visibility="visible" /> diff --git a/vector/src/main/res/layout/item_timeline_event_default_stub.xml b/vector/src/main/res/layout/item_timeline_event_default_stub.xml index 345bda0b7e..68c8936b32 100644 --- a/vector/src/main/res/layout/item_timeline_event_default_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_default_stub.xml @@ -1,12 +1,31 @@ - \ No newline at end of file + android:orientation="horizontal"> + + + + + + + diff --git a/vector/src/main/res/layout/item_timeline_event_notice_stub.xml b/vector/src/main/res/layout/item_timeline_event_notice_stub.xml index 76190062b1..9aacf357f1 100644 --- a/vector/src/main/res/layout/item_timeline_event_notice_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_notice_stub.xml @@ -3,8 +3,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:orientation="horizontal" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:orientation="horizontal"> + tools:text="@string/notice_avatar_url_changed" /> \ No newline at end of file From 632832a651340413be0940b73f0d7d10822019cd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 23 Jan 2020 15:44:41 +0100 Subject: [PATCH 03/15] Nearly same code for DefaultItem and NoticeItem --- .../timeline/factory/DefaultItemFactory.kt | 16 +++++--- .../room/detail/timeline/item/DefaultItem.kt | 40 ++++++++----------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt index 377975554a..89e21e04a2 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt @@ -16,6 +16,7 @@ package im.vector.riotx.features.home.room.detail.timeline.factory +import android.view.View import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotx.R import im.vector.riotx.core.resources.StringProvider @@ -37,14 +38,19 @@ class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: Ava informationData: MessageInformationData, highlight: Boolean, callback: TimelineEventController.Callback?): DefaultItem { + val attributes = DefaultItem.Attributes( + avatarRenderer = avatarRenderer, + informationData = informationData, + text = text, + itemLongClickListener = View.OnLongClickListener { view -> + callback?.onEventLongClicked(informationData, null, view) ?: false + }, + readReceiptsCallback = callback + ) return DefaultItem_() .leftGuideline(avatarSizeProvider.leftGuideline) .highlighted(highlight) - .text(text) - .avatarRenderer(avatarRenderer) - .informationData(informationData) - .baseCallback(callback) - .readReceiptsCallback(callback) + .attributes(attributes) } fun create(event: TimelineEvent, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt index 3bf564577f..0ccc982c4c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt @@ -30,45 +30,39 @@ import im.vector.riotx.features.home.room.detail.timeline.TimelineEventControlle abstract class DefaultItem : BaseEventItem() { @EpoxyAttribute - lateinit var informationData: MessageInformationData - @EpoxyAttribute - lateinit var avatarRenderer: AvatarRenderer - @EpoxyAttribute - var baseCallback: TimelineEventController.BaseCallback? = null - - private var longClickListener = View.OnLongClickListener { - return@OnLongClickListener baseCallback?.onEventLongClicked(informationData, null, it) == true - } - - @EpoxyAttribute - var readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null + lateinit var attributes: Attributes private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener { - readReceiptsCallback?.onReadReceiptsClicked(informationData.readReceipts) + attributes.readReceiptsCallback?.onReadReceiptsClicked(attributes.informationData.readReceipts) }) - @EpoxyAttribute - var text: CharSequence? = null - override fun bind(holder: Holder) { super.bind(holder) - holder.view.setOnLongClickListener(longClickListener) - avatarRenderer.render(informationData.matrixItem, holder.avatarView) - holder.messageView.text = text - holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener) + holder.messageTextView.text = attributes.text + attributes.avatarRenderer.render(attributes.informationData.matrixItem, holder.avatarImageView) + holder.view.setOnLongClickListener(attributes.itemLongClickListener) + holder.readReceiptsView.render(attributes.informationData.readReceipts, attributes.avatarRenderer, _readReceiptsClickListener) } override fun getEventIds(): List { - return listOf(informationData.eventId) + return listOf(attributes.informationData.eventId) } override fun getViewType() = STUB_ID class Holder : BaseHolder(STUB_ID) { - val avatarView by bind(R.id.itemDefaultAvatarView) - val messageView by bind(R.id.itemDefaultTextView) + val avatarImageView by bind(R.id.itemDefaultAvatarView) + val messageTextView by bind(R.id.itemDefaultTextView) } + data class Attributes( + val avatarRenderer: AvatarRenderer, + val informationData: MessageInformationData, + val text: CharSequence, + val itemLongClickListener: View.OnLongClickListener? = null, + val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null + ) + companion object { private const val STUB_ID = R.id.messageContentDefaultStub } From e9ea69f055ba4492c3e6e42b9afcbf0a15ef9236 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 23 Jan 2020 23:25:26 +0100 Subject: [PATCH 04/15] Add support for /rainbow and /rainbowme command (#879) --- CHANGES.md | 2 +- .../vector/riotx/features/command/Command.kt | 2 + .../riotx/features/command/CommandParser.kt | 10 +++ .../riotx/features/command/ParsedCommand.kt | 2 + .../home/room/detail/RoomDetailViewModel.kt | 21 ++++- .../composer/rainbow/RainbowGenerator.kt | 87 +++++++++++++++++++ .../room/detail/composer/rainbow/RgbColor.kt | 30 +++++++ vector/src/main/res/values/strings_riotX.xml | 3 + 8 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGenerator.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RgbColor.kt 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 From 976a8fc568b0dcef0def649199080e18f518ff18 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Jan 2020 16:36:28 +0100 Subject: [PATCH 05/15] Hide the algorithm when turning on e2e (#897) --- CHANGES.md | 1 + .../src/main/res/values/strings_RiotX.xml | 3 +++ .../timeline/format/NoticeEventFormatter.kt | 20 +++++++++++++++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 54dd5e5247..fb1ed4371d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ Features ✨: Improvements 🙌: - Sharing things to RiotX: sort list by recent room first (#771) + - Hide the algorithm when turning on e2e (#897) Other changes: - 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 d781ec5f1e..d030b857cf 100644 --- a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml +++ b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml @@ -1,4 +1,7 @@ + %1$s turned on end-to-end encryption. + %1$s turned on end-to-end encryption (unrecognised algorithm %2$s). + 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 563a970cfb..16275e6a73 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,9 +19,21 @@ 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.* +import im.vector.matrix.android.api.session.room.model.GuestAccess +import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.android.api.session.room.model.RoomAliasesContent +import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent +import im.vector.matrix.android.api.session.room.model.RoomGuestAccessContent +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.RoomMemberContent +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.call.CallInviteContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder @@ -180,7 +192,11 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active private fun formatRoomEncryptionEvent(event: Event, senderName: String?): CharSequence? { val content = event.content.toModel() ?: return null - return sp.getString(R.string.notice_end_to_end, senderName, content.algorithm) + return if (content.algorithm == MXCRYPTO_ALGORITHM_MEGOLM) { + sp.getString(R.string.notice_end_to_end_ok, senderName) + } else { + sp.getString(R.string.notice_end_to_end_unknown_algorithm, senderName, content.algorithm) + } } private fun buildProfileNotice(event: Event, senderName: String?, eventContent: RoomMemberContent?, prevEventContent: RoomMemberContent?): String { From 2eeeea33770a3725b34ea8c67eb92947864c7712 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Jan 2020 17:19:22 +0100 Subject: [PATCH 06/15] Encryption is enabled only for MEGOLM. --- .../internal/crypto/DefaultCryptoService.kt | 25 ++++++++++++++----- .../session/room/RoomSummaryUpdater.kt | 14 ++++++++--- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index 783b7eb4ad..0cf378aed9 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -56,9 +56,15 @@ import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore -import im.vector.matrix.android.internal.crypto.tasks.* +import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceTask +import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask +import im.vector.matrix.android.internal.crypto.tasks.GetDeviceInfoTask +import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask +import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask +import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.EventEntityFields import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.extensions.foldToCallback @@ -71,7 +77,12 @@ import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.JsonCanonicalizer import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.fetchCopied -import kotlinx.coroutines.* +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import org.matrix.olm.OlmManager import timber.log.Timber import java.util.concurrent.atomic.AtomicBoolean @@ -475,14 +486,16 @@ internal class DefaultCryptoService @Inject constructor( } /** - * Tells if a room is encrypted + * Tells if a room is encrypted with MXCRYPTO_ALGORITHM_MEGOLM * * @param roomId the room id - * @return true if the room is encrypted + * @return true if the room is encrypted with algorithm MXCRYPTO_ALGORITHM_MEGOLM */ override fun isRoomEncrypted(roomId: String): Boolean { - val encryptionEvent = monarchy.fetchCopied { - EventEntity.where(it, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION).findFirst() + val encryptionEvent = monarchy.fetchCopied { realm -> + EventEntity.where(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION) + .contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"") + .findFirst() } return encryptionEvent != null } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index bbb5feba15..e65a1eb73e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -23,12 +23,18 @@ import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomAliasesContent import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent import im.vector.matrix.android.api.session.room.model.RoomTopicContent +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import im.vector.matrix.android.internal.database.mapper.ContentMapper -import im.vector.matrix.android.internal.database.model.* import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.EventEntityFields +import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity -import im.vector.matrix.android.internal.database.query.* +import im.vector.matrix.android.internal.database.query.getOrCreate +import im.vector.matrix.android.internal.database.query.isEventRead +import im.vector.matrix.android.internal.database.query.latestEvent +import im.vector.matrix.android.internal.database.query.prev +import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper @@ -92,7 +98,9 @@ internal class RoomSummaryUpdater @Inject constructor( val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev() val lastCanonicalAliasEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_CANONICAL_ALIAS).prev() val lastAliasesEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).prev() - val encryptionEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ENCRYPTION).prev() + val encryptionEvent = EventEntity.where(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION) + .contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"") + .prev() roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 // avoid this call if we are sure there are unread events From ef0b438a89d4774d9c38454ca62bf6febcca5059 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 21 Jan 2020 15:42:08 +0100 Subject: [PATCH 07/15] Give the possibility to enable encryption when creating room (#837) --- CHANGES.md | 1 + .../roomdirectory/createroom/CreateRoomAction.kt | 1 + .../createroom/CreateRoomController.kt | 16 +++++++++++++--- .../createroom/CreateRoomFragment.kt | 4 ++++ .../createroom/CreateRoomViewModel.kt | 9 +++++++++ .../createroom/CreateRoomViewState.kt | 1 + vector/src/main/res/values/strings_riotX.xml | 3 +++ 7 files changed, 32 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 54dd5e5247..adca5d9331 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,7 @@ Changes in RiotX 0.14.0 (2020-XX-XX) Features ✨: - Enable encryption in unencrypted rooms, from the room settings (#212) + - Enable e2e by default when creating DM, and give the possibility to enable encryption when creating room (#837) Improvements 🙌: - Sharing things to RiotX: sort list by recent room first (#771) diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomAction.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomAction.kt index 333834ca3c..8986db180a 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomAction.kt @@ -22,5 +22,6 @@ sealed class CreateRoomAction : VectorViewModelAction { data class SetName(val name: String) : CreateRoomAction() data class SetIsPublic(val isPublic: Boolean) : CreateRoomAction() data class SetIsInRoomDirectory(val isInRoomDirectory: Boolean) : CreateRoomAction() + data class SetIsEncrypted(val isEncrypted: Boolean) : CreateRoomAction() object Create : CreateRoomAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomController.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomController.kt index 2477e6fab0..92e178c628 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomController.kt @@ -39,9 +39,7 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin var index = 0 override fun buildModels(viewState: CreateRoomViewState) { - val asyncCreateRoom = viewState.asyncCreateRoomRequest - - when (asyncCreateRoom) { + when (val asyncCreateRoom = viewState.asyncCreateRoomRequest) { is Success -> { // Nothing to display, the screen will be closed } @@ -101,12 +99,24 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin listener?.setIsInRoomDirectory(value) } } + formSwitchItem { + id("encryption") + enabled(enableFormElement) + title(stringProvider.getString(R.string.create_room_encryption_title)) + summary(stringProvider.getString(R.string.create_room_encryption_description)) + switchChecked(viewState.isEncrypted) + + listener { value -> + listener?.setIsEncrypted(value) + } + } } interface Listener { fun onNameChange(newName: String) fun setIsPublic(isPublic: Boolean) fun setIsInRoomDirectory(isInRoomDirectory: Boolean) + fun setIsEncrypted(isEncrypted: Boolean) fun retry() } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt index aacc21916a..827db96783 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt @@ -85,6 +85,10 @@ class CreateRoomFragment @Inject constructor(private val createRoomController: C viewModel.handle(CreateRoomAction.SetIsInRoomDirectory(isInRoomDirectory)) } + override fun setIsEncrypted(isEncrypted: Boolean) { + viewModel.handle(CreateRoomAction.SetIsEncrypted(isEncrypted)) + } + override fun retry() { Timber.v("Retry") viewModel.handle(CreateRoomAction.Create) diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt index 457574e736..ee170de0e0 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -31,6 +31,7 @@ import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomPreset import im.vector.riotx.core.platform.EmptyViewEvents +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity @@ -62,6 +63,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr is CreateRoomAction.SetName -> setName(action) is CreateRoomAction.SetIsPublic -> setIsPublic(action) is CreateRoomAction.SetIsInRoomDirectory -> setIsInRoomDirectory(action) + is CreateRoomAction.SetIsEncrypted -> setIsEncrypted(action) is CreateRoomAction.Create -> doCreateRoom() } } @@ -72,6 +74,8 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr private fun setIsInRoomDirectory(action: CreateRoomAction.SetIsInRoomDirectory) = setState { copy(isInRoomDirectory = action.isInRoomDirectory) } + private fun setIsEncrypted(action: CreateRoomAction.SetIsEncrypted) = setState { copy(isEncrypted = action.isEncrypted) } + private fun doCreateRoom() = withState { state -> if (state.asyncCreateRoomRequest is Loading || state.asyncCreateRoomRequest is Success) { return@withState @@ -89,6 +93,11 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr // Public room preset = if (state.isPublic) CreateRoomPreset.PRESET_PUBLIC_CHAT else CreateRoomPreset.PRESET_PRIVATE_CHAT + + // Encryption + if (state.isEncrypted) { + enableEncryptionWithAlgorithm(MXCRYPTO_ALGORITHM_MEGOLM) + } } session.createRoom(createRoomParams, object : MatrixCallback { diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewState.kt index 363d31edc6..810319d54f 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewState.kt @@ -24,5 +24,6 @@ data class CreateRoomViewState( val roomName: String = "", val isPublic: Boolean = false, val isInRoomDirectory: Boolean = false, + val isEncrypted: Boolean = false, val asyncCreateRoomRequest: Async = Uninitialized ) : MvRxState diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 89127d4e25..5fc94d243d 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -3,6 +3,9 @@ + "Enable encryption" + "Once enabled, encryption cannot be disabled." + Your email domain is not authorized to register on this server Messages in this room are not end-to-end encrypted. From da9b9f486447385fc718ca4fbad46389bb8df07d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 21 Jan 2020 15:48:35 +0100 Subject: [PATCH 08/15] Make the whole cell clickable --- .../java/im/vector/riotx/features/form/FormSwitchItem.kt | 6 ++++++ vector/src/main/res/layout/item_form_switch.xml | 1 + 2 files changed, 7 insertions(+) diff --git a/vector/src/main/java/im/vector/riotx/features/form/FormSwitchItem.kt b/vector/src/main/java/im/vector/riotx/features/form/FormSwitchItem.kt index 2e48a8b709..1f3c7c81bb 100644 --- a/vector/src/main/java/im/vector/riotx/features/form/FormSwitchItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/form/FormSwitchItem.kt @@ -44,6 +44,12 @@ abstract class FormSwitchItem : VectorEpoxyModel() { var summary: String? = null override fun bind(holder: Holder) { + holder.view.setOnClickListener { + if (enabled) { + holder.switchView.toggle() + } + } + holder.titleView.text = title holder.summaryView.setTextOrHide(summary) diff --git a/vector/src/main/res/layout/item_form_switch.xml b/vector/src/main/res/layout/item_form_switch.xml index 5757c4b853..3583ac8024 100644 --- a/vector/src/main/res/layout/item_form_switch.xml +++ b/vector/src/main/res/layout/item_form_switch.xml @@ -5,6 +5,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?riotx_background" + android:foreground="?attr/selectableItemBackground" android:minHeight="@dimen/item_form_min_height"> Date: Tue, 28 Jan 2020 21:56:02 +0100 Subject: [PATCH 09/15] Add TUs for RainbowGenerator (not all passing) --- .../composer/rainbow/RainbowGeneratorTest.kt | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt diff --git a/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt b/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt new file mode 100644 index 0000000000..521fb15bf2 --- /dev/null +++ b/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt @@ -0,0 +1,102 @@ +/* + * 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 org.junit.Assert.assertEquals +import org.junit.Test + +class RainbowGeneratorTest { + + private val rainbowGenerator = RainbowGenerator() + + @Test + fun testEmpty() { + assertEquals("", rainbowGenerator.generate("")) + } + + @Test + fun testAscii1() { + assertEquals("""a""", rainbowGenerator.generate("a")) + } + + @Test + fun testAscii2() { + val expected = + """ + T + h + i + s + + i + s + + a + + r + a + i + n + b + o + w + ! + """ + .trimIndent() + .replace("\n", "") + + assertEquals(expected, rainbowGenerator.generate("This is a rainbow!")) + } + + @Test + fun testEmoji1() { + assertEquals("""\uD83E\uDD1E""", rainbowGenerator.generate("\uD83E\uDD1E")) // 🤞 + } + + @Test + fun testEmoji2() { + assertEquals("""🤞""", rainbowGenerator.generate("🤞")) + } + + @Test + fun testEmojiMix() { + val expected = """ + T + h + i + s + + i + s + + a + + r + a + i + n + b + o + w + ! + """ + .trimIndent() + .replace("\n", "") + + assertEquals(expected, rainbowGenerator.generate("Hello 🤞 world!")) + } +} \ No newline at end of file From b2338dfcd39da4795b5923749dd4fe2731022067 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Jan 2020 22:35:40 +0100 Subject: [PATCH 10/15] Make the TU passes --- .../java/im/vector/riotx/core/utils/Emoji.kt | 36 +++++++++++++++++++ .../composer/rainbow/RainbowGenerator.kt | 4 ++- .../composer/rainbow/RainbowGeneratorTest.kt | 30 +++++++--------- 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt b/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt index f9e5654726..e91a2896bc 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt @@ -113,3 +113,39 @@ fun containsOnlyEmojis(str: String?): Boolean { return res } + +/** + * Same as split, but considering emojis + */ +fun CharSequence.splitEmoji(): List { + val result = mutableListOf() + + var index = 0 + + while (index < length) { + val firstChar = get(index) + + if (firstChar.toInt() == 0x200e) { + // Left to right mark. What should I do with it? + } else if (firstChar.toInt() in 0xD800..0xDBFF && index + 1 < length) { + // We have the start of a surrogate pair + val secondChar = get(index + 1) + + if (secondChar.toInt() in 0xDC00..0xDFFF) { + // We have an emoji + result.add("$firstChar$secondChar") + index++ + } else { + // Not sure what we have here... + result.add("$firstChar") + } + } else { + // Regular char + result.add("$firstChar") + } + + index++ + } + + return result +} 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 index c9defac8e8..9662e2d372 100644 --- 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 @@ -16,6 +16,7 @@ package im.vector.riotx.features.home.room.detail.composer.rainbow +import im.vector.riotx.core.utils.splitEmoji import javax.inject.Inject import kotlin.math.abs import kotlin.math.roundToInt @@ -30,9 +31,10 @@ class RainbowGenerator @Inject constructor() { val frequency = 360f / text.length return text + .splitEmoji() .mapIndexed { idx, letter -> // Do better than React-Sdk: Avoid adding font color for spaces - if (letter == ' ') { + if (letter == " ") { "$letter" } else { val dashColor = hueToRGB(idx * frequency, 1.0f, 0.5f).toDashColor() diff --git a/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt b/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt index 521fb15bf2..6a798638f2 100644 --- a/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt +++ b/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt @@ -64,7 +64,7 @@ class RainbowGeneratorTest { @Test fun testEmoji1() { - assertEquals("""\uD83E\uDD1E""", rainbowGenerator.generate("\uD83E\uDD1E")) // 🤞 + assertEquals("""🤞""", rainbowGenerator.generate("\uD83E\uDD1E")) // 🤞 } @Test @@ -75,24 +75,20 @@ class RainbowGeneratorTest { @Test fun testEmojiMix() { val expected = """ - T - h - i - s + H + e + l + l + o - i - s + 🤞 - a - - r - a - i - n - b - o - w - ! + w + o + r + l + d + ! """ .trimIndent() .replace("\n", "") From 007b0cabf21323f773e63dd1e9563379466a44c5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Jan 2020 22:43:10 +0100 Subject: [PATCH 11/15] Add a few TUs --- .../composer/rainbow/RainbowGeneratorTest.kt | 50 ++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt b/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt index 6a798638f2..3e1092d288 100644 --- a/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt +++ b/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt @@ -56,8 +56,7 @@ class RainbowGeneratorTest { w ! """ - .trimIndent() - .replace("\n", "") + .trimIndentOnLine() assertEquals(expected, rainbowGenerator.generate("This is a rainbow!")) } @@ -73,7 +72,18 @@ class RainbowGeneratorTest { } @Test - fun testEmojiMix() { + fun testEmoji3() { + val expected = """ + 🤞 + 🙂 + """ + .trimIndentOnLine() + + assertEquals(expected, rainbowGenerator.generate("🤞🙂")) + } + + @Test + fun testEmojiMix1() { val expected = """ H e @@ -90,9 +100,37 @@ class RainbowGeneratorTest { d ! """ - .trimIndent() - .replace("\n", "") + .trimIndentOnLine() assertEquals(expected, rainbowGenerator.generate("Hello 🤞 world!")) } -} \ No newline at end of file + + @Test + fun testEmojiMix2() { + val expected = """ + a + 🤞 + """ + .trimIndentOnLine() + + assertEquals(expected, rainbowGenerator.generate("a🤞")) + } + + @Test + fun testEmojiMix3() { + val expected = """ + 🤞 + a + """ + .trimIndentOnLine() + + assertEquals(expected, rainbowGenerator.generate("🤞a")) + } + + @Test + fun testError1() { + assertEquals("\uD83E", rainbowGenerator.generate("\uD83E")) + } +} + +fun String.trimIndentOnLine() = trimIndent().replace("\n", "") \ No newline at end of file From 27fe4e3680193641f1ff03a9cb4b0d620ac5d4f8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 29 Jan 2020 09:44:53 +0100 Subject: [PATCH 12/15] Fix build and add tests --- .../composer/rainbow/RainbowGenerator.kt | 6 +- .../composer/rainbow/RainbowGeneratorTest.kt | 60 ++++++++++--------- .../java/im/vector/riotx/test/Extensions.kt | 19 ++++++ 3 files changed, 54 insertions(+), 31 deletions(-) create mode 100644 vector/src/test/java/im/vector/riotx/test/Extensions.kt 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 index 9662e2d372..3868be4e2e 100644 --- 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 @@ -28,10 +28,10 @@ import kotlin.math.roundToInt class RainbowGenerator @Inject constructor() { fun generate(text: String): String { - val frequency = 360f / text.length + val split = text.splitEmoji() + val frequency = 360f / split.size - return text - .splitEmoji() + return split .mapIndexed { idx, letter -> // Do better than React-Sdk: Avoid adding font color for spaces if (letter == " ") { diff --git a/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt b/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt index 3e1092d288..5a9fdc0ab7 100644 --- a/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt +++ b/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt @@ -16,9 +16,11 @@ package im.vector.riotx.features.home.room.detail.composer.rainbow +import im.vector.riotx.test.trimIndentOneLine import org.junit.Assert.assertEquals import org.junit.Test +@Suppress("SpellCheckingInspection") class RainbowGeneratorTest { private val rainbowGenerator = RainbowGenerator() @@ -35,8 +37,17 @@ class RainbowGeneratorTest { @Test fun testAscii2() { - val expected = - """ + val expected = """ + a + b + """.trimIndentOneLine() + + assertEquals(expected, rainbowGenerator.generate("ab")) + } + + @Test + fun testAscii3() { + val expected = """ T h i @@ -55,8 +66,7 @@ class RainbowGeneratorTest { o w ! - """ - .trimIndentOnLine() + """.trimIndentOneLine() assertEquals(expected, rainbowGenerator.generate("This is a rainbow!")) } @@ -75,9 +85,8 @@ class RainbowGeneratorTest { fun testEmoji3() { val expected = """ 🤞 - 🙂 - """ - .trimIndentOnLine() + 🙂 + """.trimIndentOneLine() assertEquals(expected, rainbowGenerator.generate("🤞🙂")) } @@ -86,21 +95,20 @@ class RainbowGeneratorTest { fun testEmojiMix1() { val expected = """ H - e - l - l - o + e + l + l + o - 🤞 + 🤞 - w - o - r - l - d - ! - """ - .trimIndentOnLine() + w + o + r + l + d + ! + """.trimIndentOneLine() assertEquals(expected, rainbowGenerator.generate("Hello 🤞 world!")) } @@ -109,9 +117,8 @@ class RainbowGeneratorTest { fun testEmojiMix2() { val expected = """ a - 🤞 - """ - .trimIndentOnLine() + 🤞 + """.trimIndentOneLine() assertEquals(expected, rainbowGenerator.generate("a🤞")) } @@ -120,9 +127,8 @@ class RainbowGeneratorTest { fun testEmojiMix3() { val expected = """ 🤞 - a - """ - .trimIndentOnLine() + a + """.trimIndentOneLine() assertEquals(expected, rainbowGenerator.generate("🤞a")) } @@ -132,5 +138,3 @@ class RainbowGeneratorTest { assertEquals("\uD83E", rainbowGenerator.generate("\uD83E")) } } - -fun String.trimIndentOnLine() = trimIndent().replace("\n", "") \ No newline at end of file diff --git a/vector/src/test/java/im/vector/riotx/test/Extensions.kt b/vector/src/test/java/im/vector/riotx/test/Extensions.kt new file mode 100644 index 0000000000..31781ce00e --- /dev/null +++ b/vector/src/test/java/im/vector/riotx/test/Extensions.kt @@ -0,0 +1,19 @@ +/* + * 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.test + +fun String.trimIndentOneLine() = trimIndent().replace("\n", "") From 57a13fa30df86c12177a0600c478822f1af6b9b5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 29 Jan 2020 18:07:57 +0100 Subject: [PATCH 13/15] Sort room members by display names --- CHANGES.md | 1 + .../members/RoomMemberListViewModel.kt | 11 ++-- .../members/RoomMemberSummaryComparator.kt | 61 +++++++++++++++++++ 3 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberSummaryComparator.kt diff --git a/CHANGES.md b/CHANGES.md index 3993bf755b..7c235d8704 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ Features ✨: Improvements 🙌: - Sharing things to RiotX: sort list by recent room first (#771) - Hide the algorithm when turning on e2e (#897) + - Sort room members by display names Other changes: - Add support for /rainbow and /rainbowme commands (#879) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt index 8472d4a2a5..2556e3b78c 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt @@ -40,6 +40,7 @@ import io.reactivex.Observable import io.reactivex.functions.BiFunction class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomMemberListViewState, + private val roomMemberSummaryComparator: RoomMemberSummaryComparator, private val session: Session) : VectorViewModel(initialState) { @@ -113,11 +114,11 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } return listOf( - PowerLevelCategory.ADMIN to admins, - PowerLevelCategory.MODERATOR to moderators, - PowerLevelCategory.CUSTOM to customs, - PowerLevelCategory.INVITE to invites, - PowerLevelCategory.USER to users + PowerLevelCategory.ADMIN to admins.sortedWith(roomMemberSummaryComparator), + PowerLevelCategory.MODERATOR to moderators.sortedWith(roomMemberSummaryComparator), + PowerLevelCategory.CUSTOM to customs.sortedWith(roomMemberSummaryComparator), + PowerLevelCategory.INVITE to invites.sortedWith(roomMemberSummaryComparator), + PowerLevelCategory.USER to users.sortedWith(roomMemberSummaryComparator) ) } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberSummaryComparator.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberSummaryComparator.kt new file mode 100644 index 0000000000..cc1dd29d13 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberSummaryComparator.kt @@ -0,0 +1,61 @@ +/* + * 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.roomprofile.members + +import im.vector.matrix.android.api.session.room.model.RoomMemberSummary +import javax.inject.Inject + +class RoomMemberSummaryComparator @Inject constructor() : Comparator { + + override fun compare(leftRoomMemberSummary: RoomMemberSummary?, rightRoomMemberSummary: RoomMemberSummary?): Int { + return when (leftRoomMemberSummary) { + null -> + when (rightRoomMemberSummary) { + null -> 0 + else -> 1 + } + else -> + when (rightRoomMemberSummary) { + null -> -1 + else -> + when { + leftRoomMemberSummary.displayName.isNullOrBlank() -> + when { + rightRoomMemberSummary.displayName.isNullOrBlank() -> { + // No display names, compare ids + leftRoomMemberSummary.userId.compareTo(rightRoomMemberSummary.userId) + } + else -> 1 + } + else -> + when { + rightRoomMemberSummary.displayName.isNullOrBlank() -> -1 + else -> { + when (leftRoomMemberSummary.displayName) { + rightRoomMemberSummary.displayName -> + // Same display name, compare id + leftRoomMemberSummary.userId.compareTo(rightRoomMemberSummary.userId) + else -> + leftRoomMemberSummary.displayName!!.compareTo(rightRoomMemberSummary.displayName!!, true) + } + } + } + } + } + } + } +} From b848d0530f6c9792d10c34eaa2e1db0e6a65589b Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 21 Jan 2020 15:15:29 +0100 Subject: [PATCH 14/15] Update realm to 6.1.0: should fix some of the native crashes --- matrix-sdk-android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index e510d11efb..ca18c2b56e 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -10,7 +10,7 @@ buildscript { jcenter() } dependencies { - classpath "io.realm:realm-gradle-plugin:6.0.2" + classpath "io.realm:realm-gradle-plugin:6.1.0" } } From e44dc347c6209b07fa22ee2d7f06027f42bbe2c0 Mon Sep 17 00:00:00 2001 From: Christopher Rossbach <31703168+duncanturk@users.noreply.github.com> Date: Thu, 30 Jan 2020 12:07:33 +0100 Subject: [PATCH 15/15] Add "get it on F-Droid" RiotX is on F-Droid now --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1848c7baba..b43bcf643c 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ RiotX is an Android Matrix Client currently in beta but in active development. It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-android) with a new user experience. RiotX will become the official replacement as soon as all features are implemented. [Get it on Google Play](https://play.google.com/store/apps/details?id=im.vector.riotx) +[Get it on F-Droid](https://f-droid.org/app/im.vector.riotx) Nightly build: [![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)