diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 0d5f1424e5..6658c7c3cd 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -5,16 +5,28 @@ # Build debug version of the RiotX application, from the develop branch and the features branches steps: - - label: "Assemble Debug version" + - label: "Assemble GPlay Debug version" agents: # We use a medium sized instance instead of the normal small ones because # gradle build is long queue: "medium" commands: - "./gradlew clean lintGplayRelease assembleGplayDebug --stacktrace" - - "./gradlew lintFdroidRelease assembleFdroidDebug --stacktrace" artifact_paths: - "vector/build/outputs/apk/gplay/debug/*.apk" + branches: "develop feature/*" + plugins: + - docker#v3.1.0: + image: "runmymind/docker-android-sdk" + + - label: "Assemble FDroid Debug version" + agents: + # We use a medium sized instance instead of the normal small ones because + # gradle build is long + queue: "medium" + commands: + - "./gradlew clean lintFdroidRelease assembleFdroidDebug --stacktrace" + artifact_paths: - "vector/build/outputs/apk/fdroid/debug/*.apk" branches: "develop feature/*" plugins: diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt index d947e59326..864a9d6f57 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt @@ -81,13 +81,11 @@ interface Session : /** * This method start the sync thread. */ - @MainThread fun startSync() /** * This method stop the sync thread. */ - @MainThread fun stopSync() /** @@ -99,7 +97,6 @@ interface Session : /** * This method allow to close a session. It does stop some services. */ - @MainThread fun close() /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt index a93b3e7d8f..5ae49f78f2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt @@ -177,11 +177,7 @@ data class Event( * @return The curve25519 key that sent this event. */ fun getSenderKey(): String? { - return if (null != mClearEvent) { - mClearEvent!!.mSenderCurve25519Key - } else { - mSenderCurve25519Key - } + return mClearEvent?.mSenderCurve25519Key ?: mSenderCurve25519Key } /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt index ad9264675a..ca3b99b6bf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt @@ -47,7 +47,7 @@ interface MembershipService { */ fun getRoomMemberIdsLive(): LiveData> - fun getNumberOfJoinedMembers() : Int + fun getNumberOfJoinedMembers(): Int /** * Invite a user in the room @@ -55,13 +55,12 @@ interface MembershipService { fun invite(userId: String, callback: MatrixCallback) /** - * Join the room + * Join the room, or accept an invitation. */ fun join(callback: MatrixCallback) /** - * Leave the room. - * + * Leave the room, or reject an invitation. */ fun leave(callback: MatrixCallback) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt index f6de3589a5..b54f17dbd9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt @@ -16,8 +16,8 @@ package im.vector.matrix.android.api.session.room.model -import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.model.tag.RoomTag +import im.vector.matrix.android.api.session.room.timeline.TimelineEvent /** * This class holds some data of a room. @@ -29,7 +29,7 @@ data class RoomSummary( val topic: String = "", val avatarUrl: String = "", val isDirect: Boolean = false, - val lastMessage: Event? = null, + val latestEvent: TimelineEvent? = null, val otherMemberIds: List = emptyList(), val notificationCount: Int = 0, val highlightCount: Int = 0, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt index 0303512821..0ebb7ebd88 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt @@ -16,24 +16,37 @@ package im.vector.matrix.android.internal.database.mapper +import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.tag.RoomTag +import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.internal.database.model.RoomSummaryEntity +import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory +import javax.inject.Inject -internal object RoomSummaryMapper { +internal class RoomSummaryMapper @Inject constructor( + private val timelineEventFactory: TimelineEventFactory, + private val monarchy: Monarchy) { fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary { val tags = roomSummaryEntity.tags.map { RoomTag(it.tagName, it.tagOrder) } + val latestEvent = roomSummaryEntity.latestEvent?.let { + var ev: TimelineEvent? = null + monarchy.doWithRealm { realm -> + ev = timelineEventFactory.create(it, realm) + } + ev + } return RoomSummary( roomId = roomSummaryEntity.roomId, displayName = roomSummaryEntity.displayName ?: "", topic = roomSummaryEntity.topic ?: "", avatarUrl = roomSummaryEntity.avatarUrl ?: "", isDirect = roomSummaryEntity.isDirect, - lastMessage = roomSummaryEntity.lastMessage?.asDomain(), + latestEvent = latestEvent, otherMemberIds = roomSummaryEntity.otherMemberIds.toList(), highlightCount = roomSummaryEntity.highlightCount, notificationCount = roomSummaryEntity.notificationCount, @@ -42,7 +55,3 @@ internal object RoomSummaryMapper { ) } } - -internal fun RoomSummaryEntity.asDomain(): RoomSummary { - return RoomSummaryMapper.map(this) -} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt index 32f27adb1c..5e3a975264 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt @@ -27,7 +27,7 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "", var displayName: String? = "", var avatarUrl: String? = "", var topic: String? = "", - var lastMessage: EventEntity? = null, + var latestEvent: EventEntity? = null, var heroes: RealmList = RealmList(), var joinedMembersCount: Int? = 0, var invitedMembersCount: Int? = 0, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt index 6f4db6f4b4..f7a71c4752 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt @@ -16,10 +16,12 @@ package im.vector.matrix.android.internal.database.query +import im.vector.matrix.android.internal.database.helper.addSendingEvent import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity.LinkFilterMode.* import im.vector.matrix.android.internal.database.model.EventEntityFields +import im.vector.matrix.android.internal.database.model.RoomEntity import io.realm.Realm import io.realm.RealmList import io.realm.RealmQuery @@ -65,7 +67,14 @@ internal fun EventEntity.Companion.latestEvent(realm: Realm, roomId: String, includedTypes: List = emptyList(), excludedTypes: List = emptyList()): EventEntity? { - val query = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.events?.where() + + val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null + val eventList = if (roomEntity.sendingTimelineEvents.isNotEmpty()) { + roomEntity.sendingTimelineEvents + } else { + ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.events + } + val query = eventList?.where() if (includedTypes.isNotEmpty()) { query?.`in`(EventEntityFields.TYPE, includedTypes.toTypedArray()) } else if (excludedTypes.isNotEmpty()) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index e77cfd3c2f..e98b459a49 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -20,6 +20,7 @@ import android.content.Context import android.os.Looper import androidx.annotation.MainThread import androidx.lifecycle.LiveData +import androidx.work.WorkManager import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.data.SessionParams @@ -102,7 +103,6 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se SyncWorker.stopAnyBackgroundSync(context) } - @MainThread override fun startSync() { assert(isOpen) if (!syncThread.isAlive) { @@ -113,16 +113,14 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se } } - @MainThread override fun stopSync() { assert(isOpen) syncThread.kill() } - @MainThread override fun close() { - assertMainThread() assert(isOpen) + stopSync() liveEntityObservers.forEach { it.dispose() } cryptoService.close() if (monarchy.isMonarchyThreadOpen) { @@ -153,6 +151,11 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se override fun onSuccess(data: Unit) { Timber.w("SIGN_OUT: clear cache -> SUCCESS: clear crypto cache") cryptoService.clearCryptoCache(MatrixCallbackDelegate(callback)) + + WorkManager.getInstance(context).also { + it.cancelAllWork() + it.pruneWork() + } } override fun onFailure(failure: Throwable) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt index 3e2958d51a..66cc74055c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt @@ -29,7 +29,7 @@ import im.vector.matrix.android.api.session.room.send.SendService import im.vector.matrix.android.api.session.room.state.StateService import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.internal.database.RealmLiveData -import im.vector.matrix.android.internal.database.mapper.asDomain +import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields import im.vector.matrix.android.internal.database.query.where @@ -38,6 +38,7 @@ import javax.inject.Inject internal class DefaultRoom @Inject constructor(override val roomId: String, private val monarchy: Monarchy, + private val roomSummaryMapper: RoomSummaryMapper, private val timelineService: TimelineService, private val sendService: SendService, private val stateService: StateService, @@ -58,7 +59,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) } Transformations.map(liveRealmData) { results -> - val roomSummaries = results.map { it.asDomain() } + val roomSummaries = results.map { roomSummaryMapper.map(it) } if (roomSummaries.isEmpty()) { // Create a dummy RoomSummary to avoid Crash during Sign Out or clear cache @@ -72,7 +73,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, override val roomSummary: RoomSummary? get() { var sum: RoomSummaryEntity? = monarchy.fetchCopied { RoomSummaryEntity.where(it, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME).findFirst() } - return sum?.asDomain() + return sum?.let { roomSummaryMapper.map(it) } } override fun isEncrypted(): Boolean { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt index cd729c93d0..2c0f1ce90c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt @@ -23,12 +23,11 @@ import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams -import im.vector.matrix.android.internal.database.mapper.asDomain +import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.room.create.CreateRoomTask import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith @@ -36,6 +35,7 @@ import im.vector.matrix.android.internal.util.fetchManaged import javax.inject.Inject internal class DefaultRoomService @Inject constructor(private val monarchy: Monarchy, + private val roomSummaryMapper: RoomSummaryMapper, private val createRoomTask: CreateRoomTask, private val roomFactory: RoomFactory, private val taskExecutor: TaskExecutor) : RoomService { @@ -55,7 +55,7 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona override fun liveRoomSummaries(): LiveData> { return monarchy.findAllMappedWithChanges( { realm -> RoomSummaryEntity.where(realm).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) }, - { it.asDomain() } + { roomSummaryMapper.map(it) } ) } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt index 38935f1950..51e5ba0e2f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt @@ -21,6 +21,7 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.room.Room +import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor @@ -38,8 +39,8 @@ import im.vector.matrix.android.internal.session.room.state.DefaultStateService import im.vector.matrix.android.internal.session.room.state.SendStateTask import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask +import im.vector.matrix.android.internal.session.room.timeline.InMemoryTimelineEventFactory import im.vector.matrix.android.internal.session.room.timeline.PaginationTask -import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory import im.vector.matrix.android.internal.task.TaskExecutor import javax.inject.Inject @@ -47,6 +48,7 @@ internal class RoomFactory @Inject constructor(private val context: Context, private val credentials: Credentials, private val monarchy: Monarchy, private val eventFactory: LocalEchoEventFactory, + private val roomSummaryMapper: RoomSummaryMapper, private val taskExecutor: TaskExecutor, private val loadRoomMembersTask: LoadRoomMembersTask, private val inviteTask: InviteTask, @@ -61,9 +63,7 @@ internal class RoomFactory @Inject constructor(private val context: Context, private val leaveRoomTask: LeaveRoomTask) { fun create(roomId: String): Room { - val roomMemberExtractor = SenderRoomMemberExtractor(roomId) - val relationExtractor = EventRelationExtractor() - val timelineEventFactory = TimelineEventFactory(roomMemberExtractor, relationExtractor, cryptoService) + val timelineEventFactory = InMemoryTimelineEventFactory(SenderRoomMemberExtractor(), EventRelationExtractor(), cryptoService) val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineEventFactory, contextOfEventTask, paginationTask) val sendService = DefaultSendService(context, credentials, roomId, eventFactory, cryptoService, monarchy) val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask) @@ -74,6 +74,7 @@ internal class RoomFactory @Inject constructor(private val context: Context, return DefaultRoom( roomId, monarchy, + roomSummaryMapper, timelineService, sendService, stateService, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index 9cf6ac69f2..1e0794a4cf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -138,4 +138,10 @@ internal abstract class RoomModule { @Binds abstract fun bindTimelineService(timelineService: DefaultTimelineService): TimelineService + @Binds + abstract fun bindSimpleTimelineEventFactory(timelineEventFactory: SimpleTimelineEventFactory): TimelineEventFactory + + @Binds + abstract fun bindCacheableTimelineEventFactory(inMemoryTimelineEventFactory: InMemoryTimelineEventFactory): CacheableTimelineEventFactory + } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index 56622f6dd3..766e13fa83 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -27,7 +27,6 @@ import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.query.latestEvent import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.membership.RoomMembers import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary @@ -40,6 +39,23 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C private val roomDisplayNameResolver: RoomDisplayNameResolver, private val roomAvatarResolver: RoomAvatarResolver) { + // TODO: maybe allow user of SDK to give that list + private val PREVIEWABLE_TYPES = listOf( + EventType.MESSAGE, + EventType.STATE_ROOM_NAME, + EventType.STATE_ROOM_TOPIC, + EventType.STATE_ROOM_MEMBER, + EventType.STATE_HISTORY_VISIBILITY, + EventType.CALL_INVITE, + EventType.CALL_HANGUP, + EventType.CALL_ANSWER, + EventType.ENCRYPTED, + EventType.ENCRYPTION, + EventType.STATE_ROOM_THIRD_PARTY_INVITE, + EventType.STICKER, + EventType.STATE_ROOM_CREATE + ) + fun update(realm: Realm, roomId: String, membership: Membership? = null, @@ -47,7 +63,7 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C unreadNotifications: RoomSyncUnreadNotifications? = null) { val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() - ?: realm.createObject(roomId) + ?: realm.createObject(roomId) if (roomSummary != null) { if (roomSummary.heroes.isNotEmpty()) { @@ -71,13 +87,13 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C roomSummaryEntity.membership = membership } - val lastEvent = EventEntity.latestEvent(realm, roomId) + val lastEvent = EventEntity.latestEvent(realm, roomId, includedTypes = PREVIEWABLE_TYPES) val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain() val otherRoomMembers = RoomMembers(realm, roomId).getLoaded().filterKeys { it != credentials.userId } roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString() roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId) roomSummaryEntity.topic = lastTopicEvent?.content.toModel()?.topic - roomSummaryEntity.lastMessage = lastEvent + roomSummaryEntity.latestEvent = lastEvent roomSummaryEntity.otherMemberIds.clear() roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers.keys) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/SenderRoomMemberExtractor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/SenderRoomMemberExtractor.kt index f7a0a9a68a..890ecc8e00 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/SenderRoomMemberExtractor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/SenderRoomMemberExtractor.kt @@ -34,9 +34,10 @@ import io.realm.RealmList import io.realm.RealmQuery import javax.inject.Inject -internal class SenderRoomMemberExtractor @Inject constructor(private val roomId: String) { +internal class SenderRoomMemberExtractor @Inject constructor() { fun extractFrom(event: EventEntity, realm: Realm = event.realm): RoomMember? { + val roomId = event.roomId val sender = event.sender ?: return null // If the event is unlinked we want to fetch unlinked state events val unlinked = event.isUnlinked diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt index 1160cc5ee2..6e80e7c65c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt @@ -34,6 +34,7 @@ import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.content.ThumbnailExtractor +import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.util.StringProvider import im.vector.matrix.android.internal.util.tryTransactionAsync import org.commonmark.parser.Parser @@ -50,8 +51,9 @@ import javax.inject.Inject * * The transactionID is used as loc */ - -internal class LocalEchoEventFactory @Inject constructor(private val credentials: Credentials, private val stringProvider: StringProvider) { +internal class LocalEchoEventFactory @Inject constructor(private val credentials: Credentials, + private val stringProvider: StringProvider, + private val roomSummaryUpdater: RoomSummaryUpdater) { fun createTextEvent(roomId: String, msgType: String, text: String, autoMarkdown: Boolean): Event { if (autoMarkdown && msgType == MessageType.MSGTYPE_TEXT) { @@ -342,10 +344,12 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials } fun saveLocalEcho(monarchy: Monarchy, event: Event) { + if (event.roomId == null) throw IllegalStateException("Your event should have a roomId") monarchy.tryTransactionAsync { realm -> - val roomEntity = RoomEntity.where(realm, roomId = event.roomId!!).findFirst() + val roomEntity = RoomEntity.where(realm, roomId = event.roomId).findFirst() ?: return@tryTransactionAsync roomEntity.addSendingEvent(event) + roomSummaryUpdater.update(realm, event.roomId) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 1165b2100b..42d4a22c58 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -54,7 +54,7 @@ internal class DefaultTimeline( private val realmConfiguration: RealmConfiguration, private val taskExecutor: TaskExecutor, private val contextOfEventTask: GetContextOfEventTask, - private val timelineEventFactory: TimelineEventFactory, + private val timelineEventFactory: CacheableTimelineEventFactory, private val paginationTask: PaginationTask, private val allowedTypes: List? ) : Timeline { @@ -129,7 +129,7 @@ internal class DefaultTimeline( builtEventsIdMap[eventId]?.let { builtIndex -> //Update the relation of existing event builtEvents[builtIndex]?.let { te -> - builtEvents[builtIndex] = timelineEventFactory.create(eventEntity) + builtEvents[builtIndex] = timelineEventFactory.create(eventEntity, eventEntity.realm) hasChanged = true } } @@ -290,7 +290,7 @@ internal class DefaultTimeline( roomEntity?.sendingTimelineEvents ?.filter { allowedTypes?.contains(it.type) ?: false } ?.forEach { - val timelineEvent = timelineEventFactory.create(it) + val timelineEvent = timelineEventFactory.create(it, it.realm) sendingEvents.add(timelineEvent) } } @@ -418,7 +418,7 @@ internal class DefaultTimeline( nextDisplayIndex = offsetIndex + 1 } offsetResults.forEach { eventEntity -> - val timelineEvent = timelineEventFactory.create(eventEntity) + val timelineEvent = timelineEventFactory.create(eventEntity, eventEntity.realm) val position = if (direction == Timeline.Direction.FORWARDS) 0 else builtEvents.size builtEvents.add(position, timelineEvent) //Need to shift :/ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt index dbd40ad1cb..40aa4677f4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt @@ -33,7 +33,7 @@ import javax.inject.Inject internal class DefaultTimelineService @Inject constructor(private val roomId: String, private val monarchy: Monarchy, private val taskExecutor: TaskExecutor, - private val timelineEventFactory: TimelineEventFactory, + private val timelineEventFactory: CacheableTimelineEventFactory, private val contextOfEventTask: GetContextOfEventTask, private val paginationTask: PaginationTask ) : TimelineService { @@ -60,14 +60,14 @@ internal class DefaultTimelineService @Inject constructor(private val roomId: St } val result = MediatorLiveData() result.addSource(liveEventEntity) { realmResults -> - result.value = realmResults.firstOrNull()?.let { timelineEventFactory.create(it) } + result.value = realmResults.firstOrNull()?.let { timelineEventFactory.create(it, it.realm) } } result.addSource(liveAnnotationsEntity) { liveEventEntity.value?.let { result.value = liveEventEntity.value?.let { realmResults -> //recreate the timeline event - realmResults.firstOrNull()?.let { timelineEventFactory.create(it) } + realmResults.firstOrNull()?.let { timelineEventFactory.create(it, it.realm) } } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt index c2f9827dd4..e98e0d352c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt @@ -33,20 +33,69 @@ import timber.log.Timber import java.util.* import javax.inject.Inject +internal interface TimelineEventFactory { + fun create(eventEntity: EventEntity, realm: Realm): TimelineEvent +} + +internal interface CacheableTimelineEventFactory : TimelineEventFactory { + fun clear() +} + /** * This class is responsible for building [TimelineEvent] returned by a [Timeline] through [TimelineService] * It handles decryption, extracting additional data around an event as sender data and relation. */ -internal class TimelineEventFactory @Inject constructor( - private val roomMemberExtractor: SenderRoomMemberExtractor, - private val relationExtractor: EventRelationExtractor, - private val cryptoService: CryptoService) { +internal class SimpleTimelineEventFactory @Inject constructor(private val roomMemberExtractor: SenderRoomMemberExtractor, + private val relationExtractor: EventRelationExtractor, + private val cryptoService: CryptoService +) : TimelineEventFactory { + + override fun create(eventEntity: EventEntity, realm: Realm): TimelineEvent { + val senderRoomMember = roomMemberExtractor.extractFrom(eventEntity, realm) + val relations = relationExtractor.extractFrom(eventEntity, realm) + + val event = eventEntity.asDomain() + if (event.getClearType() == EventType.ENCRYPTED) { + handleEncryptedEvent(event) + } + + val isUniqueDisplayName = RoomMembers(realm, eventEntity.roomId).isUniqueDisplayName(senderRoomMember?.displayName) + + return TimelineEvent( + event, + eventEntity.localId, + eventEntity.displayIndex, + senderRoomMember?.displayName, + isUniqueDisplayName, + senderRoomMember?.avatarUrl, + eventEntity.sendState, + relations + ) + } + + private fun handleEncryptedEvent(event: Event) { + Timber.v("Encrypted event: try to decrypt ${event.eventId}") + try { + val result = cryptoService.decryptEvent(event, UUID.randomUUID().toString()) + event.setClearData(result) + } catch (failure: Throwable) { + Timber.e(failure, "Encrypted event: decryption failed") + if (failure is MXDecryptionException) { + event.setCryptoError(failure.cryptoError) + } + } + } +} + +internal class InMemoryTimelineEventFactory @Inject constructor(private val roomMemberExtractor: SenderRoomMemberExtractor, + private val relationExtractor: EventRelationExtractor, + private val cryptoService: CryptoService) : CacheableTimelineEventFactory { private val timelineId = UUID.randomUUID().toString() private val senderCache = mutableMapOf() private val decryptionCache = mutableMapOf() - fun create(eventEntity: EventEntity, realm: Realm = eventEntity.realm): TimelineEvent { + override fun create(eventEntity: EventEntity, realm: Realm): TimelineEvent { val sender = eventEntity.sender val cacheKey = sender + eventEntity.localId val senderData = senderCache.getOrPut(cacheKey) { @@ -97,8 +146,9 @@ internal class TimelineEventFactory @Inject constructor( } } - fun clear() { + override fun clear() { senderCache.clear() + decryptionCache.clear() } private data class SenderData( diff --git a/vector/src/main/java/im/vector/riotredesign/core/extensions/TextView.kt b/vector/src/main/java/im/vector/riotredesign/core/extensions/TextView.kt index 6ee8619b2f..0edcab462e 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/extensions/TextView.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/extensions/TextView.kt @@ -29,7 +29,7 @@ import im.vector.riotredesign.features.themes.ThemeUtils /** * Set a text in the TextView, or set visibility to GONE if the text is null */ -fun TextView.setTextOrHide(newText: String?, hideWhenBlank: Boolean = true) { +fun TextView.setTextOrHide(newText: CharSequence?, hideWhenBlank: Boolean = true) { if (newText == null || (newText.isBlank() && hideWhenBlank)) { isVisible = false diff --git a/vector/src/main/java/im/vector/riotredesign/features/autocomplete/user/AutocompleteUserController.kt b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/user/AutocompleteUserController.kt index dfc1f93f0f..dcf2817a89 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/autocomplete/user/AutocompleteUserController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/autocomplete/user/AutocompleteUserController.kt @@ -19,12 +19,15 @@ package im.vector.riotredesign.features.autocomplete.user import com.airbnb.epoxy.TypedEpoxyController import im.vector.matrix.android.api.session.user.model.User import im.vector.riotredesign.features.autocomplete.AutocompleteClickListener +import im.vector.riotredesign.features.home.AvatarRenderer import javax.inject.Inject class AutocompleteUserController @Inject constructor(): TypedEpoxyController>() { var listener: AutocompleteClickListener? = null + @Inject lateinit var avatarRenderer: AvatarRenderer + override fun buildModels(data: List?) { if (data.isNullOrEmpty()) { return @@ -35,6 +38,7 @@ class AutocompleteUserController @Inject constructor(): TypedEpoxyController listener?.onItemClick(user) } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 081c83a615..3deda66786 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -81,7 +81,10 @@ import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandP import im.vector.riotredesign.features.autocomplete.command.CommandAutocompletePolicy import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserPresenter import im.vector.riotredesign.features.command.Command -import im.vector.riotredesign.features.home.* +import im.vector.riotredesign.features.home.AvatarRenderer +import im.vector.riotredesign.features.home.NavigateToRoomInterceptor +import im.vector.riotredesign.features.home.PermalinkHandler +import im.vector.riotredesign.features.home.getColorFromUserId import im.vector.riotredesign.features.home.room.detail.composer.TextComposerActions import im.vector.riotredesign.features.home.room.detail.composer.TextComposerView import im.vector.riotredesign.features.home.room.detail.composer.TextComposerViewModel @@ -509,6 +512,9 @@ class RoomDetailFragment : } else if (summary?.membership == Membership.INVITE && inviter != null) { inviteView.visibility = View.VISIBLE inviteView.render(inviter, VectorInviteView.Mode.LARGE) + + // Intercept click event + inviteView.setOnClickListener { } } else if (state.asyncInviter.complete) { vectorBaseActivity.finish() } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt index 953bc0f883..8d23ac0f8e 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -504,7 +504,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro private fun observeInvitationState() { asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary -> if (summary.membership == Membership.INVITE) { - summary.lastMessage?.senderId?.let { senderId -> + summary.latestEvent?.root?.senderId?.let { senderId -> session.getUser(senderId) }?.also { setState { copy(asyncInviter = Success(it)) } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/ChronologicalRoomComparator.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/ChronologicalRoomComparator.kt index ccbe89bfab..282590656a 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/ChronologicalRoomComparator.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/ChronologicalRoomComparator.kt @@ -25,14 +25,14 @@ class ChronologicalRoomComparator @Inject constructor() : Comparator() { + + @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer + @EpoxyAttribute lateinit var roomName: CharSequence + @EpoxyAttribute lateinit var roomId: String + @EpoxyAttribute var secondLine: CharSequence? = null + @EpoxyAttribute var avatarUrl: String? = null + @EpoxyAttribute var listener: (() -> Unit)? = null + @EpoxyAttribute var invitationAcceptInProgress: Boolean = false + @EpoxyAttribute var invitationAcceptInError: Boolean = false + @EpoxyAttribute var invitationRejectInProgress: Boolean = false + @EpoxyAttribute var invitationRejectInError: Boolean = false + @EpoxyAttribute var acceptListener: (() -> Unit)? = null + @EpoxyAttribute var rejectListener: (() -> Unit)? = null + + + override fun bind(holder: Holder) { + super.bind(holder) + holder.rootView.setOnClickListener { listener?.invoke() } + + // When a request is in progress (accept or reject), we only use the accept State button + val requestInProgress = invitationAcceptInProgress || invitationRejectInProgress + + when { + requestInProgress -> holder.acceptView.render(ButtonStateView.State.Loading) + invitationAcceptInError -> holder.acceptView.render(ButtonStateView.State.Error) + else -> holder.acceptView.render(ButtonStateView.State.Button) + } + // ButtonStateView.State.Loaded not used because roomSummary will not be displayed as a room invitation anymore + + + holder.acceptView.callback = object : ButtonStateView.Callback { + override fun onButtonClicked() { + acceptListener?.invoke() + } + + override fun onRetryClicked() { + acceptListener?.invoke() + } + } + + holder.rejectView.isVisible = !requestInProgress + + when { + invitationRejectInError -> holder.rejectView.render(ButtonStateView.State.Error) + else -> holder.rejectView.render(ButtonStateView.State.Button) + } + + holder.rejectView.callback = object : ButtonStateView.Callback { + override fun onButtonClicked() { + rejectListener?.invoke() + } + + override fun onRetryClicked() { + rejectListener?.invoke() + } + } + holder.titleView.text = roomName + holder.subtitleView.setTextOrHide(secondLine) + avatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView) + } + + class Holder : VectorEpoxyHolder() { + val titleView by bind(R.id.roomInvitationNameView) + val subtitleView by bind(R.id.roomInvitationSubTitle) + val acceptView by bind(R.id.roomInvitationAccept) + val rejectView by bind(R.id.roomInvitationReject) + val avatarImageView by bind(R.id.roomInvitationAvatarImageView) + val rootView by bind(R.id.itemRoomInvitationLayout) + } + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListActions.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListActions.kt index dc9c393b37..4c5d57df3a 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListActions.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListActions.kt @@ -22,8 +22,10 @@ sealed class RoomListActions { data class SelectRoom(val roomSummary: RoomSummary) : RoomListActions() - data class FilterRooms(val roomName: CharSequence? = null) : RoomListActions() - data class ToggleCategory(val category: RoomCategory) : RoomListActions() + data class AcceptInvitation(val roomSummary: RoomSummary) : RoomListActions() + + data class RejectInvitation(val roomSummary: RoomSummary) : RoomListActions() + } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt index a306fb58d1..1b2a3650e7 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt @@ -24,12 +24,15 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.airbnb.mvrx.* +import com.google.android.material.snackbar.Snackbar import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotredesign.R import im.vector.riotredesign.core.di.ScreenComponent import im.vector.riotredesign.core.epoxy.LayoutManagerStateRestorer +import im.vector.riotredesign.core.error.ErrorFormatter +import im.vector.riotredesign.core.extensions.observeEvent import im.vector.riotredesign.core.extensions.observeEventDebounced import im.vector.riotredesign.core.platform.OnBackPressed import im.vector.riotredesign.core.platform.StateView @@ -45,7 +48,7 @@ data class RoomListParams( ) : Parcelable -class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, OnBackPressed, FabMenuView.Listener { +class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, FabMenuView.Listener { enum class DisplayMode(@StringRes val titleRes: Int) { HOME(R.string.bottom_action_home), @@ -64,6 +67,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O private val roomListParams: RoomListParams by args() @Inject lateinit var roomController: RoomSummaryController @Inject lateinit var roomListViewModelFactory: RoomListViewModel.Factory + @Inject lateinit var errorFormatter: ErrorFormatter private val roomListViewModel: RoomListViewModel by fragmentViewModel() override fun getLayoutResId() = R.layout.fragment_room_list @@ -82,6 +86,13 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O } createChatFabMenu.listener = this + + roomListViewModel.invitationAnswerErrorLiveData.observeEvent(this) { throwable -> + vectorBaseActivity.coordinatorLayout?.let { + Snackbar.make(it, errorFormatter.toHumanReadable(throwable), Snackbar.LENGTH_SHORT) + .show() + } + } } private fun setupCreateRoomButton() { @@ -135,7 +146,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O val stateRestorer = LayoutManagerStateRestorer(layoutManager).register() epoxyRecyclerView.layoutManager = layoutManager epoxyRecyclerView.itemAnimator = RoomListAnimator() - roomController.callback = this + roomController.listener = this roomController.addModelBuildListener { it.dispatchTo(stateRestorer) } stateView.contentView = epoxyRecyclerView epoxyRecyclerView.setController(roomController) @@ -233,6 +244,14 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O roomListViewModel.accept(RoomListActions.SelectRoom(room)) } + override fun onAcceptRoomInvitation(room: RoomSummary) { + roomListViewModel.accept(RoomListActions.AcceptInvitation(room)) + } + + override fun onRejectRoomInvitation(room: RoomSummary) { + roomListViewModel.accept(RoomListActions.RejectInvitation(room)) + } + override fun onToggleRoomCategory(roomCategory: RoomCategory) { roomListViewModel.accept(RoomListActions.ToggleCategory(roomCategory)) } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt index 7c6f7484ac..7cd49fd56c 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt @@ -18,13 +18,12 @@ package im.vector.riotredesign.features.home.room.list import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import arrow.core.Option import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext -import com.jakewharton.rxrelay2.BehaviorRelay import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject +import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomSummary @@ -32,8 +31,7 @@ import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.utils.LiveEvent import im.vector.riotredesign.features.home.HomeRoomListObservableStore - -typealias RoomListFilterName = CharSequence +import timber.log.Timber class RoomListViewModel @AssistedInject constructor(@Assisted initialState: RoomListViewState, private val session: Session, @@ -57,22 +55,25 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room } private val displayMode = initialState.displayMode - private val roomListDisplayModeFilter = RoomListDisplayModeFilter(displayMode) - private val roomListFilter = BehaviorRelay.createDefault>(Option.empty()) private val _openRoomLiveData = MutableLiveData>() val openRoomLiveData: LiveData> get() = _openRoomLiveData + private val _invitationAnswerErrorLiveData = MutableLiveData>() + val invitationAnswerErrorLiveData: LiveData> + get() = _invitationAnswerErrorLiveData + init { observeRoomSummaries() } fun accept(action: RoomListActions) { when (action) { - is RoomListActions.SelectRoom -> handleSelectRoom(action) - is RoomListActions.FilterRooms -> handleFilterRooms(action) - is RoomListActions.ToggleCategory -> handleToggleCategory(action) + is RoomListActions.SelectRoom -> handleSelectRoom(action) + is RoomListActions.ToggleCategory -> handleToggleCategory(action) + is RoomListActions.AcceptInvitation -> handleAcceptInvitation(action) + is RoomListActions.RejectInvitation -> handleRejectInvitation(action) } } @@ -82,11 +83,6 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room _openRoomLiveData.postValue(LiveEvent(action.roomSummary.roomId)) } - private fun handleFilterRooms(action: RoomListActions.FilterRooms) { - val optionalFilter = Option.fromNullable(action.roomName) - roomListFilter.accept(optionalFilter) - } - private fun handleToggleCategory(action: RoomListActions.ToggleCategory) = setState { this.toggle(action.category) } @@ -106,6 +102,78 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room } } + private fun handleAcceptInvitation(action: RoomListActions.AcceptInvitation) = withState { state -> + val roomId = action.roomSummary.roomId + + if (state.joiningRoomsIds.contains(roomId) || state.rejectingRoomsIds.contains(roomId)) { + // Request already sent, should not happen + Timber.w("Try to join an already joining room. Should not happen") + return@withState + } + + setState { + copy( + joiningRoomsIds = joiningRoomsIds.toMutableSet().apply { add(roomId) }, + rejectingErrorRoomsIds = rejectingErrorRoomsIds.toMutableSet().apply { remove(roomId) } + ) + } + + session.getRoom(roomId)?.join(object : MatrixCallback { + override fun onSuccess(data: Unit) { + // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. + // Instead, we wait for the room to be joined + } + + override fun onFailure(failure: Throwable) { + // Notify the user + _invitationAnswerErrorLiveData.postValue(LiveEvent(failure)) + + setState { + copy( + joiningRoomsIds = joiningRoomsIds.toMutableSet().apply { remove(roomId) }, + joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableSet().apply { add(roomId) } + ) + } + } + }) + } + + private fun handleRejectInvitation(action: RoomListActions.RejectInvitation) = withState { state -> + val roomId = action.roomSummary.roomId + + if (state.joiningRoomsIds.contains(roomId) || state.rejectingRoomsIds.contains(roomId)) { + // Request already sent, should not happen + Timber.w("Try to reject an already rejecting room. Should not happen") + return@withState + } + + setState { + copy( + rejectingRoomsIds = rejectingRoomsIds.toMutableSet().apply { add(roomId) }, + joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableSet().apply { remove(roomId) } + ) + } + + session.getRoom(roomId)?.leave(object : MatrixCallback { + override fun onSuccess(data: Unit) { + // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. + // Instead, we wait for the room to be joined + } + + override fun onFailure(failure: Throwable) { + // Notify the user + _invitationAnswerErrorLiveData.postValue(LiveEvent(failure)) + + setState { + copy( + rejectingRoomsIds = rejectingRoomsIds.toMutableSet().apply { remove(roomId) }, + rejectingErrorRoomsIds = rejectingErrorRoomsIds.toMutableSet().apply { add(roomId) } + ) + } + } + }) + } + private fun buildRoomSummaries(rooms: List): RoomSummaries { val invites = ArrayList() val favourites = ArrayList() diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt index a6afc66d9f..c56e1b6c17 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt @@ -27,6 +27,14 @@ data class RoomListViewState( val displayMode: RoomListFragment.DisplayMode, val asyncRooms: Async> = Uninitialized, val asyncFilteredRooms: Async = Uninitialized, + // List of roomIds that the user wants to join + val joiningRoomsIds: Set = emptySet(), + // List of roomIds that the user wants to join, but an error occurred + val joiningErrorRoomsIds: Set = emptySet(), + // List of roomIds that the user wants to join + val rejectingRoomsIds: Set = emptySet(), + // List of roomIds that the user wants to reject, but an error occurred + val rejectingErrorRoomsIds: Set = emptySet(), val isInviteExpanded: Boolean = true, val isFavouriteRoomsExpanded: Boolean = true, val isDirectRoomsExpanded: Boolean = true, diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt index 992b697ea8..f258ed79e9 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt @@ -18,25 +18,15 @@ package im.vector.riotredesign.features.home.room.list import androidx.annotation.StringRes import com.airbnb.epoxy.TypedEpoxyController -import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.RoomSummary -import im.vector.matrix.android.api.session.room.model.message.MessageContent -import im.vector.riotredesign.core.extensions.localDateTime -import im.vector.riotredesign.core.resources.DateProvider import im.vector.riotredesign.core.resources.StringProvider -import im.vector.riotredesign.features.home.AvatarRenderer -import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter -import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter import javax.inject.Inject class RoomSummaryController @Inject constructor(private val stringProvider: StringProvider, - private val eventFormatter: NoticeEventFormatter, - private val timelineDateFormatter: TimelineDateFormatter, - private val avatarRenderer: AvatarRenderer + private val roomSummaryItemFactory: RoomSummaryItemFactory ) : TypedEpoxyController() { - var callback: Callback? = null + var listener: Listener? = null override fun buildModels(viewState: RoomListViewState) { val roomSummaries = viewState.asyncFilteredRooms() @@ -46,10 +36,14 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri } else { val isExpanded = viewState.isCategoryExpanded(category) buildRoomCategory(viewState, summaries, category.titleRes, viewState.isCategoryExpanded(category)) { - callback?.onToggleRoomCategory(category) + listener?.onToggleRoomCategory(category) } if (isExpanded) { - buildRoomModels(summaries) + buildRoomModels(summaries, + viewState.joiningRoomsIds, + viewState.joiningErrorRoomsIds, + viewState.rejectingRoomsIds, + viewState.rejectingErrorRoomsIds) } } } @@ -83,52 +77,23 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri } } - private fun buildRoomModels(summaries: List) { + private fun buildRoomModels(summaries: List, + joiningRoomsIds: Set, + joiningErrorRoomsIds: Set, + rejectingRoomsIds: Set, + rejectingErrorRoomsIds: Set) { summaries.forEach { roomSummary -> - val unreadCount = roomSummary.notificationCount - val showHighlighted = roomSummary.highlightCount > 0 - - var lastMessageFormatted: CharSequence = "" - var lastMessageTime: CharSequence = "" - val lastMessage = roomSummary.lastMessage - if (lastMessage != null) { - val date = lastMessage.localDateTime() - val currentData = DateProvider.currentLocalDateTime() - val isSameDay = date.toLocalDate() == currentData.toLocalDate() - //TODO: get formatted - if (lastMessage.type == EventType.MESSAGE) { - val content = lastMessage.content?.toModel() - lastMessageFormatted = content?.body ?: "" - } else { - lastMessageFormatted = lastMessage.type - } - lastMessageTime = if (isSameDay) { - timelineDateFormatter.formatMessageHour(date) - } else { - //TODO: change this - timelineDateFormatter.formatMessageDay(date) - } - - - } - roomSummaryItem { - avatarRenderer(avatarRenderer) - id(roomSummary.roomId) - roomId(roomSummary.roomId) - lastEventTime(lastMessageTime) - lastFormattedEvent(lastMessageFormatted) - roomName(roomSummary.displayName) - avatarUrl(roomSummary.avatarUrl) - showHighlighted(showHighlighted) - unreadCount(unreadCount) - listener { callback?.onRoomSelected(roomSummary) } - } + roomSummaryItemFactory + .create(roomSummary, joiningRoomsIds, joiningErrorRoomsIds, rejectingRoomsIds, rejectingErrorRoomsIds, listener) + .addTo(this) } } - interface Callback { + interface Listener { fun onToggleRoomCategory(roomCategory: RoomCategory) fun onRoomSelected(room: RoomSummary) + fun onRejectRoomInvitation(room: RoomSummary) + fun onAcceptRoomInvitation(room: RoomSummary) } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItemFactory.kt new file mode 100644 index 0000000000..e846ebbbaf --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItemFactory.kt @@ -0,0 +1,136 @@ +/* + * Copyright 2019 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.riotredesign.features.home.room.list + +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.riotredesign.R +import im.vector.riotredesign.core.epoxy.VectorEpoxyModel +import im.vector.riotredesign.core.extensions.localDateTime +import im.vector.riotredesign.core.resources.ColorProvider +import im.vector.riotredesign.core.resources.DateProvider +import im.vector.riotredesign.core.resources.StringProvider +import im.vector.riotredesign.features.home.AvatarRenderer +import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter +import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter +import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName +import me.gujun.android.span.span +import javax.inject.Inject + +class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatter: NoticeEventFormatter, + private val timelineDateFormatter: TimelineDateFormatter, + private val colorProvider: ColorProvider, + private val stringProvider: StringProvider, + private val avatarRenderer: AvatarRenderer) { + + fun create(roomSummary: RoomSummary, + joiningRoomsIds: Set, + joiningErrorRoomsIds: Set, + rejectingRoomsIds: Set, + rejectingErrorRoomsIds: Set, + listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> { + return when (roomSummary.membership) { + Membership.INVITE -> createInvitationItem(roomSummary, joiningRoomsIds, joiningErrorRoomsIds, rejectingRoomsIds, rejectingErrorRoomsIds, listener) + else -> createRoomItem(roomSummary, listener) + } + } + + private fun createInvitationItem(roomSummary: RoomSummary, + joiningRoomsIds: Set, + joiningErrorRoomsIds: Set, + rejectingRoomsIds: Set, + rejectingErrorRoomsIds: Set, + listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> { + val secondLine = if (roomSummary.isDirect) { + roomSummary.latestEvent?.root?.senderId + } else { + roomSummary.latestEvent?.root?.senderId?.let { + stringProvider.getString(R.string.invited_by, it) + } + } + + return RoomInvitationItem_() + .id(roomSummary.roomId) + .avatarRenderer(avatarRenderer) + .roomId(roomSummary.roomId) + .secondLine(secondLine) + .invitationAcceptInProgress(joiningRoomsIds.contains(roomSummary.roomId)) + .invitationAcceptInError(joiningErrorRoomsIds.contains(roomSummary.roomId)) + .invitationRejectInProgress(rejectingRoomsIds.contains(roomSummary.roomId)) + .invitationRejectInError(rejectingErrorRoomsIds.contains(roomSummary.roomId)) + .acceptListener { listener?.onAcceptRoomInvitation(roomSummary) } + .rejectListener { listener?.onRejectRoomInvitation(roomSummary) } + .roomName(roomSummary.displayName) + .avatarUrl(roomSummary.avatarUrl) + .listener { listener?.onRoomSelected(roomSummary) } + } + + private fun createRoomItem(roomSummary: RoomSummary, listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> { + val unreadCount = roomSummary.notificationCount + val showHighlighted = roomSummary.highlightCount > 0 + + var latestFormattedEvent: CharSequence = "" + var latestEventTime: CharSequence = "" + val latestEvent = roomSummary.latestEvent + if (latestEvent != null) { + val date = latestEvent.root.localDateTime() + val currentDate = DateProvider.currentLocalDateTime() + val isSameDay = date.toLocalDate() == currentDate.toLocalDate() + latestFormattedEvent = if (latestEvent.root.getClearType() == EventType.MESSAGE) { + val senderName = latestEvent.senderName() ?: latestEvent.root.senderId + val content = latestEvent.root.getClearContent()?.toModel() + val message = content?.body ?: "" + if (roomSummary.isDirect.not() && senderName != null) { + span { + text = senderName + textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_primary) + } + .append(" - ") + .append(message) + } else { + message + } + } else { + span { + text = noticeEventFormatter.format(latestEvent) ?: "" + textStyle = "italic" + } + } + latestEventTime = if (isSameDay) { + timelineDateFormatter.formatMessageHour(date) + } else { + //TODO: change this + timelineDateFormatter.formatMessageDay(date) + } + } + return RoomSummaryItem_() + .id(roomSummary.roomId) + .avatarRenderer(avatarRenderer) + .roomId(roomSummary.roomId) + .lastEventTime(latestEventTime) + .lastFormattedEvent(latestFormattedEvent) + .roomName(roomSummary.displayName) + .avatarUrl(roomSummary.avatarUrl) + .showHighlighted(showHighlighted) + .unreadCount(unreadCount) + .listener { listener?.onRoomSelected(roomSummary) } + } + +} diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt index d673cc35fa..8c81570b7e 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationDrawerManager.kt @@ -20,13 +20,13 @@ import android.content.Context import android.graphics.Bitmap import androidx.core.app.NotificationCompat import androidx.core.app.Person -import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.riotredesign.BuildConfig import im.vector.riotredesign.R import im.vector.riotredesign.core.di.ActiveSessionHolder import im.vector.riotredesign.core.utils.SecretStoringUtils import im.vector.riotredesign.features.settings.PreferencesManager +import me.gujun.android.span.span import timber.log.Timber import java.io.File import java.io.FileInputStream @@ -275,9 +275,32 @@ class NotificationDrawerManager @Inject constructor(private val context: Context } try { - val summaryLine = context.resources.getQuantityString( - R.plurals.notification_compat_summary_line_for_room, events.size, roomName, events.size) - summaryInboxStyle.addLine(summaryLine) + if (events.size == 1) { + val event = events[0] + if (roomEventGroupInfo.isDirect) { + val line = span { + span { + textStyle = "bold" + +String.format("%s: ", event.senderName) + } + +(event.description ?: "") + } + summaryInboxStyle.addLine(line) + } else { + val line = span { + span { + textStyle = "bold" + +String.format("%s: %s ", roomName, event.senderName) + } + +(event.description ?: "") + } + summaryInboxStyle.addLine(line) + } + } else { + val summaryLine = context.resources.getQuantityString( + R.plurals.notification_compat_summary_line_for_room, events.size, roomName, events.size) + summaryInboxStyle.addLine(summaryLine) + } } catch (e: Throwable) { //String not found or bad format Timber.v("%%%%%%%% REFRESH NOTIFICATION DRAWER failed to resolve string") @@ -343,6 +366,11 @@ class NotificationDrawerManager @Inject constructor(private val context: Context val sumTitle = context.resources.getQuantityString( R.plurals.notification_compat_summary_title, nbEvents, nbEvents) summaryInboxStyle.setBigContentTitle(sumTitle) + //TODO get latest event? + .setSummaryText( + context.resources + .getQuantityString(R.plurals.notification_unread_notified_messages, nbEvents, nbEvents)) + NotificationUtils.buildSummaryListNotification( context, summaryInboxStyle, diff --git a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt index 365343346e..e91b4d7495 100755 --- a/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/notifications/NotificationUtils.kt @@ -606,7 +606,7 @@ object NotificationUtils { * Build the summary notification */ fun buildSummaryListNotification(context: Context, - style: NotificationCompat.Style, + style: NotificationCompat.InboxStyle, compatSummary: String, noisy: Boolean, lastMessageTimestamp: Long): Notification? { diff --git a/vector/src/main/java/im/vector/riotredesign/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/riotredesign/features/rageshake/BugReporter.kt index c92ec1d26d..542703742f 100755 --- a/vector/src/main/java/im/vector/riotredesign/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/rageshake/BugReporter.kt @@ -203,7 +203,7 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes var userId = "undefined" var olmVersion = "undefined" - activeSessionHolder.getActiveSession().let { session -> + activeSessionHolder.getSafeActiveSession()?.let { session -> userId = session.sessionParams.credentials.userId deviceId = session.sessionParams.credentials.deviceId ?: "undefined" olmVersion = session.getCryptoVersion(context, true) diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsViewState.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsViewState.kt index 6fc7f519b3..0d43368e76 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsViewState.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsViewState.kt @@ -28,11 +28,11 @@ data class PublicRoomsViewState( val asyncPublicRoomsRequest: Async> = Uninitialized, // True if more result are available server side val hasMore: Boolean = false, - // List of roomIds that the user wants to join - val joiningRoomsIds: List = emptyList(), - // List of roomIds that the user wants to join, but an error occurred - val joiningErrorRoomsIds: List = emptyList(), - // List of joined roomId, - val joinedRoomsIds: List = emptyList(), + // Set of roomIds that the user wants to join + val joiningRoomsIds: Set = emptySet(), + // Set of roomIds that the user wants to join, but an error occurred + val joiningErrorRoomsIds: Set = emptySet(), + // Set of joined roomId, + val joinedRoomsIds: Set = emptySet(), val roomDirectoryDisplayName: String? = null ) : MvRxState \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/RoomDirectoryViewModel.kt index f06a2bea12..954c0f74e3 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/RoomDirectoryViewModel.kt @@ -18,13 +18,7 @@ package im.vector.riotredesign.features.roomdirectory import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import com.airbnb.mvrx.ActivityViewModelContext -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.ViewModelContext -import com.airbnb.mvrx.appendAt +import com.airbnb.mvrx.* import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback @@ -95,19 +89,19 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: .liveRoomSummaries() .subscribe { list -> val joinedRoomIds = list - // Keep only joined room - ?.filter { it.membership == Membership.JOIN } - ?.map { it.roomId } - ?.toList() - ?: emptyList() + // Keep only joined room + ?.filter { it.membership == Membership.JOIN } + ?.map { it.roomId } + ?.toSet() + ?: emptySet() setState { copy( joinedRoomsIds = joinedRoomIds, // Remove (newly) joined room id from the joining room list - joiningRoomsIds = joiningRoomsIds.toMutableList().apply { removeAll(joinedRoomIds) }, + joiningRoomsIds = joiningRoomsIds.toMutableSet().apply { removeAll(joinedRoomIds) }, // Remove (newly) joined room id from the joining room list in error - joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableList().apply { removeAll(joinedRoomIds) } + joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableSet().apply { removeAll(joinedRoomIds) } ) } } @@ -166,39 +160,39 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: private fun load() { currentTask = session.getPublicRooms(roomDirectoryData.homeServer, - PublicRoomsParams( - limit = PUBLIC_ROOMS_LIMIT, - filter = PublicRoomsFilter(searchTerm = currentFilter), - includeAllNetworks = roomDirectoryData.includeAllNetworks, - since = since, - thirdPartyInstanceId = roomDirectoryData.thirdPartyInstanceId - ), - object : MatrixCallback { - override fun onSuccess(data: PublicRoomsResponse) { - currentTask = null + PublicRoomsParams( + limit = PUBLIC_ROOMS_LIMIT, + filter = PublicRoomsFilter(searchTerm = currentFilter), + includeAllNetworks = roomDirectoryData.includeAllNetworks, + since = since, + thirdPartyInstanceId = roomDirectoryData.thirdPartyInstanceId + ), + object : MatrixCallback { + override fun onSuccess(data: PublicRoomsResponse) { + currentTask = null - since = data.nextBatch + since = data.nextBatch - setState { - copy( - asyncPublicRoomsRequest = Success(data.chunk!!), - // It's ok to append at the end of the list, so I use publicRooms.size() - publicRooms = publicRooms.appendAt(data.chunk!!, publicRooms.size), - hasMore = since != null - ) - } - } + setState { + copy( + asyncPublicRoomsRequest = Success(data.chunk!!), + // It's ok to append at the end of the list, so I use publicRooms.size() + publicRooms = publicRooms.appendAt(data.chunk!!, publicRooms.size), + hasMore = since != null + ) + } + } - override fun onFailure(failure: Throwable) { - currentTask = null + override fun onFailure(failure: Throwable) { + currentTask = null - setState { - copy( - asyncPublicRoomsRequest = Fail(failure) - ) - } - } - }) + setState { + copy( + asyncPublicRoomsRequest = Fail(failure) + ) + } + } + }) } fun joinRoom(publicRoom: PublicRoom) = withState { state -> @@ -210,7 +204,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: setState { copy( - joiningRoomsIds = joiningRoomsIds.toMutableList().apply { add(publicRoom.roomId) } + joiningRoomsIds = joiningRoomsIds.toMutableSet().apply { add(publicRoom.roomId) } ) } @@ -226,8 +220,8 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: setState { copy( - joiningRoomsIds = joiningRoomsIds.toMutableList().apply { remove(publicRoom.roomId) }, - joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableList().apply { add(publicRoom.roomId) } + joiningRoomsIds = joiningRoomsIds.toMutableSet().apply { remove(publicRoom.roomId) }, + joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableSet().apply { add(publicRoom.roomId) } ) } } diff --git a/vector/src/main/res/layout/activity_home.xml b/vector/src/main/res/layout/activity_home.xml index 401ab6269f..599b7d4cfb 100644 --- a/vector/src/main/res/layout/activity_home.xml +++ b/vector/src/main/res/layout/activity_home.xml @@ -7,7 +7,7 @@ tools:openDrawer="start"> diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml index ec9866d788..d1f6f76dc6 100644 --- a/vector/src/main/res/layout/fragment_room_detail.xml +++ b/vector/src/main/res/layout/fragment_room_detail.xml @@ -93,6 +93,7 @@ android:id="@+id/inviteView" android:layout_width="0dp" android:layout_height="wrap_content" + android:background="?riotx_background" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/vector/src/main/res/layout/item_room_invitation.xml b/vector/src/main/res/layout/item_room_invitation.xml new file mode 100644 index 0000000000..2737eba410 --- /dev/null +++ b/vector/src/main/res/layout/item_room_invitation.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/values/config.xml b/vector/src/main/res/values/config.xml index 39c654e989..f390449976 100755 --- a/vector/src/main/res/values/config.xml +++ b/vector/src/main/res/values/config.xml @@ -1,7 +1,7 @@ - "Riot X" + "RiotX" https://vector.im diff --git a/vector/src/main/res/values/styles_riot.xml b/vector/src/main/res/values/styles_riot.xml index 83004b0de2..789071934f 100644 --- a/vector/src/main/res/values/styles_riot.xml +++ b/vector/src/main/res/values/styles_riot.xml @@ -142,6 +142,16 @@ ?colorAccent + +