From 20c1886fedb153666a6e5870dfa64ba89dedbccc Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Mar 2022 10:40:29 +0100 Subject: [PATCH 01/54] Support both unstable and stable prefixes. Author: Onuray --- changelog.d/5340.bugfix | 1 + .../sdk/api/session/events/model/Event.kt | 4 +- .../sdk/api/session/events/model/EventType.kt | 6 +-- .../model/message/MessageLocationContent.kt | 37 +++++++++------- .../room/model/message/MessagePollContent.kt | 8 +++- .../message/MessagePollResponseContent.kt | 8 +++- .../session/room/model/message/PollAnswer.kt | 8 +++- .../room/model/message/PollCreationInfo.kt | 8 ++-- .../room/model/message/PollQuestion.kt | 8 +++- .../session/room/model/message/PollType.kt | 6 +++ .../room/summary/RoomSummaryConstants.kt | 5 +-- .../session/room/timeline/TimelineEvent.kt | 6 +-- .../notification/ProcessEventForPushTask.kt | 2 +- .../EventRelationsAggregationProcessor.kt | 23 +++++----- .../room/prune/RedactionEventProcessor.kt | 2 +- .../room/send/LocalEchoEventFactory.kt | 42 +++++++------------ .../app/core/extensions/TimelineEvent.kt | 2 +- .../home/room/detail/TimelineFragment.kt | 4 +- .../action/MessageActionsViewModel.kt | 25 +++++------ .../timeline/factory/MessageItemFactory.kt | 8 ++-- .../timeline/factory/TimelineItemFactory.kt | 6 +-- .../format/DisplayableEventFormatter.kt | 8 ++-- .../timeline/format/NoticeEventFormatter.kt | 8 ++-- .../helper/TimelineDisplayableEvents.kt | 5 +-- .../style/TimelineMessageLayoutFactory.kt | 3 +- .../poll/create/CreatePollController.kt | 4 +- .../poll/create/CreatePollViewModel.kt | 7 ++-- .../poll/create/CreatePollViewState.kt | 2 +- .../poll/create/PollTypeSelectionItem.kt | 6 +-- .../raw/wellknown/ElementWellKnown.kt | 12 +++++- .../app/features/location/LocationDataTest.kt | 18 ++++++-- 31 files changed, 164 insertions(+), 128 deletions(-) create mode 100644 changelog.d/5340.bugfix diff --git a/changelog.d/5340.bugfix b/changelog.d/5340.bugfix new file mode 100644 index 0000000000..17fd9716c0 --- /dev/null +++ b/changelog.d/5340.bugfix @@ -0,0 +1 @@ +Support both stable and unstable prefixes \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index df57ca5681..c0ca40bc73 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -349,7 +349,7 @@ fun Event.isAttachmentMessage(): Boolean { } } -fun Event.isPoll(): Boolean = getClearType() == EventType.POLL_START || getClearType() == EventType.POLL_END +fun Event.isPoll(): Boolean = getClearType() in EventType.POLL_START || getClearType() in EventType.POLL_END fun Event.isSticker(): Boolean = getClearType() == EventType.STICKER @@ -372,7 +372,7 @@ fun Event.getRelationContent(): RelationDefaultContent? { * Returns the poll question or null otherwise */ fun Event.getPollQuestion(): String? = - getPollContent()?.pollCreationInfo?.question?.question + getPollContent()?.getBestPollCreationInfo()?.question?.getBestQuestion() /** * Returns the relation content for a specific type or null otherwise diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt index 0c77b574e7..22fb9bcbe2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt @@ -103,9 +103,9 @@ object EventType { const val REACTION = "m.reaction" // Poll - const val POLL_START = "org.matrix.msc3381.poll.start" - const val POLL_RESPONSE = "org.matrix.msc3381.poll.response" - const val POLL_END = "org.matrix.msc3381.poll.end" + val POLL_START = listOf("org.matrix.msc3381.poll.start", "m.poll.start") + val POLL_RESPONSE = listOf("org.matrix.msc3381.poll.response", "m.poll.response") + val POLL_END = listOf("org.matrix.msc3381.poll.end", "m.poll.end") // Unwedging internal const val DUMMY = "m.dummy" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt index d07bd2d73a..84bf5cf7b7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt @@ -39,37 +39,46 @@ data class MessageLocationContent( */ @Json(name = "geo_uri") val geoUri: String, + @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, + @Json(name = "m.new_content") override val newContent: Content? = null, /** * See https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md */ - @Json(name = "org.matrix.msc3488.location") val locationInfo: LocationInfo? = null, - - @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, - @Json(name = "m.new_content") override val newContent: Content? = null, - + @Json(name = "org.matrix.msc3488.location") val unstableLocationInfo: LocationInfo? = null, + @Json(name = "m.location") val locationInfo: LocationInfo? = null, + /** + * Exact time that the data in the event refers to (milliseconds since the UNIX epoch) + */ + @Json(name = "org.matrix.msc3488.ts") val unstableTs: Long? = null, + @Json(name = "m.ts") val ts: Long? = null, + @Json(name = "org.matrix.msc1767.text") val unstableText: String? = null, + @Json(name = "m.text") val text: String? = null, /** * m.asset defines a generic asset that can be used for location tracking but also in other places like * inventories, geofencing, checkins/checkouts etc. * It should contain a mandatory namespaced type key defining what particular asset is being referred to. * For the purposes of user location tracking m.self should be used in order to avoid duplicating the mxid. */ - @Json(name = "m.asset") val locationAsset: LocationAsset? = null, - - /** - * Exact time that the data in the event refers to (milliseconds since the UNIX epoch) - */ - @Json(name = "org.matrix.msc3488.ts") val ts: Long? = null, - - @Json(name = "org.matrix.msc1767.text") val text: String? = null + @Json(name = "org.matrix.msc3488.asset") val unstableLocationAsset: LocationAsset? = null, + @Json(name = "m.asset") val locationAsset: LocationAsset? = null ) : MessageContent { - fun getBestGeoUri() = locationInfo?.geoUri ?: geoUri + fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo + + fun getBestTs() = ts ?: unstableTs + + fun getBestText() = text ?: unstableText + + fun getBestLocationAsset() = locationAsset ?: unstableLocationAsset + + fun getBestGeoUri() = getBestLocationInfo()?.geoUri ?: geoUri /** * @return true if the location asset is a user location, not a generic one. */ fun isSelfLocation(): Boolean { // Should behave like m.self if locationAsset is null + val locationAsset = getBestLocationAsset() return locationAsset?.type == null || locationAsset.type == LocationAssetType.SELF } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt index a4e1317290..43c0c90068 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt @@ -31,5 +31,9 @@ data class MessagePollContent( @Json(name = "body") override val body: String = "", @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.new_content") override val newContent: Content? = null, - @Json(name = "org.matrix.msc3381.poll.start") val pollCreationInfo: PollCreationInfo? = null -) : MessageContent + @Json(name = "org.matrix.msc3381.poll.start") val unstablePollCreationInfo: PollCreationInfo? = null, + @Json(name = "m.poll.start") val pollCreationInfo: PollCreationInfo? = null +) : MessageContent { + + fun getBestPollCreationInfo() = pollCreationInfo ?: unstablePollCreationInfo +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt index f3b4e3dc23..022915ed69 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt @@ -31,5 +31,9 @@ data class MessagePollResponseContent( @Json(name = "body") override val body: String = "", @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.new_content") override val newContent: Content? = null, - @Json(name = "org.matrix.msc3381.poll.response") val response: PollResponse? = null -) : MessageContent + @Json(name = "org.matrix.msc3381.poll.response") val unstableResponse: PollResponse? = null, + @Json(name = "m.response") val response: PollResponse? = null +) : MessageContent { + + fun getBestResponse() = response ?: unstableResponse +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollAnswer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollAnswer.kt index 8f5ff53c85..34614d9d15 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollAnswer.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollAnswer.kt @@ -22,5 +22,9 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class PollAnswer( @Json(name = "id") val id: String? = null, - @Json(name = "org.matrix.msc1767.text") val answer: String? = null -) + @Json(name = "org.matrix.msc1767.text") val unstableAnswer: String? = null, + @Json(name = "m.text") val answer: String? = null +) { + + fun getBestAnswer() = answer ?: unstableAnswer +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt index a82c01b159..81b034a809 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt @@ -21,8 +21,8 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class PollCreationInfo( - @Json(name = "question") val question: PollQuestion? = null, - @Json(name = "kind") val kind: PollType? = PollType.DISCLOSED, - @Json(name = "max_selections") val maxSelections: Int = 1, - @Json(name = "answers") val answers: List? = null + @Json(name = "question") val question: PollQuestion? = null, + @Json(name = "kind") val kind: PollType? = PollType.DISCLOSED_UNSTABLE, + @Json(name = "max_selections") val maxSelections: Int = 1, + @Json(name = "answers") val answers: List? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollQuestion.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollQuestion.kt index 76025f745e..df9517892b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollQuestion.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollQuestion.kt @@ -21,5 +21,9 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class PollQuestion( - @Json(name = "org.matrix.msc1767.text") val question: String? = null -) + @Json(name = "org.matrix.msc1767.text") val unstableQuestion: String? = null, + @Json(name = "m.text") val question: String? = null +) { + + fun getBestQuestion() = question ?: unstableQuestion +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollType.kt index 3a8066b9bc..54801e698d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollType.kt @@ -25,11 +25,17 @@ enum class PollType { * Voters should see results as soon as they have voted. */ @Json(name = "org.matrix.msc3381.poll.disclosed") + DISCLOSED_UNSTABLE, + + @Json(name = "m.poll.disclosed") DISCLOSED, /** * Results should be only revealed when the poll is ended. */ @Json(name = "org.matrix.msc3381.poll.undisclosed") + UNDISCLOSED_UNSTABLE, + + @Json(name = "m.poll.undisclosed") UNDISCLOSED } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt index 3bba2deae5..eaed9053ea 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt @@ -32,7 +32,6 @@ object RoomSummaryConstants { EventType.CALL_ANSWER, EventType.ENCRYPTED, EventType.STICKER, - EventType.REACTION, - EventType.POLL_START - ) + EventType.REACTION + ) + EventType.POLL_START } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index 6f8bae876b..f9398ac7b8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -134,9 +134,9 @@ fun TimelineEvent.getEditedEventId(): String? { */ fun TimelineEvent.getLastMessageContent(): MessageContent? { return when (root.getClearType()) { - EventType.STICKER -> root.getClearContent().toModel() - EventType.POLL_START -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() - else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() + EventType.STICKER -> root.getClearContent().toModel() + in EventType.POLL_START -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() + else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt index 8b05d2ea62..8ae203c2b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt @@ -56,7 +56,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor( val allEvents = (newJoinEvents + inviteEvents).filter { event -> when (event.type) { - EventType.POLL_START, + in EventType.POLL_START, EventType.MESSAGE, EventType.REDACTION, EventType.ENCRYPTED, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index 1e0eb8b497..8159da844f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -86,11 +86,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor( // TODO Add ? // EventType.KEY_VERIFICATION_READY, EventType.KEY_VERIFICATION_KEY, - EventType.ENCRYPTED, - EventType.POLL_START, - EventType.POLL_RESPONSE, - EventType.POLL_END - ) + EventType.ENCRYPTED + ) + EventType.POLL_START + EventType.POLL_RESPONSE + EventType.POLL_END override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean { return allowedTypes.contains(eventType) @@ -156,7 +153,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( Timber.v("###REPLACE in room $roomId for event ${event.eventId}") // A replace! handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) - } else if (event.getClearType() == EventType.POLL_RESPONSE) { + } else if (event.getClearType() in EventType.POLL_RESPONSE) { event.getClearContent().toModel(catchError = true)?.let { pollResponseContent -> Timber.v("###RESPONSE in room $roomId for event ${event.eventId}") handleResponse(realm, event, pollResponseContent, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) @@ -177,12 +174,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor( handleVerification(realm, event, roomId, isLocalEcho, it) } } - EventType.POLL_RESPONSE -> { + in EventType.POLL_RESPONSE -> { event.getClearContent().toModel(catchError = true)?.let { handleResponse(realm, event, it, roomId, isLocalEcho, event.getRelationContent()?.eventId) } } - EventType.POLL_END -> { + in EventType.POLL_END -> { event.content.toModel(catchError = true)?.let { handleEndPoll(realm, event, it, roomId, isLocalEcho) } @@ -217,7 +214,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } } - EventType.POLL_START -> { + in EventType.POLL_START -> { val content: MessagePollContent? = event.content.toModel() if (content?.relatesTo?.type == RelationType.REPLACE) { Timber.v("###REPLACE in room $roomId for event ${event.eventId}") @@ -225,12 +222,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor( handleReplace(realm, event, content, roomId, isLocalEcho) } } - EventType.POLL_RESPONSE -> { + in EventType.POLL_RESPONSE -> { event.content.toModel(catchError = true)?.let { handleResponse(realm, event, it, roomId, isLocalEcho) } } - EventType.POLL_END -> { + in EventType.POLL_END -> { event.content.toModel(catchError = true)?.let { handleEndPoll(realm, event, it, roomId, isLocalEcho) } @@ -407,12 +404,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor( return } - val option = content.response?.answers?.first() ?: return Unit.also { + val option = content.getBestResponse()?.answers?.first() ?: return Unit.also { Timber.d("## POLL Ignoring malformed response no option eventId:$eventId content: ${event.content}") } // Check if this option is in available options - if (!targetPollContent.pollCreationInfo?.answers?.map { it.id }?.contains(option).orFalse()) { + if (!targetPollContent.getBestPollCreationInfo()?.answers?.map { it.id }?.contains(option).orFalse()) { Timber.v("## POLL $targetEventId doesn't contain option $option") return } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt index ee52fe574b..4753e12157 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt @@ -71,7 +71,7 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr when (typeToPrune) { EventType.ENCRYPTED, EventType.MESSAGE, - EventType.POLL_START -> { + in EventType.POLL_START -> { Timber.d("REDACTION for message ${eventToPrune.eventId}") val unsignedData = EventMapper.map(eventToPrune).unsignedData ?: UnsignedData(null, null) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 3c36d58710..0c014134a8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -137,16 +137,11 @@ internal class LocalEchoEventFactory @Inject constructor( options: List, pollType: PollType): MessagePollContent { return MessagePollContent( - pollCreationInfo = PollCreationInfo( - question = PollQuestion( - question = question - ), + unstablePollCreationInfo = PollCreationInfo( + question = PollQuestion(unstableQuestion = question), kind = pollType, answers = options.map { option -> - PollAnswer( - id = UUID.randomUUID().toString(), - answer = option - ) + PollAnswer(id = UUID.randomUUID().toString(), unstableAnswer = option) } ) ) @@ -167,7 +162,7 @@ internal class LocalEchoEventFactory @Inject constructor( originServerTs = dummyOriginServerTs(), senderId = userId, eventId = localId, - type = EventType.POLL_START, + type = EventType.POLL_START.first(), content = newContent.toContent() ) } @@ -179,11 +174,9 @@ internal class LocalEchoEventFactory @Inject constructor( body = answerId, relatesTo = RelationDefaultContent( type = RelationType.REFERENCE, - eventId = pollEventId), - response = PollResponse( - answers = listOf(answerId) - ) - + eventId = pollEventId + ), + unstableResponse = PollResponse(answers = listOf(answerId)) ) val localId = LocalEcho.createLocalEchoId() return Event( @@ -191,7 +184,7 @@ internal class LocalEchoEventFactory @Inject constructor( originServerTs = dummyOriginServerTs(), senderId = userId, eventId = localId, - type = EventType.POLL_RESPONSE, + type = EventType.POLL_RESPONSE.first(), content = content.toContent(), unsignedData = UnsignedData(age = null, transactionId = localId)) } @@ -207,7 +200,7 @@ internal class LocalEchoEventFactory @Inject constructor( originServerTs = dummyOriginServerTs(), senderId = userId, eventId = localId, - type = EventType.POLL_START, + type = EventType.POLL_START.first(), content = content.toContent(), unsignedData = UnsignedData(age = null, transactionId = localId)) } @@ -226,7 +219,7 @@ internal class LocalEchoEventFactory @Inject constructor( originServerTs = dummyOriginServerTs(), senderId = userId, eventId = localId, - type = EventType.POLL_END, + type = EventType.POLL_END.first(), content = content.toContent(), unsignedData = UnsignedData(age = null, transactionId = localId)) } @@ -239,15 +232,10 @@ internal class LocalEchoEventFactory @Inject constructor( val content = MessageLocationContent( geoUri = geoUri, body = geoUri, - locationInfo = LocationInfo( - geoUri = geoUri, - description = geoUri - ), - locationAsset = LocationAsset( - type = LocationAssetType.SELF - ), - ts = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()), - text = geoUri + unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri), + unstableLocationAsset = LocationAsset(type = LocationAssetType.SELF), + unstableTs = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()), + unstableText = geoUri ) return createMessageEvent(roomId, content) } @@ -638,7 +626,7 @@ internal class LocalEchoEventFactory @Inject constructor( MessageType.MSGTYPE_AUDIO -> return TextContent("sent an audio file.") MessageType.MSGTYPE_IMAGE -> return TextContent("sent an image.") MessageType.MSGTYPE_VIDEO -> return TextContent("sent a video.") - MessageType.MSGTYPE_POLL_START -> return TextContent((content as? MessagePollContent)?.pollCreationInfo?.question?.question ?: "") + MessageType.MSGTYPE_POLL_START -> return TextContent((content as? MessagePollContent)?.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "") else -> return TextContent(content?.body ?: "") } } diff --git a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt index b1df29ebc6..28c1587b1a 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent fun TimelineEvent.canReact(): Boolean { // Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment - return root.getClearType() in listOf(EventType.MESSAGE, EventType.STICKER, EventType.POLL_START) && + return root.getClearType() in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START && root.sendState == SendState.SYNCED && !root.isRedacted() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 662af3d546..c638666cd7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -1189,7 +1189,7 @@ class TimelineFragment @Inject constructor( val formattedDuration = DateUtils.formatElapsedTime(((messageContent.audioInfo?.duration ?: 0) / 1000).toLong()) getString(R.string.voice_message_reply_content, formattedDuration) } else if (messageContent is MessagePollContent) { - messageContent.pollCreationInfo?.question?.question + messageContent.getBestPollCreationInfo()?.question?.getBestQuestion() } else { messageContent?.body ?: "" } @@ -2165,7 +2165,7 @@ class TimelineFragment @Inject constructor( timelineViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add)) } is EventSharedAction.Edit -> { - if (action.eventType == EventType.POLL_START) { + if (action.eventType in EventType.POLL_START) { navigator.openCreatePoll(requireContext(), timelineArgs.roomId, action.eventId, PollMode.EDIT) } else if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) { messageComposerViewModel.handle(MessageComposerAction.EnterEditMode(action.eventId, views.composerLayout.text.toString())) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 5575d9b7f6..81a8150c95 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -181,7 +181,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } else { when (timelineEvent.root.getClearType()) { EventType.MESSAGE, - EventType.STICKER -> { + EventType.STICKER -> { val messageContent: MessageContent? = timelineEvent.getLastMessageContent() if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) { val html = messageContent.formattedBody @@ -207,13 +207,14 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted EventType.CALL_INVITE, EventType.CALL_CANDIDATES, EventType.CALL_HANGUP, - EventType.CALL_ANSWER -> { + EventType.CALL_ANSWER -> { noticeEventFormatter.format(timelineEvent, room?.roomSummary()?.isDirect.orFalse()) } - EventType.POLL_START -> { - timelineEvent.root.getClearContent().toModel(catchError = true)?.pollCreationInfo?.question?.question ?: "" + in EventType.POLL_START -> { + timelineEvent.root.getClearContent().toModel(catchError = true)?.getBestPollCreationInfo()?.question?.getBestQuestion() + ?: "" } - else -> null + else -> null } } } catch (failure: Throwable) { @@ -373,7 +374,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } if (canRedact(timelineEvent, actionPermissions)) { - if (timelineEvent.root.getClearType() == EventType.POLL_START) { + if (timelineEvent.root.getClearType() in EventType.POLL_START) { add(EventSharedAction.Redact( eventId, askForReason = informationData.senderId != session.myUserId, @@ -425,7 +426,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted private fun canReply(event: TimelineEvent, messageContent: MessageContent?, actionPermissions: ActionPermissions): Boolean { // Only EventType.MESSAGE and EventType.POLL_START event types are supported for the moment - if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.POLL_START)) return false + if (event.root.getClearType() !in EventType.POLL_START + EventType.MESSAGE) return false if (!actionPermissions.canSendMessage) return false return when (messageContent?.msgType) { MessageType.MSGTYPE_TEXT, @@ -511,7 +512,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted private fun canRedact(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean { // Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment - if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.POLL_START)) return false + if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START) return false // Message sent by the current user can always be redacted if (event.root.senderId == session.myUserId) return true // Check permission for messages sent by other users @@ -526,13 +527,13 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted private fun canViewReactions(event: TimelineEvent): Boolean { // Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment - if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.POLL_START)) return false + if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START) return false return event.annotations?.reactionsSummary?.isNotEmpty() ?: false } private fun canEdit(event: TimelineEvent, myUserId: String, actionPermissions: ActionPermissions): Boolean { // Only event of type EventType.MESSAGE and EventType.POLL_START are supported for the moment - if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.POLL_START)) return false + if (event.root.getClearType() !in listOf(EventType.MESSAGE) + EventType.POLL_START) return false if (!actionPermissions.canSendMessage) return false // TODO if user is admin or moderator val messageContent = event.root.getClearContent().toModel() @@ -578,13 +579,13 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } private fun canEndPoll(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean { - return event.root.getClearType() == EventType.POLL_START && + return event.root.getClearType() in EventType.POLL_START && canRedact(event, actionPermissions) && event.annotations?.pollResponseSummary?.closedTime == null } private fun canEditPoll(event: TimelineEvent): Boolean { - return event.root.getClearType() == EventType.POLL_START && + return event.root.getClearType() in EventType.POLL_START && event.annotations?.pollResponseSummary?.closedTime == null && event.annotations?.pollResponseSummary?.aggregatedContent?.totalVotes ?: 0 == 0 } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index aa1758dd6c..5e1e9b64d2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -247,7 +247,7 @@ class MessageItemFactory @Inject constructor( val didUserVoted = pollResponseSummary?.myVote?.isNotEmpty().orFalse() val winnerVoteCount = pollResponseSummary?.winnerVoteCount val isPollSent = informationData.sendState.isSent() - val isPollUndisclosed = pollContent.pollCreationInfo?.kind == PollType.UNDISCLOSED + val isPollUndisclosed = pollContent.getBestPollCreationInfo()?.kind == PollType.UNDISCLOSED_UNSTABLE val totalVotesText = (pollResponseSummary?.totalVotes ?: 0).let { when { @@ -262,13 +262,13 @@ class MessageItemFactory @Inject constructor( } } - pollContent.pollCreationInfo?.answers?.forEach { option -> + pollContent.getBestPollCreationInfo()?.answers?.forEach { option -> val voteSummary = pollResponseSummary?.votes?.get(option.id) val isMyVote = pollResponseSummary?.myVote == option.id val voteCount = voteSummary?.total ?: 0 val votePercentage = voteSummary?.percentage ?: 0.0 val optionId = option.id ?: "" - val optionAnswer = option.answer ?: "" + val optionAnswer = option.getBestAnswer() ?: "" optionViewStates.add( if (!isPollSent) { @@ -291,7 +291,7 @@ class MessageItemFactory @Inject constructor( ) } - val question = pollContent.pollCreationInfo?.question?.question ?: "" + val question = pollContent.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "" return PollItem_() .attributes(attributes) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index b41e1d8f25..f9d2613e27 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -94,7 +94,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me when (event.root.getClearType()) { // Message itemsX EventType.STICKER, - EventType.POLL_START, + in EventType.POLL_START, EventType.MESSAGE -> messageItemFactory.create(params) EventType.REDACTION, EventType.KEY_VERIFICATION_ACCEPT, @@ -107,8 +107,8 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.CALL_SELECT_ANSWER, EventType.CALL_NEGOTIATE, EventType.REACTION, - EventType.POLL_RESPONSE, - EventType.POLL_END -> noticeItemFactory.create(params) + in EventType.POLL_RESPONSE, + in EventType.POLL_END -> noticeItemFactory.create(params) // Calls EventType.CALL_INVITE, EventType.CALL_HANGUP, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt index d5f3a74e4e..1c339e6cf4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt @@ -120,14 +120,14 @@ class DisplayableEventFormatter @Inject constructor( EventType.CALL_CANDIDATES -> { span { } } - EventType.POLL_START -> { - timelineEvent.root.getClearContent().toModel(catchError = true)?.pollCreationInfo?.question?.question + in EventType.POLL_START -> { + timelineEvent.root.getClearContent().toModel(catchError = true)?.getBestPollCreationInfo()?.question?.getBestQuestion() ?: stringProvider.getString(R.string.sent_a_poll) } - EventType.POLL_RESPONSE -> { + in EventType.POLL_RESPONSE -> { stringProvider.getString(R.string.poll_response_room_list_preview) } - EventType.POLL_END -> { + in EventType.POLL_END -> { stringProvider.getString(R.string.poll_end_room_list_preview) } else -> { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index c7be395693..a20c1e5f97 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -106,8 +106,8 @@ class NoticeEventFormatter @Inject constructor( EventType.STATE_SPACE_PARENT, EventType.REDACTION, EventType.STICKER, - EventType.POLL_RESPONSE, - EventType.POLL_END -> formatDebug(timelineEvent.root) + in EventType.POLL_RESPONSE, + in EventType.POLL_END -> formatDebug(timelineEvent.root) else -> { Timber.v("Type $type not handled by this formatter") null @@ -196,8 +196,8 @@ class NoticeEventFormatter @Inject constructor( } private fun formatDebug(event: Event): CharSequence { - val threadPrefix = if (event.isThread()) "thread" else "" - return "Debug: $threadPrefix event type \"${event.getClearType()}\"" + val threadPrefix = if (event.isThread()) "thread" else "" + return "Debug: $threadPrefix event type \"${event.getClearType()}\"" } private fun formatRoomCreateEvent(event: Event, isDm: Boolean): CharSequence? { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index 53a9fbbaea..96a2ca4609 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -50,9 +50,8 @@ object TimelineDisplayableEvents { EventType.STATE_ROOM_TOMBSTONE, EventType.STATE_ROOM_JOIN_RULES, EventType.KEY_VERIFICATION_DONE, - EventType.KEY_VERIFICATION_CANCEL, - EventType.POLL_START - ) + EventType.KEY_VERIFICATION_CANCEL + ) + EventType.POLL_START } fun TimelineEvent.canBeMerged(): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt index 8c1c308bb5..96ab89bfea 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt @@ -43,10 +43,9 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess // Can be rendered in bubbles, other types will fallback to default private val EVENT_TYPES_WITH_BUBBLE_LAYOUT = setOf( EventType.MESSAGE, - EventType.POLL_START, EventType.ENCRYPTED, EventType.STICKER - ) + ) + EventType.POLL_START // Can't be rendered in bubbles, so get back to default layout private val MSG_TYPES_WITHOUT_BUBBLE_LAYOUT = setOf( diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollController.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollController.kt index b4f61dbc1f..0ef92e4d2f 100644 --- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollController.kt +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollController.kt @@ -60,9 +60,9 @@ class CreatePollController @Inject constructor( pollTypeChangedListener { _, id -> host.callback?.onPollTypeChanged( if (id == R.id.openPollTypeRadioButton) { - PollType.DISCLOSED + PollType.DISCLOSED_UNSTABLE } else { - PollType.UNDISCLOSED + PollType.UNDISCLOSED_UNSTABLE } ) } diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt index 5c7ef72297..2358f7f9a0 100644 --- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt @@ -71,9 +71,10 @@ class CreatePollViewModel @AssistedInject constructor( val event = room.getTimelineEvent(eventId) ?: return val content = event.getLastMessageContent() as? MessagePollContent ?: return - val pollType = content.pollCreationInfo?.kind ?: PollType.DISCLOSED - val question = content.pollCreationInfo?.question?.question ?: "" - val options = content.pollCreationInfo?.answers?.mapNotNull { it.answer } ?: List(MIN_OPTIONS_COUNT) { "" } + val pollCreationInfo = content.getBestPollCreationInfo() + val pollType = pollCreationInfo?.kind ?: PollType.DISCLOSED_UNSTABLE + val question = pollCreationInfo?.question?.getBestQuestion() ?: "" + val options = pollCreationInfo?.answers?.mapNotNull { it.getBestAnswer() } ?: List(MIN_OPTIONS_COUNT) { "" } setState { copy( diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt index 175d1b0116..fc3b746f32 100644 --- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt @@ -27,7 +27,7 @@ data class CreatePollViewState( val options: List = List(CreatePollViewModel.MIN_OPTIONS_COUNT) { "" }, val canCreatePoll: Boolean = false, val canAddMoreOptions: Boolean = true, - val pollType: PollType = PollType.DISCLOSED + val pollType: PollType = PollType.DISCLOSED_UNSTABLE ) : MavericksState { constructor(args: CreatePollArgs) : this( diff --git a/vector/src/main/java/im/vector/app/features/poll/create/PollTypeSelectionItem.kt b/vector/src/main/java/im/vector/app/features/poll/create/PollTypeSelectionItem.kt index 1b24a70cb9..0736c236b5 100644 --- a/vector/src/main/java/im/vector/app/features/poll/create/PollTypeSelectionItem.kt +++ b/vector/src/main/java/im/vector/app/features/poll/create/PollTypeSelectionItem.kt @@ -28,7 +28,7 @@ import org.matrix.android.sdk.api.session.room.model.message.PollType abstract class PollTypeSelectionItem : VectorEpoxyModel() { @EpoxyAttribute - var pollType: PollType = PollType.DISCLOSED + var pollType: PollType = PollType.DISCLOSED_UNSTABLE @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var pollTypeChangedListener: RadioGroup.OnCheckedChangeListener? = null @@ -38,8 +38,8 @@ abstract class PollTypeSelectionItem : VectorEpoxyModel R.id.openPollTypeRadioButton - PollType.UNDISCLOSED -> R.id.closedPollTypeRadioButton + PollType.DISCLOSED_UNSTABLE, PollType.DISCLOSED -> R.id.openPollTypeRadioButton + PollType.UNDISCLOSED_UNSTABLE, PollType.UNDISCLOSED -> R.id.closedPollTypeRadioButton } ) diff --git a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt index 0ae2a16b71..b1f1f6b1e0 100644 --- a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt +++ b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt @@ -38,8 +38,16 @@ data class ElementWellKnown( val riotE2E: E2EWellKnownConfig? = null, @Json(name = "org.matrix.msc3488.tile_server") - val mapTileServerConfig: MapTileServerConfig? = null -) + val unstableMapTileServerConfig: MapTileServerConfig? = null, + + @Json(name = "m.tile_server") + val stableMapTileServerConfig: MapTileServerConfig? = null +) { + + @Transient + var mapTileServerConfig: MapTileServerConfig? = null + get() = stableMapTileServerConfig ?: unstableMapTileServerConfig +} @JsonClass(generateAdapter = true) data class E2EWellKnownConfig( diff --git a/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt b/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt index e273c0b3c9..6b97b715db 100644 --- a/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt +++ b/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt @@ -22,6 +22,7 @@ import org.amshove.kluent.shouldBeTrue import org.junit.Test import org.matrix.android.sdk.api.session.room.model.message.LocationAsset import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType +import org.matrix.android.sdk.api.session.room.model.message.LocationInfo import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent class LocationDataTest { @@ -64,13 +65,24 @@ class LocationDataTest { @Test fun selfLocationTest() { - val contentWithNullAsset = MessageLocationContent(body = "", geoUri = "", locationAsset = null) + val contentWithNullAsset = MessageLocationContent(body = "", geoUri = "") contentWithNullAsset.isSelfLocation().shouldBeTrue() - val contentWithNullAssetType = MessageLocationContent(body = "", geoUri = "", locationAsset = LocationAsset(type = null)) + val contentWithNullAssetType = MessageLocationContent(body = "", geoUri = "", unstableLocationAsset = LocationAsset(type = null)) contentWithNullAssetType.isSelfLocation().shouldBeTrue() - val contentWithSelfAssetType = MessageLocationContent(body = "", geoUri = "", locationAsset = LocationAsset(type = LocationAssetType.SELF)) + val contentWithSelfAssetType = MessageLocationContent(body = "", geoUri = "", unstableLocationAsset = LocationAsset(type = LocationAssetType.SELF)) contentWithSelfAssetType.isSelfLocation().shouldBeTrue() } + + @Test + fun unstablePrefixTest() { + val geoUri = "geo :12.34,56.78;13.56" + + val contentWithUnstablePrefixes = MessageLocationContent(body = "", geoUri = "", unstableLocationInfo = LocationInfo(geoUri = geoUri)) + contentWithUnstablePrefixes.getBestLocationInfo()?.geoUri.shouldBeEqualTo(geoUri) + + val contentWithStablePrefixes = MessageLocationContent(body = "", geoUri = "", locationInfo = LocationInfo(geoUri = geoUri)) + contentWithStablePrefixes.getBestLocationInfo()?.geoUri.shouldBeEqualTo(geoUri) + } } From 67a24b38cb93a3a0ac8626a5f32b91bd48889a34 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Mar 2022 10:48:35 +0100 Subject: [PATCH 02/54] Avoid stable prefix --- .../java/im/vector/app/features/location/UrlMapProvider.kt | 2 +- .../vector/app/features/raw/wellknown/ElementWellKnown.kt | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt b/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt index adb5c27a02..3e4e16861e 100644 --- a/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt +++ b/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt @@ -39,7 +39,7 @@ class UrlMapProvider @Inject constructor( suspend fun getMapUrl(): String { val upstreamMapUrl = tryOrNull { rawService.getElementWellknown(session.sessionParams) } - ?.mapTileServerConfig + ?.getBestMapTileServerConfig() ?.mapStyleUrl return upstreamMapUrl ?: fallbackMapUrl } diff --git a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt index b1f1f6b1e0..91b0f4d2f7 100644 --- a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt +++ b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt @@ -41,12 +41,9 @@ data class ElementWellKnown( val unstableMapTileServerConfig: MapTileServerConfig? = null, @Json(name = "m.tile_server") - val stableMapTileServerConfig: MapTileServerConfig? = null + val mapTileServerConfig: MapTileServerConfig? = null ) { - - @Transient - var mapTileServerConfig: MapTileServerConfig? = null - get() = stableMapTileServerConfig ?: unstableMapTileServerConfig + fun getBestMapTileServerConfig() = mapTileServerConfig ?: unstableMapTileServerConfig } @JsonClass(generateAdapter = true) From 9b7e3290002fa13478b9dcf36ca4f60bc1e43600 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 9 Mar 2022 13:31:29 +0300 Subject: [PATCH 03/54] Fix lint error. --- changelog.d/5340.bugfix | 2 +- .../room/detail/timeline/action/MessageActionsViewModel.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/changelog.d/5340.bugfix b/changelog.d/5340.bugfix index 17fd9716c0..4c53f0088c 100644 --- a/changelog.d/5340.bugfix +++ b/changelog.d/5340.bugfix @@ -1 +1 @@ -Support both stable and unstable prefixes \ No newline at end of file +Support both stable and unstable prefixes for Events about Polls and Location \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 81a8150c95..b99dbcc220 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -211,8 +211,8 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted noticeEventFormatter.format(timelineEvent, room?.roomSummary()?.isDirect.orFalse()) } in EventType.POLL_START -> { - timelineEvent.root.getClearContent().toModel(catchError = true)?.getBestPollCreationInfo()?.question?.getBestQuestion() - ?: "" + timelineEvent.root.getClearContent().toModel(catchError = true) + ?.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "" } else -> null } From 025dcc8d884e83fa95fcdddf9ed1ce21b28dc121 Mon Sep 17 00:00:00 2001 From: Claire G Date: Mon, 14 Mar 2022 10:28:42 +0100 Subject: [PATCH 04/54] Fix bug: readReceipt in wrong order --- changelog.d/5514.bugfix | 1 + .../timeline/factory/ReadReceiptsItemFactory.kt | 15 ++++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 changelog.d/5514.bugfix diff --git a/changelog.d/5514.bugfix b/changelog.d/5514.bugfix new file mode 100644 index 0000000000..0dfbca6e9a --- /dev/null +++ b/changelog.d/5514.bugfix @@ -0,0 +1 @@ +Read receipt in wrong order \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt index e66dd4b043..502f17e78b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt @@ -37,17 +37,22 @@ class ReadReceiptsItemFactory @Inject constructor(private val avatarRenderer: Av val readReceiptsData = readReceipts .map { ReadReceiptData(it.roomMember.userId, it.roomMember.avatarUrl, it.roomMember.displayName, it.originServerTs) - } - .toList() - + }.toList() + val readReceiptsDataSorted = sortItem(readReceiptsData) return ReadReceiptsItem_() .id("read_receipts_$eventId") .eventId(eventId) - .readReceipts(readReceiptsData) + .readReceipts(readReceiptsDataSorted) .avatarRenderer(avatarRenderer) .shouldHideReadReceipts(isFromThreadTimeLine) .clickListener { - callback?.onReadReceiptsClicked(readReceiptsData) + callback?.onReadReceiptsClicked(readReceiptsDataSorted) } } + + fun sortItem(readReceipt: List): List { + return readReceipt.sortedByDescending { + it.timestamp + } + } } From 134d7b2bf813450e3e1fc516a3ec8231f451973e Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Thu, 3 Feb 2022 14:59:18 +0100 Subject: [PATCH 05/54] Adding changelog entry --- changelog.d/4533.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4533.misc diff --git a/changelog.d/4533.misc b/changelog.d/4533.misc new file mode 100644 index 0000000000..1137a1c43c --- /dev/null +++ b/changelog.d/4533.misc @@ -0,0 +1 @@ +Improve headers UI in Rooms/Messages lists From 70481e3ba3729da7464a0c2d9842ffd3ae19c63c Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Thu, 3 Feb 2022 15:57:31 +0100 Subject: [PATCH 06/54] Changing style of header --- .../main/res/layout/item_room_category.xml | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/vector/src/main/res/layout/item_room_category.xml b/vector/src/main/res/layout/item_room_category.xml index 0fd1e96d4a..e9baf4ceb4 100644 --- a/vector/src/main/res/layout/item_room_category.xml +++ b/vector/src/main/res/layout/item_room_category.xml @@ -5,32 +5,35 @@ android:id="@+id/roomCategoryRootView" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="?android:colorBackground" + android:background="?attr/vctr_header_background" android:clickable="true" android:focusable="true" android:foreground="?attr/selectableItemBackground" android:gravity="center_vertical" android:orientation="horizontal" - android:paddingStart="8dp" - android:paddingTop="12dp" - android:paddingEnd="@dimen/layout_horizontal_margin" - android:paddingBottom="4dp"> + android:paddingStart="16dp" + android:paddingVertical="8dp" + android:paddingEnd="@dimen/layout_horizontal_margin"> + + - \ No newline at end of file + From b1d1090d1df4e75a55fe54aed7ca3a1ed41ca6c7 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Thu, 3 Feb 2022 16:47:17 +0100 Subject: [PATCH 07/54] Adding number of items (UI part) --- .../home/room/list/SectionHeaderAdapter.kt | 22 +++++++++---------- .../main/res/layout/item_room_category.xml | 22 ++++++++++++++----- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt index 560e0d00a3..7eb063eece 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt @@ -18,14 +18,10 @@ package im.vector.app.features.home.room.list import android.view.LayoutInflater import android.view.ViewGroup -import androidx.core.content.ContextCompat -import androidx.core.graphics.drawable.DrawableCompat import androidx.recyclerview.widget.RecyclerView import im.vector.app.R import im.vector.app.core.epoxy.ClickListener -import im.vector.app.core.epoxy.onClick import im.vector.app.databinding.ItemRoomCategoryBinding -import im.vector.app.features.themes.ThemeUtils class SectionHeaderAdapter constructor( private val onClickAction: ClickListener @@ -33,6 +29,7 @@ class SectionHeaderAdapter constructor( data class RoomsSectionData( val name: String, + val itemCount: Int = 0, val isExpanded: Boolean = true, val notificationCount: Int = 0, val isHighlighted: Boolean = false, @@ -79,14 +76,17 @@ class SectionHeaderAdapter constructor( } fun bind(roomsSectionData: RoomsSectionData) { - binding.roomCategoryTitleView.text = roomsSectionData.name - val tintColor = ThemeUtils.getColor(binding.root.context, R.attr.vctr_content_secondary) - val expandedArrowDrawableRes = if (roomsSectionData.isExpanded) R.drawable.ic_expand_more else R.drawable.ic_expand_less - val expandedArrowDrawable = ContextCompat.getDrawable(binding.root.context, expandedArrowDrawableRes)?.also { - DrawableCompat.setTint(it, tintColor) + with(binding) { + roomCategoryTitleView.text = roomsSectionData.name + val tintColor = ThemeUtils.getColor(root.context, R.attr.vctr_content_secondary) + val expandedArrowDrawableRes = if (roomsSectionData.isExpanded) R.drawable.ic_expand_more else R.drawable.ic_expand_less + val expandedArrowDrawable = ContextCompat.getDrawable(root.context, expandedArrowDrawableRes)?.also { + DrawableCompat.setTint(it, tintColor) + } + roomCategoryCounterView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null) + roomCategoryCounterView.text = roomsSectionData.itemCount.toString() + roomCategoryUnreadCounterBadgeView.render(UnreadCounterBadgeView.State(roomsSectionData.notificationCount, roomsSectionData.isHighlighted)) } - binding.roomCategoryUnreadCounterBadgeView.render(UnreadCounterBadgeView.State(roomsSectionData.notificationCount, roomsSectionData.isHighlighted)) - binding.roomCategoryTitleView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null) } companion object { diff --git a/vector/src/main/res/layout/item_room_category.xml b/vector/src/main/res/layout/item_room_category.xml index e9baf4ceb4..14d7118e43 100644 --- a/vector/src/main/res/layout/item_room_category.xml +++ b/vector/src/main/res/layout/item_room_category.xml @@ -11,8 +11,8 @@ android:foreground="?attr/selectableItemBackground" android:gravity="center_vertical" android:orientation="horizontal" - android:paddingStart="16dp" android:paddingVertical="8dp" + android:paddingStart="16dp" android:paddingEnd="@dimen/layout_horizontal_margin"> - + Date: Fri, 4 Feb 2022 18:02:09 +0100 Subject: [PATCH 08/54] Fix remove of imports --- .../app/features/home/room/list/SectionHeaderAdapter.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt index 7eb063eece..c96660942d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt @@ -18,10 +18,14 @@ package im.vector.app.features.home.room.list import android.view.LayoutInflater import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.DrawableCompat import androidx.recyclerview.widget.RecyclerView import im.vector.app.R import im.vector.app.core.epoxy.ClickListener +import im.vector.app.core.epoxy.onClick import im.vector.app.databinding.ItemRoomCategoryBinding +import im.vector.app.features.themes.ThemeUtils class SectionHeaderAdapter constructor( private val onClickAction: ClickListener From c7dae341c075ae6b500893c7d2a2e60bd28a316d Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Mon, 7 Feb 2022 09:55:24 +0100 Subject: [PATCH 09/54] (DRAFT) Room counter flow --- matrix-sdk-android/build.gradle | 3 ++ .../sdk/api/session/room/RoomService.kt | 6 ++++ .../session/room/DefaultRoomService.kt | 5 ++++ .../room/summary/RoomSummaryDataSource.kt | 29 +++++++++++++++++++ .../home/room/list/RoomCategoryItem.kt | 1 + .../home/room/list/RoomListFragment.kt | 12 ++++++++ .../room/list/RoomListSectionBuilderGroup.kt | 2 ++ .../room/list/RoomListSectionBuilderSpace.kt | 7 +++++ .../features/home/room/list/RoomsSection.kt | 3 ++ 9 files changed, 68 insertions(+) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 2b2c38e22a..8a66d188fd 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -73,6 +73,9 @@ android { kotlinOptions { jvmTarget = "11" + freeCompilerArgs += [ + "-Xopt-in=kotlin.RequiresOptIn" + ] } sourceSets { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index bca432320d..43d87999a6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.room import androidx.lifecycle.LiveData import androidx.paging.PagedList +import kotlinx.coroutines.flow.Flow import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership @@ -216,6 +217,11 @@ interface RoomService { pagedListConfig: PagedList.Config = defaultPagedListConfig, sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): UpdatableLivePageResult + /** + * Retrieve a flow on the the number of rooms. + */ + fun getRoomCountFlow(queryParams: RoomSummaryQueryParams): Flow + /** * TODO Doc */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index 4a02c55db0..0d78489fbd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations import androidx.paging.PagedList import com.zhuinden.monarchy.Monarchy +import kotlinx.coroutines.flow.Flow import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.RoomService @@ -109,6 +110,10 @@ internal class DefaultRoomService @Inject constructor( return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder) } + override fun getRoomCountFlow(queryParams: RoomSummaryQueryParams): Flow { + return roomSummaryDataSource.getCountFlow(queryParams) + } + override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount { return roomSummaryDataSource.getNotificationCountForRooms(queryParams) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index c9fc3c9575..cabef69e2e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -25,7 +25,12 @@ import androidx.paging.PagedList import com.zhuinden.monarchy.Monarchy import io.realm.Realm import io.realm.RealmQuery +import io.realm.kotlin.toFlow import io.realm.kotlin.where +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.isNormalized @@ -42,6 +47,7 @@ import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotification import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional +import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields @@ -55,6 +61,7 @@ import javax.inject.Inject internal class RoomSummaryDataSource @Inject constructor( @SessionDatabase private val monarchy: Monarchy, + private val realmSessionProvider: RealmSessionProvider, private val roomSummaryMapper: RoomSummaryMapper, private val queryStringValueProcessor: QueryStringValueProcessor ) { @@ -230,6 +237,28 @@ internal class RoomSummaryDataSource @Inject constructor( } } +// @OptIn(ExperimentalCoroutinesApi::class) +// fun getCountFlow(queryParams: RoomSummaryQueryParams): Flow = callbackFlow { +// val realmResult = realmSessionProvider.withRealm { realm -> +// roomSummariesQuery(realm, queryParams).findAllAsync() +// } +// val changeListener = RealmChangeListener> { +// trySendBlocking(it.size) +// .onFailure { throwable -> Timber.e(throwable) } +// } +// realmResult.addChangeListener(changeListener) +// awaitClose { realmResult.removeChangeListener(changeListener) } +// } + + fun getCountFlow(queryParams: RoomSummaryQueryParams): Flow = + // TODO handle properly threads and dispatchers otherwise use livedata of monarchy + realmSessionProvider + .withRealm { realm -> roomSummariesQuery(realm, queryParams).findAllAsync() } + .toFlow() + .map { it.size } + .flowOn(Dispatchers.IO) + + // TODO should we improve how we update notification count with flow ?? fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount { var notificationCount: RoomAggregateNotificationCount? = null monarchy.doWithRealm { realm -> diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt index bec3ccc643..8961e4f7c2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt @@ -29,6 +29,7 @@ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.onClick import im.vector.app.features.themes.ThemeUtils +// TODO check where it is used in the project @EpoxyModelClass(layout = R.layout.item_room_category) abstract class RoomCategoryItem : VectorEpoxyModel() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index 28849204c4..d6b529e1ca 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -287,6 +287,12 @@ class RoomListFragment @Inject constructor( )) checkEmptyState() } + // TODO use flow if possible ? + section.itemCount.observe(viewLifecycleOwner) { count -> + sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy( + itemCount = count + )) + } section.notificationCount.observe(viewLifecycleOwner) { counts -> sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy( notificationCount = counts.totalCount, @@ -326,6 +332,12 @@ class RoomListFragment @Inject constructor( isLoading = false)) checkEmptyState() } + // TODO use flow instead ? + section.itemCount.observe(viewLifecycleOwner) { count -> + sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy( + itemCount = count + )) + } section.notificationCount.observe(viewLifecycleOwner) { counts -> sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy( notificationCount = counts.totalCount, diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt index 77f61149f8..b90283e86e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt @@ -242,6 +242,7 @@ class RoomListSectionBuilderGroup( @StringRes nameRes: Int, notifyOfLocalEcho: Boolean = false, query: (RoomSummaryQueryParams.Builder) -> Unit) { + // TODO check when this class is used: difference with RoomListSectionBuilderSpace ? withQueryParams( { query.invoke(it) }, { roomQueryParams -> @@ -251,6 +252,7 @@ class RoomListSectionBuilderGroup( activeSpaceUpdaters.add(it) }.livePagedList .let { livePagedList -> + // TODO should we improve this ? // use it also as a source to update count livePagedList.asFlow() .onEach { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt index 296e61690b..b4c6240688 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt @@ -374,6 +374,7 @@ class RoomListSectionBuilderSpace( // use it also as a source to update count livePagedList.asFlow() .onEach { + // TODO should we improve this ? Timber.v("Thread space list: ${Thread.currentThread()}") sections.find { it.sectionName == name } ?.notificationCount @@ -398,6 +399,12 @@ class RoomListSectionBuilderSpace( ) ) } + + // TODO extract into a dedicated private method + session.getRoomCountFlow(roomQueryParams) + .onEach { count -> sections.find { section -> section.sectionName == name }?.itemCount?.postValue(count) } + .flowOn(Dispatchers.Default) + .launchIn(viewModelScope) } ) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt index 5eaae262a6..b8d03a30b0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt @@ -26,9 +26,12 @@ data class RoomsSection( val sectionName: String, // can be a paged list or a regular list val livePages: LiveData>? = null, + // TODO liveList is not used : can we delete ? val liveList: LiveData>? = null, val liveSuggested: LiveData? = null, val isExpanded: MutableLiveData = MutableLiveData(true), + // TODO expose a Flow instead ? + val itemCount: MutableLiveData = MutableLiveData(0), val notificationCount: MutableLiveData = MutableLiveData(RoomAggregateNotificationCount(0, 0)), val notifyOfLocalEcho: Boolean = false ) From 0aaa650ac3cece2de3e6e3ab8368ea88feac5708 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Mon, 7 Feb 2022 16:10:26 +0100 Subject: [PATCH 10/54] Using flow to show items counter --- dependencies.gradle | 3 +- matrix-sdk-android/build.gradle | 3 -- .../room/summary/RoomSummaryDataSource.kt | 25 +++++----------- vector/build.gradle | 1 + .../home/room/list/RoomListFragment.kt | 30 ++++++++++++------- .../room/list/RoomListSectionBuilderGroup.kt | 14 ++++++--- .../room/list/RoomListSectionBuilderSpace.kt | 23 +++++++------- .../features/home/room/list/RoomsSection.kt | 4 +-- 8 files changed, 56 insertions(+), 47 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index 87b8e3c12f..1f2a08b6a6 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -58,6 +58,7 @@ ext.libs = [ 'lifecycleCommon' : "androidx.lifecycle:lifecycle-common:$lifecycle", 'lifecycleLivedata' : "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle", 'lifecycleProcess' : "androidx.lifecycle:lifecycle-process:$lifecycle", + 'lifecycleRuntimeKtx' : "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle", 'datastore' : "androidx.datastore:datastore:1.0.0", 'datastorepreferences' : "androidx.datastore:datastore-preferences:1.0.0", 'pagingRuntimeKtx' : "androidx.paging:paging-runtime-ktx:2.1.2", @@ -141,4 +142,4 @@ ext.libs = [ 'timberJunitRule' : "net.lachlanmckee:timber-junit-rule:1.0.1", 'junit' : "junit:junit:4.13.2" ] -] \ No newline at end of file +] diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 8a66d188fd..2b2c38e22a 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -73,9 +73,6 @@ android { kotlinOptions { jvmTarget = "11" - freeCompilerArgs += [ - "-Xopt-in=kotlin.RequiresOptIn" - ] } sourceSets { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index cabef69e2e..eecbb0ecfe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -29,8 +29,10 @@ import io.realm.kotlin.toFlow import io.realm.kotlin.where import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.isNormalized @@ -63,7 +65,8 @@ internal class RoomSummaryDataSource @Inject constructor( @SessionDatabase private val monarchy: Monarchy, private val realmSessionProvider: RealmSessionProvider, private val roomSummaryMapper: RoomSummaryMapper, - private val queryStringValueProcessor: QueryStringValueProcessor + private val queryStringValueProcessor: QueryStringValueProcessor, + private val coroutineDispatchers: MatrixCoroutineDispatchers ) { fun getRoomSummary(roomIdOrAlias: String): RoomSummary? { @@ -237,28 +240,16 @@ internal class RoomSummaryDataSource @Inject constructor( } } -// @OptIn(ExperimentalCoroutinesApi::class) -// fun getCountFlow(queryParams: RoomSummaryQueryParams): Flow = callbackFlow { -// val realmResult = realmSessionProvider.withRealm { realm -> -// roomSummariesQuery(realm, queryParams).findAllAsync() -// } -// val changeListener = RealmChangeListener> { -// trySendBlocking(it.size) -// .onFailure { throwable -> Timber.e(throwable) } -// } -// realmResult.addChangeListener(changeListener) -// awaitClose { realmResult.removeChangeListener(changeListener) } -// } - fun getCountFlow(queryParams: RoomSummaryQueryParams): Flow = - // TODO handle properly threads and dispatchers otherwise use livedata of monarchy realmSessionProvider .withRealm { realm -> roomSummariesQuery(realm, queryParams).findAllAsync() } .toFlow() + // need to create the flow on a context dispatcher with a thread with attached Looper + .flowOn(coroutineDispatchers.main) .map { it.size } - .flowOn(Dispatchers.IO) + .flowOn(coroutineDispatchers.io) + .distinctUntilChanged() - // TODO should we improve how we update notification count with flow ?? fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount { var notificationCount: RoomAggregateNotificationCount? = null monarchy.doWithRealm { realm -> diff --git a/vector/build.gradle b/vector/build.gradle index 2b37c12323..2d9c097da8 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -355,6 +355,7 @@ dependencies { // Lifecycle implementation libs.androidx.lifecycleLivedata implementation libs.androidx.lifecycleProcess + implementation libs.androidx.lifecycleRuntimeKtx implementation libs.androidx.datastore implementation libs.androidx.datastorepreferences diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index d6b529e1ca..4e7e679ad5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -23,6 +23,8 @@ import android.view.View import android.view.ViewGroup import androidx.core.content.ContextCompat import androidx.core.view.isVisible +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.LinearLayoutManager @@ -50,8 +52,10 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel import im.vector.app.features.home.room.list.widget.NotifsFabMenuView import im.vector.app.features.notifications.NotificationDrawerManager +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -287,11 +291,14 @@ class RoomListFragment @Inject constructor( )) checkEmptyState() } - // TODO use flow if possible ? - section.itemCount.observe(viewLifecycleOwner) { count -> - sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy( - itemCount = count - )) + lifecycleScope.launch { + section.itemCount + .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) + .collect { count -> + sectionAdapter.updateSection( + sectionAdapter.roomsSectionData.copy(itemCount = count) + ) + } } section.notificationCount.observe(viewLifecycleOwner) { counts -> sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy( @@ -332,11 +339,14 @@ class RoomListFragment @Inject constructor( isLoading = false)) checkEmptyState() } - // TODO use flow instead ? - section.itemCount.observe(viewLifecycleOwner) { count -> - sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy( - itemCount = count - )) + lifecycleScope.launch { + section.itemCount + .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) + .collect { count -> + sectionAdapter.updateSection( + sectionAdapter.roomsSectionData.copy(itemCount = count) + ) + } } section.notificationCount.observe(viewLifecycleOwner) { counts -> sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy( diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt index b90283e86e..84d1450a5e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt @@ -68,11 +68,18 @@ class RoomListSectionBuilderGroup( it.memberships = Membership.activeMemberships() }, { qpm -> + // TODO find a way to show the filtered rooms count ? val name = stringProvider.getString(R.string.bottom_action_rooms) session.getFilteredPagedRoomSummariesLive(qpm) .let { updatableFilterLivePageResult -> onUpdatable(updatableFilterLivePageResult) - sections.add(RoomsSection(name, updatableFilterLivePageResult.livePagedList)) + sections.add( + RoomsSection( + sectionName = name, + livePages = updatableFilterLivePageResult.livePagedList, + itemCount = session.getRoomCountFlow(qpm) + ) + ) } } ) @@ -242,7 +249,6 @@ class RoomListSectionBuilderGroup( @StringRes nameRes: Int, notifyOfLocalEcho: Boolean = false, query: (RoomSummaryQueryParams.Builder) -> Unit) { - // TODO check when this class is used: difference with RoomListSectionBuilderSpace ? withQueryParams( { query.invoke(it) }, { roomQueryParams -> @@ -252,7 +258,6 @@ class RoomListSectionBuilderGroup( activeSpaceUpdaters.add(it) }.livePagedList .let { livePagedList -> - // TODO should we improve this ? // use it also as a source to update count livePagedList.asFlow() .onEach { @@ -267,7 +272,8 @@ class RoomListSectionBuilderGroup( RoomsSection( sectionName = name, livePages = livePagedList, - notifyOfLocalEcho = notifyOfLocalEcho + notifyOfLocalEcho = notifyOfLocalEcho, + itemCount = session.getRoomCountFlow(roomQueryParams) ) ) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt index b4c6240688..383dacdefb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt @@ -38,6 +38,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.ActiveSpaceFilter @@ -87,11 +88,18 @@ class RoomListSectionBuilderSpace( it.memberships = Membership.activeMemberships() }, { qpm -> + // TODO find a way to show the filtered rooms count ? val name = stringProvider.getString(R.string.bottom_action_rooms) session.getFilteredPagedRoomSummariesLive(qpm) .let { updatableFilterLivePageResult -> onUpdatable(updatableFilterLivePageResult) - sections.add(RoomsSection(name, updatableFilterLivePageResult.livePagedList)) + sections.add( + RoomsSection( + sectionName = name, + livePages = updatableFilterLivePageResult.livePagedList, + itemCount = session.getRoomCountFlow(qpm) + ) + ) } } ) @@ -261,7 +269,8 @@ class RoomListSectionBuilderSpace( RoomsSection( sectionName = stringProvider.getString(R.string.suggested_header), liveSuggested = liveSuggestedRooms, - notifyOfLocalEcho = false + notifyOfLocalEcho = false, + itemCount = suggestedRoomsFlow.map { suggestions -> suggestions.size } ) ) } @@ -374,7 +383,6 @@ class RoomListSectionBuilderSpace( // use it also as a source to update count livePagedList.asFlow() .onEach { - // TODO should we improve this ? Timber.v("Thread space list: ${Thread.currentThread()}") sections.find { it.sectionName == name } ?.notificationCount @@ -395,16 +403,11 @@ class RoomListSectionBuilderSpace( RoomsSection( sectionName = name, livePages = livePagedList, - notifyOfLocalEcho = notifyOfLocalEcho + notifyOfLocalEcho = notifyOfLocalEcho, + itemCount = session.getRoomCountFlow(roomQueryParams) ) ) } - - // TODO extract into a dedicated private method - session.getRoomCountFlow(roomQueryParams) - .onEach { count -> sections.find { section -> section.sectionName == name }?.itemCount?.postValue(count) } - .flowOn(Dispatchers.Default) - .launchIn(viewModelScope) } ) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt index b8d03a30b0..be00f3f61b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.list import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.paging.PagedList +import kotlinx.coroutines.flow.Flow import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount @@ -30,8 +31,7 @@ data class RoomsSection( val liveList: LiveData>? = null, val liveSuggested: LiveData? = null, val isExpanded: MutableLiveData = MutableLiveData(true), - // TODO expose a Flow instead ? - val itemCount: MutableLiveData = MutableLiveData(0), + val itemCount: Flow, val notificationCount: MutableLiveData = MutableLiveData(RoomAggregateNotificationCount(0, 0)), val notifyOfLocalEcho: Boolean = false ) From 53c24d20b062a65c5706e49edadf03074e60e0b2 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Mon, 7 Feb 2022 17:50:27 +0100 Subject: [PATCH 11/54] Moving notification badge on the right side --- .../main/res/layout/item_room_category.xml | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/vector/src/main/res/layout/item_room_category.xml b/vector/src/main/res/layout/item_room_category.xml index 14d7118e43..a09bec7883 100644 --- a/vector/src/main/res/layout/item_room_category.xml +++ b/vector/src/main/res/layout/item_room_category.xml @@ -1,5 +1,5 @@ - + android:paddingHorizontal="@dimen/layout_horizontal_margin" + android:paddingVertical="8dp"> @@ -52,10 +61,12 @@ android:gravity="center" android:minWidth="16dp" android:minHeight="16dp" - android:paddingStart="4dp" - android:paddingEnd="4dp" + android:paddingHorizontal="4dp" android:textColor="?colorOnError" + app:layout_constraintBottom_toBottomOf="@+id/roomCategoryTitleView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@+id/roomCategoryTitleView" tools:background="@drawable/bg_unread_highlight" tools:text="24" /> - + From ad9d36e58c45a8b4e28e7fca1c24db90586b9902 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Mon, 7 Feb 2022 17:54:13 +0100 Subject: [PATCH 12/54] Setting item count text only when > 0 --- .../vector/app/features/home/room/list/SectionHeaderAdapter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt index c96660942d..b6a3777ded 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt @@ -88,7 +88,7 @@ class SectionHeaderAdapter constructor( DrawableCompat.setTint(it, tintColor) } roomCategoryCounterView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null) - roomCategoryCounterView.text = roomsSectionData.itemCount.toString() + roomCategoryCounterView.text = roomsSectionData.itemCount.takeIf { it > 0 }?.toString().orEmpty() roomCategoryUnreadCounterBadgeView.render(UnreadCounterBadgeView.State(roomsSectionData.notificationCount, roomsSectionData.isHighlighted)) } } From d5345160fa6781a5081dfc66c35b8e7488311cf5 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Tue, 8 Feb 2022 14:44:34 +0100 Subject: [PATCH 13/54] Adding TODO --- .../home/room/list/RoomCategoryItem.kt | 2 +- .../home/room/list/RoomListFragment.kt | 33 +++++++++---------- .../room/list/RoomListSectionBuilderGroup.kt | 2 +- .../room/list/RoomListSectionBuilderSpace.kt | 5 +-- .../features/home/room/list/RoomsSection.kt | 2 +- 5 files changed, 21 insertions(+), 23 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt index 8961e4f7c2..31129139bf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt @@ -29,7 +29,6 @@ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.onClick import im.vector.app.features.themes.ThemeUtils -// TODO check where it is used in the project @EpoxyModelClass(layout = R.layout.item_room_category) abstract class RoomCategoryItem : VectorEpoxyModel() { @@ -41,6 +40,7 @@ abstract class RoomCategoryItem : VectorEpoxyModel() { override fun bind(holder: Holder) { super.bind(holder) + // TODO do we need to update the binding? do not understand how it is used in the app val tintColor = ThemeUtils.getColor(holder.rootView.context, R.attr.vctr_content_secondary) val expandedArrowDrawableRes = if (expanded) R.drawable.ic_expand_more else R.drawable.ic_expand_less val expandedArrowDrawable = ContextCompat.getDrawable(holder.rootView.context, expandedArrowDrawableRes)?.also { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index 4e7e679ad5..bcc062f3a8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -291,15 +291,7 @@ class RoomListFragment @Inject constructor( )) checkEmptyState() } - lifecycleScope.launch { - section.itemCount - .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) - .collect { count -> - sectionAdapter.updateSection( - sectionAdapter.roomsSectionData.copy(itemCount = count) - ) - } - } + listenItemCount(section, sectionAdapter) section.notificationCount.observe(viewLifecycleOwner) { counts -> sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy( notificationCount = counts.totalCount, @@ -323,6 +315,7 @@ class RoomListFragment @Inject constructor( )) checkEmptyState() } + listenItemCount(section, sectionAdapter) section.isExpanded.observe(viewLifecycleOwner) { _ -> refreshCollapseStates() } @@ -339,15 +332,7 @@ class RoomListFragment @Inject constructor( isLoading = false)) checkEmptyState() } - lifecycleScope.launch { - section.itemCount - .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) - .collect { count -> - sectionAdapter.updateSection( - sectionAdapter.roomsSectionData.copy(itemCount = count) - ) - } - } + listenItemCount(section, sectionAdapter) section.notificationCount.observe(viewLifecycleOwner) { counts -> sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy( notificationCount = counts.totalCount, @@ -395,6 +380,18 @@ class RoomListFragment @Inject constructor( } } + private fun listenItemCount(section: RoomsSection, sectionAdapter: SectionHeaderAdapter) { + lifecycleScope.launch { + section.itemCount + .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) + .collect { count -> + sectionAdapter.updateSection( + sectionAdapter.roomsSectionData.copy(itemCount = count) + ) + } + } + } + private fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) { when (quickAction) { is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt index 84d1450a5e..a29481e401 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt @@ -68,7 +68,7 @@ class RoomListSectionBuilderGroup( it.memberships = Membership.activeMemberships() }, { qpm -> - // TODO find a way to show the filtered rooms count ? + // TODO find a clean way to listen query params changes to show the filtered rooms count val name = stringProvider.getString(R.string.bottom_action_rooms) session.getFilteredPagedRoomSummariesLive(qpm) .let { updatableFilterLivePageResult -> diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt index 383dacdefb..cd055c2e4a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt @@ -88,7 +88,7 @@ class RoomListSectionBuilderSpace( it.memberships = Membership.activeMemberships() }, { qpm -> - // TODO find a way to show the filtered rooms count ? + // TODO find a clean way to listen query params changes to show the filtered rooms count val name = stringProvider.getString(R.string.bottom_action_rooms) session.getFilteredPagedRoomSummariesLive(qpm) .let { updatableFilterLivePageResult -> @@ -404,7 +404,8 @@ class RoomListSectionBuilderSpace( sectionName = name, livePages = livePagedList, notifyOfLocalEcho = notifyOfLocalEcho, - itemCount = session.getRoomCountFlow(roomQueryParams) + // TODO not working when switching spaces, how does the query is updated in this case? + itemCount = session.getRoomCountFlow(roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())) ) ) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt index be00f3f61b..b504c2b515 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt @@ -27,7 +27,7 @@ data class RoomsSection( val sectionName: String, // can be a paged list or a regular list val livePages: LiveData>? = null, - // TODO liveList is not used : can we delete ? + // TODO liveList is not used : can we delete this livedata? val liveList: LiveData>? = null, val liveSuggested: LiveData? = null, val isExpanded: MutableLiveData = MutableLiveData(true), From b72c357dd1131fe1c6fb5d8762f493ceb751b34d Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Tue, 8 Feb 2022 14:59:00 +0100 Subject: [PATCH 14/54] Removing unused imports --- .../sdk/internal/session/room/summary/RoomSummaryDataSource.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index eecbb0ecfe..319a181309 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -27,7 +27,6 @@ import io.realm.Realm import io.realm.RealmQuery import io.realm.kotlin.toFlow import io.realm.kotlin.where -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn From c79aa267c3add0ad63932b2914f03c6005f10488 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Wed, 9 Feb 2022 10:10:23 +0100 Subject: [PATCH 15/54] Fix switching space use case --- .../features/home/room/list/RoomListSectionBuilderSpace.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt index cd055c2e4a..4839612afc 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt @@ -399,13 +399,15 @@ class RoomListSectionBuilderSpace( .flowOn(Dispatchers.Default) .launchIn(viewModelScope) + val itemCountFlow = livePagedList.asFlow() + .flatMapLatest { session.getRoomCountFlow(roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())) } + sections.add( RoomsSection( sectionName = name, livePages = livePagedList, notifyOfLocalEcho = notifyOfLocalEcho, - // TODO not working when switching spaces, how does the query is updated in this case? - itemCount = session.getRoomCountFlow(roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())) + itemCount = itemCountFlow ) ) } From 3d27d9d2d2e73886ad751d90c8b7dcca33dc160d Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Wed, 9 Feb 2022 10:40:29 +0100 Subject: [PATCH 16/54] Fix filtering use case --- .../session/room/UpdatableLivePageResult.kt | 4 +-- .../room/summary/RoomSummaryDataSource.kt | 14 +++++---- .../room/list/RoomListSectionBuilderGroup.kt | 12 ++++--- .../room/list/RoomListSectionBuilderSpace.kt | 31 +++++++++---------- .../home/room/list/RoomListViewModel.kt | 4 +-- .../spaces/manage/SpaceAddRoomsViewModel.kt | 21 +++++-------- 6 files changed, 40 insertions(+), 46 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableLivePageResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableLivePageResult.kt index b83f57f5ef..db87f913b9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableLivePageResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableLivePageResult.kt @@ -22,10 +22,8 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary interface UpdatableLivePageResult { val livePagedList: LiveData> - - fun updateQuery(builder: (RoomSummaryQueryParams) -> RoomSummaryQueryParams) - val liveBoundaries: LiveData + var queryParams: RoomSummaryQueryParams } data class ResultBoundaries( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index 319a181309..ea4f102fa5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -228,14 +228,16 @@ internal class RoomSummaryDataSource @Inject constructor( return object : UpdatableLivePageResult { override val livePagedList: LiveData> = mapped - override fun updateQuery(builder: (RoomSummaryQueryParams) -> RoomSummaryQueryParams) { - realmDataSourceFactory.updateQuery { - roomSummariesQuery(it, builder.invoke(queryParams)).process(sortOrder) - } - } - override val liveBoundaries: LiveData get() = boundaries + + override var queryParams: RoomSummaryQueryParams = queryParams + set(value) { + field = value + realmDataSourceFactory.updateQuery { + roomSummariesQuery(it, value).process(sortOrder) + } + } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt index a29481e401..005a9ef63f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt @@ -28,6 +28,7 @@ import im.vector.app.features.invite.showInvites import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -68,16 +69,19 @@ class RoomListSectionBuilderGroup( it.memberships = Membership.activeMemberships() }, { qpm -> - // TODO find a clean way to listen query params changes to show the filtered rooms count val name = stringProvider.getString(R.string.bottom_action_rooms) session.getFilteredPagedRoomSummariesLive(qpm) .let { updatableFilterLivePageResult -> onUpdatable(updatableFilterLivePageResult) + + val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow() + .flatMapLatest { session.getRoomCountFlow(updatableFilterLivePageResult.queryParams) } + sections.add( RoomsSection( sectionName = name, livePages = updatableFilterLivePageResult.livePagedList, - itemCount = session.getRoomCountFlow(qpm) + itemCount = itemCountFlow ) ) } @@ -116,9 +120,7 @@ class RoomListSectionBuilderGroup( .onEach { groupingMethod -> val selectedGroupId = (groupingMethod.orNull() as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId activeGroupAwareQueries.onEach { updater -> - updater.updateQuery { query -> - query.copy(activeGroupId = selectedGroupId) - } + updater.queryParams = updater.queryParams.copy(activeGroupId = selectedGroupId) } }.launchIn(coroutineScope) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt index 4839612afc..d3922843ac 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt @@ -88,16 +88,19 @@ class RoomListSectionBuilderSpace( it.memberships = Membership.activeMemberships() }, { qpm -> - // TODO find a clean way to listen query params changes to show the filtered rooms count val name = stringProvider.getString(R.string.bottom_action_rooms) session.getFilteredPagedRoomSummariesLive(qpm) .let { updatableFilterLivePageResult -> onUpdatable(updatableFilterLivePageResult) + + val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow() + .flatMapLatest { session.getRoomCountFlow(updatableFilterLivePageResult.queryParams) } + sections.add( RoomsSection( sectionName = name, livePages = updatableFilterLivePageResult.livePagedList, - itemCount = session.getRoomCountFlow(qpm) + itemCount = itemCountFlow ) ) } @@ -347,11 +350,9 @@ class RoomListSectionBuilderSpace( RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL -> { activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater { override fun updateForSpaceId(roomId: String?) { - it.updateQuery { - it.copy( - activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId) - ) - } + it.queryParams = roomQueryParams.copy( + activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId) + ) } }) } @@ -359,17 +360,13 @@ class RoomListSectionBuilderSpace( activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater { override fun updateForSpaceId(roomId: String?) { if (roomId != null) { - it.updateQuery { - it.copy( - activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId) - ) - } + it.queryParams = roomQueryParams.copy( + activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId) + ) } else { - it.updateQuery { - it.copy( - activeSpaceFilter = ActiveSpaceFilter.None - ) - } + it.queryParams = roomQueryParams.copy( + activeSpaceFilter = ActiveSpaceFilter.None + ) } } }) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 4a81a8b526..ec8b01876b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -192,8 +192,8 @@ class RoomListViewModel @AssistedInject constructor( roomFilter = action.filter ) } - updatableQuery?.updateQuery { - it.copy( + updatableQuery?.apply { + queryParams = queryParams.copy( displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.NORMALIZED) ) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt index 8fa269d439..383b76c946 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt @@ -17,7 +17,6 @@ package im.vector.app.features.spaces.manage import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.viewModelScope import androidx.paging.PagedList import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading @@ -143,17 +142,13 @@ class SpaceAddRoomsViewModel @AssistedInject constructor( override fun handle(action: SpaceAddRoomActions) { when (action) { - is SpaceAddRoomActions.UpdateFilter -> { - updatableLivePageResult.updateQuery { - it.copy( - displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) - ) - } - updatableLiveSpacePageResult.updateQuery { - it.copy( - displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) - ) - } + is SpaceAddRoomActions.UpdateFilter -> { + updatableLivePageResult.queryParams = updatableLivePageResult.queryParams.copy( + displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) + ) + updatableLivePageResult.queryParams = updatableLivePageResult.queryParams.copy( + displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) + ) setState { copy( currentFilter = action.filter @@ -164,7 +159,7 @@ class SpaceAddRoomsViewModel @AssistedInject constructor( selectionList[action.roomSummary.roomId] = (selectionList[action.roomSummary.roomId] ?: false).not() selectionListLiveData.postValue(selectionList.toMap()) } - SpaceAddRoomActions.Save -> { + SpaceAddRoomActions.Save -> { doAddSelectedRooms() } } From 30c6518630ff889056443e91e94d4b553ca6381c Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Wed, 9 Feb 2022 11:16:37 +0100 Subject: [PATCH 17/54] Updating remaining category item --- .../vector/app/features/home/room/list/RoomCategoryItem.kt | 7 +++++-- .../im/vector/app/features/home/room/list/RoomsSection.kt | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt index 31129139bf..e9def535db 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt @@ -40,21 +40,24 @@ abstract class RoomCategoryItem : VectorEpoxyModel() { override fun bind(holder: Holder) { super.bind(holder) - // TODO do we need to update the binding? do not understand how it is used in the app val tintColor = ThemeUtils.getColor(holder.rootView.context, R.attr.vctr_content_secondary) val expandedArrowDrawableRes = if (expanded) R.drawable.ic_expand_more else R.drawable.ic_expand_less val expandedArrowDrawable = ContextCompat.getDrawable(holder.rootView.context, expandedArrowDrawableRes)?.also { DrawableCompat.setTint(it, tintColor) } holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted)) - holder.titleView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null) holder.titleView.text = title + with(holder.counterView) { + text = "" + setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null) + } holder.rootView.onClick(listener) } class Holder : VectorEpoxyHolder() { val unreadCounterBadgeView by bind(R.id.roomCategoryUnreadCounterBadgeView) val titleView by bind(R.id.roomCategoryTitleView) + val counterView by bind(R.id.roomCategoryCounterView) val rootView by bind(R.id.roomCategoryRootView) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt index b504c2b515..357df5ecd3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt @@ -27,7 +27,6 @@ data class RoomsSection( val sectionName: String, // can be a paged list or a regular list val livePages: LiveData>? = null, - // TODO liveList is not used : can we delete this livedata? val liveList: LiveData>? = null, val liveSuggested: LiveData? = null, val isExpanded: MutableLiveData = MutableLiveData(true), From 20749e04cb9388b444bdbf7274cecc36e1bfc397 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Wed, 9 Feb 2022 11:26:55 +0100 Subject: [PATCH 18/54] Fix coding style --- .../home/room/list/RoomListSectionBuilderSpace.kt | 5 ++++- vector/src/main/res/layout/item_room_category.xml | 10 ++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt index d3922843ac..6726243996 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt @@ -397,7 +397,10 @@ class RoomListSectionBuilderSpace( .launchIn(viewModelScope) val itemCountFlow = livePagedList.asFlow() - .flatMapLatest { session.getRoomCountFlow(roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())) } + .flatMapLatest { + val queryParams = roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId()) + session.getRoomCountFlow(queryParams) + } sections.add( RoomsSection( diff --git a/vector/src/main/res/layout/item_room_category.xml b/vector/src/main/res/layout/item_room_category.xml index a09bec7883..14b9c0fb84 100644 --- a/vector/src/main/res/layout/item_room_category.xml +++ b/vector/src/main/res/layout/item_room_category.xml @@ -23,7 +23,6 @@ android:maxLines="1" android:textAllCaps="true" android:textColor="?vctr_content_primary" - android:textStyle="normal" app:layout_constraintEnd_toStartOf="@id/roomCategoryCounterView" app:layout_constraintHorizontal_bias="0" app:layout_constraintHorizontal_chainStyle="packed" @@ -43,12 +42,11 @@ android:gravity="center_vertical" android:maxLines="1" android:textColor="?vctr_content_secondary" - android:textStyle="normal" app:drawableTint="?vctr_content_secondary" - app:layout_constraintBottom_toBottomOf="@+id/roomCategoryTitleView" + app:layout_constraintBottom_toBottomOf="@id/roomCategoryTitleView" app:layout_constraintEnd_toStartOf="@id/roomCategoryUnreadCounterBadgeView" app:layout_constraintStart_toEndOf="@id/roomCategoryTitleView" - app:layout_constraintTop_toTopOf="@+id/roomCategoryTitleView" + app:layout_constraintTop_toTopOf="@id/roomCategoryTitleView" app:layout_constraintWidth="wrap_content_constrained" tools:drawableEnd="@drawable/ic_expand_more" tools:text="14" /> @@ -63,9 +61,9 @@ android:minHeight="16dp" android:paddingHorizontal="4dp" android:textColor="?colorOnError" - app:layout_constraintBottom_toBottomOf="@+id/roomCategoryTitleView" + app:layout_constraintBottom_toBottomOf="@id/roomCategoryTitleView" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="@+id/roomCategoryTitleView" + app:layout_constraintTop_toTopOf="@id/roomCategoryTitleView" tools:background="@drawable/bg_unread_highlight" tools:text="24" /> From f327eaa3f1e7b5fd40e1d448336ad3111adaa5ac Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Thu, 10 Feb 2022 15:23:09 +0100 Subject: [PATCH 19/54] Adding item counter in add existing room to space screen --- .../home/room/list/RoomCategoryItem.kt | 3 +- .../room/list/RoomListSectionBuilderGroup.kt | 1 + .../room/list/RoomListSectionBuilderSpace.kt | 2 + .../spaces/manage/AddRoomListController.kt | 9 ++- .../spaces/manage/SpaceAddRoomFragment.kt | 70 ++++++++++++------- .../spaces/manage/SpaceAddRoomsViewModel.kt | 32 +++++++-- 6 files changed, 85 insertions(+), 32 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt index e9def535db..6c872e0f99 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt @@ -33,6 +33,7 @@ import im.vector.app.features.themes.ThemeUtils abstract class RoomCategoryItem : VectorEpoxyModel() { @EpoxyAttribute lateinit var title: String + @EpoxyAttribute var itemCount: Int = 0 @EpoxyAttribute var expanded: Boolean = false @EpoxyAttribute var unreadNotificationCount: Int = 0 @EpoxyAttribute var showHighlighted: Boolean = false @@ -48,7 +49,7 @@ abstract class RoomCategoryItem : VectorEpoxyModel() { holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted)) holder.titleView.text = title with(holder.counterView) { - text = "" + text = itemCount.takeIf { it > 0 }?.toString().orEmpty() setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null) } holder.rootView.onClick(listener) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt index 005a9ef63f..ec7915ba34 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt @@ -76,6 +76,7 @@ class RoomListSectionBuilderGroup( val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow() .flatMapLatest { session.getRoomCountFlow(updatableFilterLivePageResult.queryParams) } + .distinctUntilChanged() sections.add( RoomsSection( diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt index 6726243996..f82dbd43e1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt @@ -95,6 +95,7 @@ class RoomListSectionBuilderSpace( val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow() .flatMapLatest { session.getRoomCountFlow(updatableFilterLivePageResult.queryParams) } + .distinctUntilChanged() sections.add( RoomsSection( @@ -401,6 +402,7 @@ class RoomListSectionBuilderSpace( val queryParams = roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId()) session.getRoomCountFlow(queryParams) } + .distinctUntilChanged() sections.add( RoomsSection( diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt index 3164daf634..c2d63aa8d3 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt @@ -94,6 +94,12 @@ class AddRoomListController @Inject constructor( } var totalSize: Int = 0 + set(value) { + if (value != field) { + field = value + requestForcedModelBuild() + } + } var selectedItems: Map = emptyMap() set(value) { @@ -120,7 +126,8 @@ class AddRoomListController @Inject constructor( add( RoomCategoryItem_().apply { id("header") - title(host.sectionName ?: "") + title(host.sectionName.orEmpty()) + itemCount(host.totalSize) expanded(host.expanded) listener { host.expanded = !host.expanded diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt index bcf0a8a949..8d6a351013 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt @@ -22,6 +22,8 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.LinearLayoutManager @@ -35,9 +37,12 @@ import im.vector.app.core.extensions.cleanup import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentSpaceAddRoomsBinding +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.room.model.RoomSummary import reactivecircus.flowbinding.appcompat.queryTextChanges import javax.inject.Inject @@ -169,48 +174,63 @@ class SpaceAddRoomFragment @Inject constructor( } private fun setupRecyclerView() { - val concatAdapter = ConcatAdapter() - spaceEpoxyController.sectionName = getString(R.string.spaces_header) - roomEpoxyController.sectionName = getString(R.string.rooms_header) - spaceEpoxyController.listener = this - roomEpoxyController.listener = this + setupSpaceSection() + setupRoomSection() + setupDmSection() - viewModel.updatableLiveSpacePageResult.liveBoundaries.observe(viewLifecycleOwner) { + views.roomList.adapter = ConcatAdapter().apply { + addAdapter(roomEpoxyController.adapter) + addAdapter(spaceEpoxyController.adapter) + addAdapter(dmEpoxyController.adapter) + } + } + + private fun setupSpaceSection() { + spaceEpoxyController.sectionName = getString(R.string.spaces_header) + spaceEpoxyController.listener = this + viewModel.spaceUpdatableLivePageResult.liveBoundaries.observe(viewLifecycleOwner) { spaceEpoxyController.boundaryChange(it) } - viewModel.updatableLiveSpacePageResult.livePagedList.observe(viewLifecycleOwner) { - spaceEpoxyController.totalSize = it.size + viewModel.spaceUpdatableLivePageResult.livePagedList.observe(viewLifecycleOwner) { spaceEpoxyController.submitList(it) } + listenItemCount(viewModel.spaceCountFlow) { spaceEpoxyController.totalSize = it } + } - viewModel.updatableLivePageResult.liveBoundaries.observe(viewLifecycleOwner) { + private fun setupRoomSection() { + roomEpoxyController.sectionName = getString(R.string.rooms_header) + roomEpoxyController.listener = this + + viewModel.roomUpdatableLivePageResult.liveBoundaries.observe(viewLifecycleOwner) { roomEpoxyController.boundaryChange(it) } - viewModel.updatableLivePageResult.livePagedList.observe(viewLifecycleOwner) { - roomEpoxyController.totalSize = it.size + viewModel.roomUpdatableLivePageResult.livePagedList.observe(viewLifecycleOwner) { roomEpoxyController.submitList(it) } - + listenItemCount(viewModel.roomCountFlow) { roomEpoxyController.totalSize = it } views.roomList.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) views.roomList.setHasFixedSize(true) + } - concatAdapter.addAdapter(roomEpoxyController.adapter) - concatAdapter.addAdapter(spaceEpoxyController.adapter) - + private fun setupDmSection() { // This controller can be disabled depending on the space type (public or not) - viewModel.updatableDMLivePageResult.liveBoundaries.observe(viewLifecycleOwner) { - dmEpoxyController.boundaryChange(it) - } - viewModel.updatableDMLivePageResult.livePagedList.observe(viewLifecycleOwner) { - dmEpoxyController.totalSize = it.size - dmEpoxyController.submitList(it) - } dmEpoxyController.sectionName = getString(R.string.direct_chats_header) dmEpoxyController.listener = this + viewModel.dmUpdatableLivePageResult.liveBoundaries.observe(viewLifecycleOwner) { + dmEpoxyController.boundaryChange(it) + } + viewModel.dmUpdatableLivePageResult.livePagedList.observe(viewLifecycleOwner) { + dmEpoxyController.submitList(it) + } + listenItemCount(viewModel.dmCountFlow) { dmEpoxyController.totalSize = it } + } - concatAdapter.addAdapter(dmEpoxyController.adapter) - - views.roomList.adapter = concatAdapter + private fun listenItemCount(itemCountFlow: Flow, onEachAction: (Int) -> Unit) { + lifecycleScope.launch { + itemCountFlow + .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) + .collect { count -> onEachAction(count) } + } } override fun onBackPressed(toolbarButton: Boolean): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt index 383b76c946..7d99c53f23 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt @@ -17,6 +17,7 @@ package im.vector.app.features.spaces.manage import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asFlow import androidx.paging.PagedList import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading @@ -29,6 +30,9 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.query.ActiveSpaceFilter @@ -59,7 +63,7 @@ class SpaceAddRoomsViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - val updatableLiveSpacePageResult: UpdatableLivePageResult by lazy { + val spaceUpdatableLivePageResult: UpdatableLivePageResult by lazy { session.getFilteredPagedRoomSummariesLive( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) @@ -78,7 +82,13 @@ class SpaceAddRoomsViewModel @AssistedInject constructor( ) } - val updatableLivePageResult: UpdatableLivePageResult by lazy { + val spaceCountFlow: Flow by lazy { + spaceUpdatableLivePageResult.livePagedList.asFlow() + .flatMapLatest { session.getRoomCountFlow(spaceUpdatableLivePageResult.queryParams) } + .distinctUntilChanged() + } + + val roomUpdatableLivePageResult: UpdatableLivePageResult by lazy { session.getFilteredPagedRoomSummariesLive( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) @@ -98,7 +108,13 @@ class SpaceAddRoomsViewModel @AssistedInject constructor( ) } - val updatableDMLivePageResult: UpdatableLivePageResult by lazy { + val roomCountFlow: Flow by lazy { + roomUpdatableLivePageResult.livePagedList.asFlow() + .flatMapLatest { session.getRoomCountFlow(roomUpdatableLivePageResult.queryParams) } + .distinctUntilChanged() + } + + val dmUpdatableLivePageResult: UpdatableLivePageResult by lazy { session.getFilteredPagedRoomSummariesLive( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) @@ -118,6 +134,12 @@ class SpaceAddRoomsViewModel @AssistedInject constructor( ) } + val dmCountFlow: Flow by lazy { + dmUpdatableLivePageResult.livePagedList.asFlow() + .flatMapLatest { session.getRoomCountFlow(dmUpdatableLivePageResult.queryParams) } + .distinctUntilChanged() + } + private val selectionList = mutableMapOf() val selectionListLiveData = MutableLiveData>() @@ -143,10 +165,10 @@ class SpaceAddRoomsViewModel @AssistedInject constructor( override fun handle(action: SpaceAddRoomActions) { when (action) { is SpaceAddRoomActions.UpdateFilter -> { - updatableLivePageResult.queryParams = updatableLivePageResult.queryParams.copy( + roomUpdatableLivePageResult.queryParams = roomUpdatableLivePageResult.queryParams.copy( displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) ) - updatableLivePageResult.queryParams = updatableLivePageResult.queryParams.copy( + roomUpdatableLivePageResult.queryParams = roomUpdatableLivePageResult.queryParams.copy( displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) ) setState { From 291d7d762731d05780c15f6d33d989778a3e4445 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Thu, 17 Feb 2022 17:21:07 +0100 Subject: [PATCH 20/54] Fix some doc comment --- .../java/org/matrix/android/sdk/api/session/room/RoomService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index 43d87999a6..f506b147df 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -218,7 +218,7 @@ interface RoomService { sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): UpdatableLivePageResult /** - * Retrieve a flow on the the number of rooms. + * Retrieve a flow on the number of rooms. */ fun getRoomCountFlow(queryParams: RoomSummaryQueryParams): Flow From a826a50c1031d65e9ccfc69561afce3f91ea0a5f Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Thu, 17 Feb 2022 17:21:46 +0100 Subject: [PATCH 21/54] Renaming observe item count method --- .../app/features/home/room/list/RoomListFragment.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index bcc062f3a8..0300a72e9c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -52,7 +52,6 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel import im.vector.app.features.home.room.list.widget.NotifsFabMenuView import im.vector.app.features.notifications.NotificationDrawerManager -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -291,7 +290,7 @@ class RoomListFragment @Inject constructor( )) checkEmptyState() } - listenItemCount(section, sectionAdapter) + observeItemCount(section, sectionAdapter) section.notificationCount.observe(viewLifecycleOwner) { counts -> sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy( notificationCount = counts.totalCount, @@ -315,7 +314,7 @@ class RoomListFragment @Inject constructor( )) checkEmptyState() } - listenItemCount(section, sectionAdapter) + observeItemCount(section, sectionAdapter) section.isExpanded.observe(viewLifecycleOwner) { _ -> refreshCollapseStates() } @@ -332,7 +331,7 @@ class RoomListFragment @Inject constructor( isLoading = false)) checkEmptyState() } - listenItemCount(section, sectionAdapter) + observeItemCount(section, sectionAdapter) section.notificationCount.observe(viewLifecycleOwner) { counts -> sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy( notificationCount = counts.totalCount, @@ -380,7 +379,7 @@ class RoomListFragment @Inject constructor( } } - private fun listenItemCount(section: RoomsSection, sectionAdapter: SectionHeaderAdapter) { + private fun observeItemCount(section: RoomsSection, sectionAdapter: SectionHeaderAdapter) { lifecycleScope.launch { section.itemCount .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) From 0942c6d64889af2fc7a23e341ba2ed8d3449c918 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Thu, 17 Feb 2022 17:22:18 +0100 Subject: [PATCH 22/54] Removing some with() code pattern --- .../home/room/list/RoomCategoryItem.kt | 6 ++---- .../home/room/list/SectionHeaderAdapter.kt | 18 ++++++++---------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt index 6c872e0f99..6057072e41 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt @@ -48,10 +48,8 @@ abstract class RoomCategoryItem : VectorEpoxyModel() { } holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted)) holder.titleView.text = title - with(holder.counterView) { - text = itemCount.takeIf { it > 0 }?.toString().orEmpty() - setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null) - } + holder.counterView.text = itemCount.takeIf { it > 0 }?.toString().orEmpty() + holder.counterView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null) holder.rootView.onClick(listener) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt index b6a3777ded..2e6436d21d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt @@ -80,17 +80,15 @@ class SectionHeaderAdapter constructor( } fun bind(roomsSectionData: RoomsSectionData) { - with(binding) { - roomCategoryTitleView.text = roomsSectionData.name - val tintColor = ThemeUtils.getColor(root.context, R.attr.vctr_content_secondary) - val expandedArrowDrawableRes = if (roomsSectionData.isExpanded) R.drawable.ic_expand_more else R.drawable.ic_expand_less - val expandedArrowDrawable = ContextCompat.getDrawable(root.context, expandedArrowDrawableRes)?.also { - DrawableCompat.setTint(it, tintColor) - } - roomCategoryCounterView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null) - roomCategoryCounterView.text = roomsSectionData.itemCount.takeIf { it > 0 }?.toString().orEmpty() - roomCategoryUnreadCounterBadgeView.render(UnreadCounterBadgeView.State(roomsSectionData.notificationCount, roomsSectionData.isHighlighted)) + binding.roomCategoryTitleView.text = roomsSectionData.name + val tintColor = ThemeUtils.getColor(binding.root.context, R.attr.vctr_content_secondary) + val expandedArrowDrawableRes = if (roomsSectionData.isExpanded) R.drawable.ic_expand_more else R.drawable.ic_expand_less + val expandedArrowDrawable = ContextCompat.getDrawable(binding.root.context, expandedArrowDrawableRes)?.also { + DrawableCompat.setTint(it, tintColor) } + binding.roomCategoryCounterView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null) + binding.roomCategoryCounterView.text = roomsSectionData.itemCount.takeIf { it > 0 }?.toString().orEmpty() + binding.roomCategoryUnreadCounterBadgeView.render(UnreadCounterBadgeView.State(roomsSectionData.notificationCount, roomsSectionData.isHighlighted)) } companion object { From 8bb0a5cb4ccade5f1167c3e8a4cd1f654d4a3738 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Thu, 17 Feb 2022 17:31:44 +0100 Subject: [PATCH 23/54] Fixing wrong remove of import --- .../im/vector/app/features/home/room/list/RoomListFragment.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index 0300a72e9c..4265eebe62 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -52,6 +52,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel import im.vector.app.features.home.room.list.widget.NotifsFabMenuView import im.vector.app.features.notifications.NotificationDrawerManager +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch From 2176129b11414f87a6ce597a973ad242538a8880 Mon Sep 17 00:00:00 2001 From: ericdecanini Date: Mon, 14 Mar 2022 11:46:48 +0100 Subject: [PATCH 24/54] Moves additional top space usage to bubble view --- .../detail/timeline/item/AbsMessageItem.kt | 3 -- .../timeline/style/TimelineMessageLayout.kt | 49 ++++++++++--------- .../detail/timeline/view/MessageBubbleView.kt | 2 + 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt index 7b80acbc8e..2fac9df665 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -106,8 +106,6 @@ abstract class AbsMessageItem : AbsBaseMessageItem holder.timeView.isVisible = false } - holder.additionalTopSpace.isVisible = attributes.informationData.messageLayout.addTopMargin - // Render send state indicator holder.sendStateImageView.render(attributes.informationData.sendStateDecoration) holder.eventSendingIndicator.isVisible = attributes.informationData.sendStateDecoration == SendStateDecoration.SENDING_MEDIA @@ -157,7 +155,6 @@ abstract class AbsMessageItem : AbsBaseMessageItem abstract class Holder(@IdRes stubId: Int) : AbsBaseMessageItem.Holder(stubId) { - val additionalTopSpace by bind(R.id.additionalTopSpace) val avatarImageView by bind(R.id.messageAvatarImageView) val memberNameView by bind(R.id.messageMemberNameView) val timeView by bind(R.id.messageTimeView) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt index 202d8b05da..c0e668e013 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt @@ -21,43 +21,44 @@ import im.vector.app.R import kotlinx.parcelize.Parcelize sealed interface TimelineMessageLayout : Parcelable { + val layoutRes: Int val showAvatar: Boolean val showDisplayName: Boolean - val addTopMargin: Boolean val showTimestamp: Boolean @Parcelize - data class Default(override val showAvatar: Boolean, - override val showDisplayName: Boolean, - override val showTimestamp: Boolean, - override val addTopMargin: Boolean = false, - // Keep defaultLayout generated on epoxy items - override val layoutRes: Int = 0) : TimelineMessageLayout + data class Default( + override val showAvatar: Boolean, + override val showDisplayName: Boolean, + override val showTimestamp: Boolean, + // Keep defaultLayout generated on epoxy items + override val layoutRes: Int = 0, + ) : TimelineMessageLayout @Parcelize data class Bubble( - override val showAvatar: Boolean, - override val showDisplayName: Boolean, - override val showTimestamp: Boolean = true, - override val addTopMargin: Boolean = false, - val isIncoming: Boolean, - val isPseudoBubble: Boolean, - val cornersRadius: CornersRadius, - val timestampAsOverlay: Boolean, - override val layoutRes: Int = if (isIncoming) { - R.layout.item_timeline_event_bubble_incoming_base - } else { - R.layout.item_timeline_event_bubble_outgoing_base - } + override val showAvatar: Boolean, + override val showDisplayName: Boolean, + override val showTimestamp: Boolean = true, + val addTopMargin: Boolean = false, + val isIncoming: Boolean, + val isPseudoBubble: Boolean, + val cornersRadius: CornersRadius, + val timestampAsOverlay: Boolean, + override val layoutRes: Int = if (isIncoming) { + R.layout.item_timeline_event_bubble_incoming_base + } else { + R.layout.item_timeline_event_bubble_outgoing_base + }, ) : TimelineMessageLayout { @Parcelize data class CornersRadius( - val topStartRadius: Float, - val topEndRadius: Float, - val bottomStartRadius: Float, - val bottomEndRadius: Float + val topStartRadius: Float, + val topEndRadius: Float, + val bottomStartRadius: Float, + val bottomEndRadius: Float, ) : Parcelable } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt index 93eae9a1d3..7f21e2a248 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt @@ -139,6 +139,8 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri marginStart = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end) } } + + views.additionalTopSpace.isVisible = messageLayout.addTopMargin } private fun TimelineMessageLayout.Bubble.CornersRadius.toFloatArray(): FloatArray { From 4a1bf11168188911861b0e00d524bbe07ca33976 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 9 Feb 2022 11:24:32 +0000 Subject: [PATCH 25/54] adding base choose name fragment with UI --- .../java/im/vector/app/features/onboarding/OnboardingVariant.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariant.kt index 91c125fa5b..0d49ccb699 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariant.kt @@ -22,4 +22,3 @@ interface OnboardingVariant { fun onNewIntent(intent: Intent?) fun initUiAndData(isFirstCreation: Boolean) fun setIsLoading(isLoading: Boolean) -} From 074cde45193b06d1f09ce71483f5437a60bb9451 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 10 Feb 2022 11:51:59 +0000 Subject: [PATCH 26/54] add click handling for the display name actions --- .../im/vector/app/features/onboarding/OnboardingViewModel.kt | 1 + .../vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 413745f98c..f88282dad8 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -48,6 +48,7 @@ import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.auth.AuthenticationService diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index e336419e3f..6eb2933e00 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -429,4 +429,5 @@ class FtueAuthVariant( option = commonOption ) } + } From 1c8091483240dfae65f510c8a18a59084b484b2a Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 10 Feb 2022 14:23:18 +0000 Subject: [PATCH 27/54] adding tests around the onboarding view model - cases for the personalisation and display name actions --- .../vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index 6eb2933e00..e336419e3f 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -429,5 +429,4 @@ class FtueAuthVariant( option = commonOption ) } - } From 232524ddc3cdfed04c9791d7d66e58729c018154 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 16 Feb 2022 14:04:49 +0000 Subject: [PATCH 28/54] adding barebones profile picture fragment with ability to select a user avatar --- .../java/im/vector/app/features/onboarding/OnboardingVariant.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariant.kt index 0d49ccb699..91c125fa5b 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariant.kt @@ -22,3 +22,4 @@ interface OnboardingVariant { fun onNewIntent(intent: Intent?) fun initUiAndData(isFirstCreation: Boolean) fun setIsLoading(isLoading: Boolean) +} From 567fd9a13d46f25b8e12336877710790fb1d20d2 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 18 Feb 2022 12:21:38 +0000 Subject: [PATCH 29/54] extracting method for the handling of the profile picture selection --- .../im/vector/app/features/onboarding/OnboardingViewModel.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index f88282dad8..413745f98c 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -48,7 +48,6 @@ import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode import kotlinx.coroutines.Job -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.auth.AuthenticationService From 6c4381fda5d6fe73f9b26110d579184e022ffa1b Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 18 Feb 2022 16:15:20 +0000 Subject: [PATCH 30/54] adding dedicated camera icon for choosing profile picture --- .../layout/fragment_ftue_profile_picture.xml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/vector/src/main/res/layout/fragment_ftue_profile_picture.xml b/vector/src/main/res/layout/fragment_ftue_profile_picture.xml index 0def088062..dd3b954191 100644 --- a/vector/src/main/res/layout/fragment_ftue_profile_picture.xml +++ b/vector/src/main/res/layout/fragment_ftue_profile_picture.xml @@ -90,6 +90,25 @@ + + Date: Thu, 10 Feb 2022 11:51:59 +0000 Subject: [PATCH 31/54] add click handling for the display name actions --- .../vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index e336419e3f..6eb2933e00 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -429,4 +429,5 @@ class FtueAuthVariant( option = commonOption ) } + } From 7ded9007db6fa3401897e80db98f28bd026eb09a Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 10 Feb 2022 11:51:59 +0000 Subject: [PATCH 32/54] add click handling for the display name actions --- .../im/vector/app/features/onboarding/OnboardingViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 413745f98c..f88282dad8 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -48,6 +48,7 @@ import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.auth.AuthenticationService From 10e4fd1707a39b0cd9adfdf6185305bdfaef9f04 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 18 Feb 2022 11:30:47 +0000 Subject: [PATCH 33/54] updating upstream avatar on profile picture save and continue step - moves the personalisation state to a dedicated model to allow for back and forth state restoration --- vector/src/test/java/im/vector/app/test/Extensions.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/test/java/im/vector/app/test/Extensions.kt b/vector/src/test/java/im/vector/app/test/Extensions.kt index 3ff041dc11..56c01b1357 100644 --- a/vector/src/test/java/im/vector/app/test/Extensions.kt +++ b/vector/src/test/java/im/vector/app/test/Extensions.kt @@ -21,6 +21,7 @@ import im.vector.app.core.platform.VectorViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction import kotlinx.coroutines.CoroutineScope +import org.hamcrest.Matcher fun String.trimIndentOneLine() = trimIndent().replace("\n", "") From 50740b14499b010d1875435d3086a3dfdecd5b2a Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 18 Feb 2022 12:21:38 +0000 Subject: [PATCH 34/54] extracting method for the handling of the profile picture selection --- .../im/vector/app/features/onboarding/OnboardingViewModel.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index f88282dad8..413745f98c 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -48,7 +48,6 @@ import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode import kotlinx.coroutines.Job -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.auth.AuthenticationService From 3df4f1e099e6249c755fe773e6fa35ea46fa6cb2 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 24 Feb 2022 16:41:35 +0000 Subject: [PATCH 35/54] adding entry points for injecting and overriding the homeserver capabilities --- .../debug/features/DebugVectorOverrides.kt | 25 ++++++ .../settings/DebugPrivateSettingsFragment.kt | 6 ++ .../DebugPrivateSettingsViewActions.kt | 8 +- .../settings/DebugPrivateSettingsViewModel.kt | 33 ++++++- .../settings/DebugPrivateSettingsViewState.kt | 15 ++++ .../debug/settings/OverrideDropdownView.kt | 87 +++++++++++++++++++ .../debug/settings/PrivateSettingOverrides.kt | 42 +++++++++ .../fragment_debug_private_settings.xml | 18 ++++ .../res/layout/view_boolean_dropdown.xml | 25 ++++++ .../app/features/DefaultVectorOverrides.kt | 7 ++ .../layout/fragment_ftue_profile_picture.xml | 19 ---- 11 files changed, 260 insertions(+), 25 deletions(-) create mode 100644 vector/src/debug/java/im/vector/app/features/debug/settings/OverrideDropdownView.kt create mode 100644 vector/src/debug/java/im/vector/app/features/debug/settings/PrivateSettingOverrides.kt create mode 100644 vector/src/debug/res/layout/view_boolean_dropdown.xml diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt index 4394f5436e..b2206b81c9 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt @@ -22,13 +22,17 @@ import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.preferencesDataStore +import im.vector.app.features.HomeserverCapabilitiesOverride import im.vector.app.features.VectorOverrides +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import org.matrix.android.sdk.api.extensions.orFalse private val Context.dataStore: DataStore by preferencesDataStore(name = "vector_overrides") private val keyForceDialPadDisplay = booleanPreferencesKey("force_dial_pad_display") private val keyForceLoginFallback = booleanPreferencesKey("force_login_fallback") +private val forceCanChangeDisplayName = booleanPreferencesKey("force_can_change_display_name") +private val forceCanChangeAvatar = booleanPreferencesKey("force_can_change_avatar") class DebugVectorOverrides(private val context: Context) : VectorOverrides { @@ -40,6 +44,13 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides { preferences[keyForceLoginFallback].orFalse() } + override val forceHomeserverCapabilities = context.dataStore.data.map { preferences -> + HomeserverCapabilitiesOverride( + canChangeDisplayName = preferences[forceCanChangeDisplayName], + canChangeAvatar = preferences[forceCanChangeAvatar] + ) + } + suspend fun setForceDialPadDisplay(force: Boolean) { context.dataStore.edit { settings -> settings[keyForceDialPadDisplay] = force @@ -51,4 +62,18 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides { settings[keyForceLoginFallback] = force } } + + suspend fun updateHomeserverCapabilities(block: HomeserverCapabilitiesOverride.() -> HomeserverCapabilitiesOverride) { + val capabilitiesOverride = block(forceHomeserverCapabilities.firstOrNull() ?: HomeserverCapabilitiesOverride(null, null)) + context.dataStore.edit { settings -> + when (capabilitiesOverride.canChangeDisplayName) { + null -> settings.remove(forceCanChangeDisplayName) + else -> settings[forceCanChangeDisplayName] = capabilitiesOverride.canChangeDisplayName + } + when (capabilitiesOverride.canChangeAvatar) { + null -> settings.remove(forceCanChangeAvatar) + else -> settings[forceCanChangeAvatar] = capabilitiesOverride.canChangeAvatar + } + } + } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt index b54d776901..38253fe7c2 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt @@ -50,6 +50,12 @@ class DebugPrivateSettingsFragment : VectorBaseFragment + viewModel.handle(DebugPrivateSettingsViewActions.SetDisplayNameCapabilityOverride(option)) + } + views.forceChangeAvatarCapability.bind(it.homeserverCapabilityOverrides.avatar) { option -> + viewModel.handle(DebugPrivateSettingsViewActions.SetAvatarCapabilityOverride(option)) + } views.forceLoginFallback.isChecked = it.forceLoginFallback } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt index 1c76cf6fb2..5dea3dce64 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt @@ -18,7 +18,9 @@ package im.vector.app.features.debug.settings import im.vector.app.core.platform.VectorViewModelAction -sealed class DebugPrivateSettingsViewActions : VectorViewModelAction { - data class SetDialPadVisibility(val force: Boolean) : DebugPrivateSettingsViewActions() - data class SetForceLoginFallbackEnabled(val force: Boolean) : DebugPrivateSettingsViewActions() +sealed interface DebugPrivateSettingsViewActions : VectorViewModelAction { + data class SetDialPadVisibility(val force: Boolean) : DebugPrivateSettingsViewActions + data class SetForceLoginFallbackEnabled(val force: Boolean) : DebugPrivateSettingsViewActions + data class SetDisplayNameCapabilityOverride(val option: BooleanHomeserverCapabilitiesOverride?) : DebugPrivateSettingsViewActions + data class SetAvatarCapabilityOverride(val option: BooleanHomeserverCapabilitiesOverride?) : DebugPrivateSettingsViewActions } diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt index 8d040d4773..4d9c72168c 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt @@ -22,9 +22,12 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.debug.features.DebugVectorOverrides +import im.vector.app.features.debug.settings.DebugPrivateSettingsViewActions.SetAvatarCapabilityOverride +import im.vector.app.features.debug.settings.DebugPrivateSettingsViewActions.SetDisplayNameCapabilityOverride import kotlinx.coroutines.launch class DebugPrivateSettingsViewModel @AssistedInject constructor( @@ -40,10 +43,10 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() init { - observeVectorDataStore() + observeVectorOverrides() } - private fun observeVectorDataStore() { + private fun observeVectorOverrides() { debugVectorOverrides.forceDialPad.setOnEach { copy( dialPadVisible = it @@ -52,13 +55,23 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor( debugVectorOverrides.forceLoginFallback.setOnEach { copy(forceLoginFallback = it) } + debugVectorOverrides.forceHomeserverCapabilities.setOnEach { + val activeDisplayNameOption = BooleanHomeserverCapabilitiesOverride.from(it.canChangeDisplayName) + val activeAvatarOption = BooleanHomeserverCapabilitiesOverride.from(it.canChangeAvatar) + copy(homeserverCapabilityOverrides = homeserverCapabilityOverrides.copy( + displayName = homeserverCapabilityOverrides.displayName.copy(activeOption = activeDisplayNameOption), + avatar = homeserverCapabilityOverrides.displayName.copy(activeOption = activeAvatarOption), + )) + } } override fun handle(action: DebugPrivateSettingsViewActions) { when (action) { is DebugPrivateSettingsViewActions.SetDialPadVisibility -> handleSetDialPadVisibility(action) is DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled -> handleSetForceLoginFallbackEnabled(action) - } + is SetDisplayNameCapabilityOverride -> handSetDisplayNameCapabilityOverride(action) + is SetAvatarCapabilityOverride -> handSetAvatarCapabilityOverride(action) + }.exhaustive } private fun handleSetDialPadVisibility(action: DebugPrivateSettingsViewActions.SetDialPadVisibility) { @@ -72,4 +85,18 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor( debugVectorOverrides.setForceLoginFallback(action.force) } } + + private fun handSetDisplayNameCapabilityOverride(action: SetDisplayNameCapabilityOverride) { + viewModelScope.launch { + val forceDisplayName = action.option.toBoolean() + debugVectorOverrides.updateHomeserverCapabilities { copy(canChangeDisplayName = forceDisplayName) } + } + } + + private fun handSetAvatarCapabilityOverride(action: SetAvatarCapabilityOverride) { + viewModelScope.launch { + val forceAvatar = action.option.toBoolean() + debugVectorOverrides.updateHomeserverCapabilities { copy(canChangeAvatar = forceAvatar) } + } + } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt index 7fca29af8c..749b11a744 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt @@ -17,8 +17,23 @@ package im.vector.app.features.debug.settings import com.airbnb.mvrx.MavericksState +import im.vector.app.features.debug.settings.OverrideDropdownView.OverrideDropdown data class DebugPrivateSettingsViewState( val dialPadVisible: Boolean = false, val forceLoginFallback: Boolean = false, + val homeserverCapabilityOverrides: HomeserverCapabilityOverrides = HomeserverCapabilityOverrides() ) : MavericksState + +data class HomeserverCapabilityOverrides( + val displayName: OverrideDropdown = OverrideDropdown( + label = "Override display name capability", + activeOption = null, + options = listOf(BooleanHomeserverCapabilitiesOverride.ForceEnabled, BooleanHomeserverCapabilitiesOverride.ForceDisabled) + ), + val avatar: OverrideDropdown = OverrideDropdown( + label = "Override avatar capability", + activeOption = null, + options = listOf(BooleanHomeserverCapabilitiesOverride.ForceEnabled, BooleanHomeserverCapabilitiesOverride.ForceDisabled) + ) +) diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/OverrideDropdownView.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/OverrideDropdownView.kt new file mode 100644 index 0000000000..546c9df9b7 --- /dev/null +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/OverrideDropdownView.kt @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.debug.settings + +import android.content.Context +import android.util.AttributeSet +import android.view.Gravity +import android.view.View +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.LinearLayout +import android.widget.TextView +import androidx.appcompat.widget.AppCompatSpinner +import im.vector.app.R + +class OverrideDropdownView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null +) : LinearLayout(context, attrs) { + + private val labelView: TextView + private val optionsSpinner: AppCompatSpinner + + init { + orientation = HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + inflate(context, R.layout.view_boolean_dropdown, this) + labelView = findViewById(R.id.feature_label) + optionsSpinner = findViewById(R.id.feature_options) + } + + fun bind(feature: OverrideDropdown, listener: Listener) { + labelView.text = feature.label + + optionsSpinner.apply { + val arrayAdapter = ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item) + val options = listOf("Inactive") + feature.options.map { it.label } + arrayAdapter.addAll(options) + adapter = arrayAdapter + + feature.activeOption?.let { + setSelection(options.indexOf(it.label), false) + } + + onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + when (position) { + 0 -> listener.onOverrideSelected(option = null) + else -> listener.onOverrideSelected(feature.options[position - 1]) + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + // do nothing + } + } + } + } + + fun interface Listener { + fun onOverrideSelected(option: T?) + } + + data class OverrideDropdown( + val label: String, + val options: List, + val activeOption: T?, + ) +} + +interface OverrideOption { + val label: String +} diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/PrivateSettingOverrides.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/PrivateSettingOverrides.kt new file mode 100644 index 0000000000..316e8fb901 --- /dev/null +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/PrivateSettingOverrides.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.debug.settings + +sealed interface BooleanHomeserverCapabilitiesOverride : OverrideOption { + + companion object { + fun from(value: Boolean?) = when (value) { + null -> null + true -> ForceEnabled + false -> ForceDisabled + } + } + + object ForceEnabled : BooleanHomeserverCapabilitiesOverride { + override val label = "Force enabled" + } + + object ForceDisabled : BooleanHomeserverCapabilitiesOverride { + override val label = "Force disabled" + } +} + +fun BooleanHomeserverCapabilitiesOverride?.toBoolean() = when (this) { + null -> null + BooleanHomeserverCapabilitiesOverride.ForceDisabled -> false + BooleanHomeserverCapabilitiesOverride.ForceEnabled -> true +} diff --git a/vector/src/debug/res/layout/fragment_debug_private_settings.xml b/vector/src/debug/res/layout/fragment_debug_private_settings.xml index 6760c68169..c42ad68dce 100644 --- a/vector/src/debug/res/layout/fragment_debug_private_settings.xml +++ b/vector/src/debug/res/layout/fragment_debug_private_settings.xml @@ -31,6 +31,24 @@ android:layout_height="wrap_content" android:text="Force login and registration fallback" /> + + + + diff --git a/vector/src/debug/res/layout/view_boolean_dropdown.xml b/vector/src/debug/res/layout/view_boolean_dropdown.xml new file mode 100644 index 0000000000..a231980797 --- /dev/null +++ b/vector/src/debug/res/layout/view_boolean_dropdown.xml @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt b/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt index 4128fdbe3c..daa0d9e0bd 100644 --- a/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt +++ b/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt @@ -22,9 +22,16 @@ import kotlinx.coroutines.flow.flowOf interface VectorOverrides { val forceDialPad: Flow val forceLoginFallback: Flow + val forceHomeserverCapabilities: Flow? } +data class HomeserverCapabilitiesOverride( + val canChangeDisplayName: Boolean?, + val canChangeAvatar: Boolean? +) + class DefaultVectorOverrides : VectorOverrides { override val forceDialPad = flowOf(false) override val forceLoginFallback = flowOf(false) + override val forceHomeserverCapabilities: Flow? = null } diff --git a/vector/src/main/res/layout/fragment_ftue_profile_picture.xml b/vector/src/main/res/layout/fragment_ftue_profile_picture.xml index dd3b954191..0def088062 100644 --- a/vector/src/main/res/layout/fragment_ftue_profile_picture.xml +++ b/vector/src/main/res/layout/fragment_ftue_profile_picture.xml @@ -90,25 +90,6 @@ - - Date: Thu, 24 Feb 2022 17:37:19 +0000 Subject: [PATCH 36/54] dynamically changing the account created layout based on if the homeserver supports personalisation --- .../onboarding/OnboardingViewModel.kt | 25 +++++++--- .../onboarding/OnboardingViewState.kt | 7 ++- .../FtueAuthAccountCreatedFragment.kt | 9 ++++ .../onboarding/ftueauth/FtueAuthVariant.kt | 16 +++---- .../layout/fragment_ftue_account_created.xml | 46 +++++++++++++++++-- 5 files changed, 80 insertions(+), 23 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 413745f98c..2688c2e811 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -48,6 +48,7 @@ import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.auth.AuthenticationService @@ -80,6 +81,7 @@ class OnboardingViewModel @AssistedInject constructor( private val stringProvider: StringProvider, private val homeServerHistoryService: HomeServerHistoryService, private val vectorFeatures: VectorFeatures, + private val vectorOverrides: VectorOverrides, private val analyticsTracker: AnalyticsTracker, private val uriFilenameResolver: UriFilenameResolver, private val vectorOverrides: VectorOverrides @@ -762,15 +764,24 @@ class OnboardingViewModel @AssistedInject constructor( authenticationService.reset() session.configureAndStart(applicationContext) - setState { - copy( - asyncLoginAction = Success(Unit) - ) - } when (isAccountCreated) { - true -> _viewEvents.post(OnboardingViewEvents.OnAccountCreated) - false -> _viewEvents.post(OnboardingViewEvents.OnAccountSignedIn) + true -> { + val homeServerCapabilities = session.getHomeServerCapabilities() + val capabilityOverrides = vectorOverrides.forceHomeserverCapabilities()?.firstOrNull() + val personalizationState = state.personalizationState.copy( + supportsChangingDisplayName = capabilityOverrides?.canChangeDisplayName ?: homeServerCapabilities.canChangeDisplayName, + supportsChangingProfilePicture = capabilityOverrides?.canChangeAvatar ?: homeServerCapabilities.canChangeAvatar + ) + setState { + copy(asyncLoginAction = Success(Unit), personalizationState = personalizationState) + } + _viewEvents.post(OnboardingViewEvents.OnAccountCreated) + } + false -> { + setState { copy(asyncLoginAction = Success(Unit)) } + _viewEvents.post(OnboardingViewEvents.OnAccountSignedIn) + } } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt index bd5d93ae4d..76c6998b1f 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt @@ -97,6 +97,11 @@ enum class OnboardingFlow { @Parcelize data class PersonalizationState( + val supportsChangingDisplayName: Boolean = false, + val supportsChangingProfilePicture: Boolean = false, val displayName: String? = null, val selectedPictureUri: Uri? = null -) : Parcelable +) : Parcelable { + + fun supportsPersonalization() = supportsChangingDisplayName || supportsChangingProfilePicture +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt index d021fd2813..ec2ad12f8d 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt @@ -20,11 +20,13 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.isVisible import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.databinding.FragmentFtueAccountCreatedBinding import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewEvents +import im.vector.app.features.onboarding.OnboardingViewState import javax.inject.Inject class FtueAuthAccountCreatedFragment @Inject constructor( @@ -44,6 +46,13 @@ class FtueAuthAccountCreatedFragment @Inject constructor( views.accountCreatedSubtitle.text = getString(R.string.ftue_account_created_subtitle, activeSessionHolder.getActiveSession().myUserId) views.accountCreatedPersonalize.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnPersonalizeProfile)) } views.accountCreatedTakeMeHome.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome)) } + views.accountCreatedTakeMeHomeCta.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome)) } + } + + override fun updateWithState(state: OnboardingViewState) { + val canPersonalize = state.personalizationState.supportsPersonalization() + views.personalizeButtonGroup.isVisible = canPersonalize + views.takeMeHomeButtonGroup.isVisible = !canPersonalize } override fun resetViewModel() { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index 6eb2933e00..fc9eeb4334 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -230,7 +230,7 @@ class FtueAuthVariant( FtueAuthUseCaseFragment::class.java, option = commonOption) } - OnboardingViewEvents.OnAccountCreated -> onAccountCreated() + is OnboardingViewEvents.OnAccountCreated -> onAccountCreated() OnboardingViewEvents.OnAccountSignedIn -> onAccountSignedIn() OnboardingViewEvents.OnPersonalizeProfile -> onPersonalizeProfile() OnboardingViewEvents.OnTakeMeHome -> navigateToHome(createdAccount = true) @@ -399,15 +399,11 @@ class FtueAuthVariant( } private fun onAccountCreated() { - if (vectorFeatures.isOnboardingPersonalizeEnabled()) { - activity.supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) - activity.replaceFragment( - views.loginFragmentContainer, - FtueAuthAccountCreatedFragment::class.java, - ) - } else { - navigateToHome(createdAccount = true) - } + activity.supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) + activity.replaceFragment( + views.loginFragmentContainer, + FtueAuthAccountCreatedFragment::class.java + ) } private fun navigateToHome(createdAccount: Boolean) { diff --git a/vector/src/main/res/layout/fragment_ftue_account_created.xml b/vector/src/main/res/layout/fragment_ftue_account_created.xml index 1985af1d5e..65bcdf2b63 100644 --- a/vector/src/main/res/layout/fragment_ftue_account_created.xml +++ b/vector/src/main/res/layout/fragment_ftue_account_created.xml @@ -86,6 +86,14 @@ app:layout_constraintBottom_toTopOf="@id/accountCreatedPersonalize" app:layout_constraintTop_toBottomOf="@id/accountCreatedSubtitle" /> + +