mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-27 03:49:04 +03:00
Merge pull request #257 from vector-im/feature/room_list_improvements
Room list: last event preview - invitations list
This commit is contained in:
commit
f4b124d29f
32 changed files with 704 additions and 177 deletions
|
@ -47,7 +47,7 @@ interface MembershipService {
|
||||||
*/
|
*/
|
||||||
fun getRoomMemberIdsLive(): LiveData<List<String>>
|
fun getRoomMemberIdsLive(): LiveData<List<String>>
|
||||||
|
|
||||||
fun getNumberOfJoinedMembers() : Int
|
fun getNumberOfJoinedMembers(): Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invite a user in the room
|
* Invite a user in the room
|
||||||
|
@ -55,13 +55,12 @@ interface MembershipService {
|
||||||
fun invite(userId: String, callback: MatrixCallback<Unit>)
|
fun invite(userId: String, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Join the room
|
* Join the room, or accept an invitation.
|
||||||
*/
|
*/
|
||||||
fun join(callback: MatrixCallback<Unit>)
|
fun join(callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Leave the room.
|
* Leave the room, or reject an invitation.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
fun leave(callback: MatrixCallback<Unit>)
|
fun leave(callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.model
|
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.model.tag.RoomTag
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class holds some data of a room.
|
* This class holds some data of a room.
|
||||||
|
@ -29,7 +29,7 @@ data class RoomSummary(
|
||||||
val topic: String = "",
|
val topic: String = "",
|
||||||
val avatarUrl: String = "",
|
val avatarUrl: String = "",
|
||||||
val isDirect: Boolean = false,
|
val isDirect: Boolean = false,
|
||||||
val lastMessage: Event? = null,
|
val latestEvent: TimelineEvent? = null,
|
||||||
val otherMemberIds: List<String> = emptyList(),
|
val otherMemberIds: List<String> = emptyList(),
|
||||||
val notificationCount: Int = 0,
|
val notificationCount: Int = 0,
|
||||||
val highlightCount: Int = 0,
|
val highlightCount: Int = 0,
|
||||||
|
|
|
@ -16,24 +16,37 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.database.mapper
|
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.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
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.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 {
|
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
|
||||||
val tags = roomSummaryEntity.tags.map {
|
val tags = roomSummaryEntity.tags.map {
|
||||||
RoomTag(it.tagName, it.tagOrder)
|
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(
|
return RoomSummary(
|
||||||
roomId = roomSummaryEntity.roomId,
|
roomId = roomSummaryEntity.roomId,
|
||||||
displayName = roomSummaryEntity.displayName ?: "",
|
displayName = roomSummaryEntity.displayName ?: "",
|
||||||
topic = roomSummaryEntity.topic ?: "",
|
topic = roomSummaryEntity.topic ?: "",
|
||||||
avatarUrl = roomSummaryEntity.avatarUrl ?: "",
|
avatarUrl = roomSummaryEntity.avatarUrl ?: "",
|
||||||
isDirect = roomSummaryEntity.isDirect,
|
isDirect = roomSummaryEntity.isDirect,
|
||||||
lastMessage = roomSummaryEntity.lastMessage?.asDomain(),
|
latestEvent = latestEvent,
|
||||||
otherMemberIds = roomSummaryEntity.otherMemberIds.toList(),
|
otherMemberIds = roomSummaryEntity.otherMemberIds.toList(),
|
||||||
highlightCount = roomSummaryEntity.highlightCount,
|
highlightCount = roomSummaryEntity.highlightCount,
|
||||||
notificationCount = roomSummaryEntity.notificationCount,
|
notificationCount = roomSummaryEntity.notificationCount,
|
||||||
|
@ -42,7 +55,3 @@ internal object RoomSummaryMapper {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun RoomSummaryEntity.asDomain(): RoomSummary {
|
|
||||||
return RoomSummaryMapper.map(this)
|
|
||||||
}
|
|
|
@ -27,7 +27,7 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
|
||||||
var displayName: String? = "",
|
var displayName: String? = "",
|
||||||
var avatarUrl: String? = "",
|
var avatarUrl: String? = "",
|
||||||
var topic: String? = "",
|
var topic: String? = "",
|
||||||
var lastMessage: EventEntity? = null,
|
var latestEvent: EventEntity? = null,
|
||||||
var heroes: RealmList<String> = RealmList(),
|
var heroes: RealmList<String> = RealmList(),
|
||||||
var joinedMembersCount: Int? = 0,
|
var joinedMembersCount: Int? = 0,
|
||||||
var invitedMembersCount: Int? = 0,
|
var invitedMembersCount: Int? = 0,
|
||||||
|
|
|
@ -16,10 +16,12 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.database.query
|
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.ChunkEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
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.EventEntity.LinkFilterMode.*
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
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.Realm
|
||||||
import io.realm.RealmList
|
import io.realm.RealmList
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
|
@ -65,7 +67,14 @@ internal fun EventEntity.Companion.latestEvent(realm: Realm,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
includedTypes: List<String> = emptyList(),
|
includedTypes: List<String> = emptyList(),
|
||||||
excludedTypes: List<String> = emptyList()): EventEntity? {
|
excludedTypes: List<String> = 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()) {
|
if (includedTypes.isNotEmpty()) {
|
||||||
query?.`in`(EventEntityFields.TYPE, includedTypes.toTypedArray())
|
query?.`in`(EventEntityFields.TYPE, includedTypes.toTypedArray())
|
||||||
} else if (excludedTypes.isNotEmpty()) {
|
} else if (excludedTypes.isNotEmpty()) {
|
||||||
|
|
|
@ -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.state.StateService
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
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.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.RoomSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
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,
|
internal class DefaultRoom @Inject constructor(override val roomId: String,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
|
private val roomSummaryMapper: RoomSummaryMapper,
|
||||||
private val timelineService: TimelineService,
|
private val timelineService: TimelineService,
|
||||||
private val sendService: SendService,
|
private val sendService: SendService,
|
||||||
private val stateService: StateService,
|
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)
|
RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
|
||||||
}
|
}
|
||||||
Transformations.map(liveRealmData) { results ->
|
Transformations.map(liveRealmData) { results ->
|
||||||
val roomSummaries = results.map { it.asDomain() }
|
val roomSummaries = results.map { roomSummaryMapper.map(it) }
|
||||||
|
|
||||||
if (roomSummaries.isEmpty()) {
|
if (roomSummaries.isEmpty()) {
|
||||||
// Create a dummy RoomSummary to avoid Crash during Sign Out or clear cache
|
// 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?
|
override val roomSummary: RoomSummary?
|
||||||
get() {
|
get() {
|
||||||
var sum: RoomSummaryEntity? = monarchy.fetchCopied { RoomSummaryEntity.where(it, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME).findFirst() }
|
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 {
|
override fun isEncrypted(): Boolean {
|
||||||
|
|
|
@ -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.RoomService
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
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.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.RoomEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
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.model.RoomSummaryEntityFields
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
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.session.room.create.CreateRoomTask
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
|
@ -36,6 +35,7 @@ import im.vector.matrix.android.internal.util.fetchManaged
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultRoomService @Inject constructor(private val monarchy: Monarchy,
|
internal class DefaultRoomService @Inject constructor(private val monarchy: Monarchy,
|
||||||
|
private val roomSummaryMapper: RoomSummaryMapper,
|
||||||
private val createRoomTask: CreateRoomTask,
|
private val createRoomTask: CreateRoomTask,
|
||||||
private val roomFactory: RoomFactory,
|
private val roomFactory: RoomFactory,
|
||||||
private val taskExecutor: TaskExecutor) : RoomService {
|
private val taskExecutor: TaskExecutor) : RoomService {
|
||||||
|
@ -55,7 +55,7 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
|
||||||
override fun liveRoomSummaries(): LiveData<List<RoomSummary>> {
|
override fun liveRoomSummaries(): LiveData<List<RoomSummary>> {
|
||||||
return monarchy.findAllMappedWithChanges(
|
return monarchy.findAllMappedWithChanges(
|
||||||
{ realm -> RoomSummaryEntity.where(realm).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) },
|
{ realm -> RoomSummaryEntity.where(realm).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) },
|
||||||
{ it.asDomain() }
|
{ roomSummaryMapper.map(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -21,6 +21,7 @@ import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
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.crypto.CryptoService
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
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.DefaultMembershipService
|
||||||
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
|
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
|
||||||
import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor
|
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.state.SendStateTask
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService
|
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.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.PaginationTask
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory
|
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -47,6 +48,7 @@ internal class RoomFactory @Inject constructor(private val context: Context,
|
||||||
private val credentials: Credentials,
|
private val credentials: Credentials,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
private val eventFactory: LocalEchoEventFactory,
|
private val eventFactory: LocalEchoEventFactory,
|
||||||
|
private val roomSummaryMapper: RoomSummaryMapper,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||||
private val inviteTask: InviteTask,
|
private val inviteTask: InviteTask,
|
||||||
|
@ -61,9 +63,7 @@ internal class RoomFactory @Inject constructor(private val context: Context,
|
||||||
private val leaveRoomTask: LeaveRoomTask) {
|
private val leaveRoomTask: LeaveRoomTask) {
|
||||||
|
|
||||||
fun create(roomId: String): Room {
|
fun create(roomId: String): Room {
|
||||||
val roomMemberExtractor = SenderRoomMemberExtractor(roomId)
|
val timelineEventFactory = InMemoryTimelineEventFactory(SenderRoomMemberExtractor(), EventRelationExtractor(), cryptoService)
|
||||||
val relationExtractor = EventRelationExtractor()
|
|
||||||
val timelineEventFactory = TimelineEventFactory(roomMemberExtractor, relationExtractor, cryptoService)
|
|
||||||
val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineEventFactory, contextOfEventTask, paginationTask)
|
val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineEventFactory, contextOfEventTask, paginationTask)
|
||||||
val sendService = DefaultSendService(context, credentials, roomId, eventFactory, cryptoService, monarchy)
|
val sendService = DefaultSendService(context, credentials, roomId, eventFactory, cryptoService, monarchy)
|
||||||
val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask)
|
val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask)
|
||||||
|
@ -74,6 +74,7 @@ internal class RoomFactory @Inject constructor(private val context: Context,
|
||||||
return DefaultRoom(
|
return DefaultRoom(
|
||||||
roomId,
|
roomId,
|
||||||
monarchy,
|
monarchy,
|
||||||
|
roomSummaryMapper,
|
||||||
timelineService,
|
timelineService,
|
||||||
sendService,
|
sendService,
|
||||||
stateService,
|
stateService,
|
||||||
|
|
|
@ -138,4 +138,10 @@ internal abstract class RoomModule {
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindTimelineService(timelineService: DefaultTimelineService): TimelineService
|
abstract fun bindTimelineService(timelineService: DefaultTimelineService): TimelineService
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindSimpleTimelineEventFactory(timelineEventFactory: SimpleTimelineEventFactory): TimelineEventFactory
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindCacheableTimelineEventFactory(inMemoryTimelineEventFactory: InMemoryTimelineEventFactory): CacheableTimelineEventFactory
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.latestEvent
|
||||||
import im.vector.matrix.android.internal.database.query.prev
|
import im.vector.matrix.android.internal.database.query.prev
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
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.RoomDisplayNameResolver
|
||||||
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
||||||
import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary
|
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 roomDisplayNameResolver: RoomDisplayNameResolver,
|
||||||
private val roomAvatarResolver: RoomAvatarResolver) {
|
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,
|
fun update(realm: Realm,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
membership: Membership? = null,
|
membership: Membership? = null,
|
||||||
|
@ -47,7 +63,7 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C
|
||||||
unreadNotifications: RoomSyncUnreadNotifications? = null) {
|
unreadNotifications: RoomSyncUnreadNotifications? = null) {
|
||||||
|
|
||||||
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
|
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||||
?: realm.createObject(roomId)
|
?: realm.createObject(roomId)
|
||||||
|
|
||||||
if (roomSummary != null) {
|
if (roomSummary != null) {
|
||||||
if (roomSummary.heroes.isNotEmpty()) {
|
if (roomSummary.heroes.isNotEmpty()) {
|
||||||
|
@ -71,13 +87,13 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C
|
||||||
roomSummaryEntity.membership = membership
|
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 lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain()
|
||||||
val otherRoomMembers = RoomMembers(realm, roomId).getLoaded().filterKeys { it != credentials.userId }
|
val otherRoomMembers = RoomMembers(realm, roomId).getLoaded().filterKeys { it != credentials.userId }
|
||||||
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
|
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
|
||||||
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
|
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
|
||||||
roomSummaryEntity.topic = lastTopicEvent?.content.toModel<RoomTopicContent>()?.topic
|
roomSummaryEntity.topic = lastTopicEvent?.content.toModel<RoomTopicContent>()?.topic
|
||||||
roomSummaryEntity.lastMessage = lastEvent
|
roomSummaryEntity.latestEvent = lastEvent
|
||||||
roomSummaryEntity.otherMemberIds.clear()
|
roomSummaryEntity.otherMemberIds.clear()
|
||||||
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers.keys)
|
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers.keys)
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,9 +34,10 @@ import io.realm.RealmList
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
import javax.inject.Inject
|
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? {
|
fun extractFrom(event: EventEntity, realm: Realm = event.realm): RoomMember? {
|
||||||
|
val roomId = event.roomId
|
||||||
val sender = event.sender ?: return null
|
val sender = event.sender ?: return null
|
||||||
// If the event is unlinked we want to fetch unlinked state events
|
// If the event is unlinked we want to fetch unlinked state events
|
||||||
val unlinked = event.isUnlinked
|
val unlinked = event.isUnlinked
|
||||||
|
|
|
@ -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.findLastLiveChunkFromRoom
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
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.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.StringProvider
|
||||||
import im.vector.matrix.android.internal.util.tryTransactionAsync
|
import im.vector.matrix.android.internal.util.tryTransactionAsync
|
||||||
import org.commonmark.parser.Parser
|
import org.commonmark.parser.Parser
|
||||||
|
@ -50,8 +51,9 @@ import javax.inject.Inject
|
||||||
*
|
*
|
||||||
* The transactionID is used as loc
|
* The transactionID is used as loc
|
||||||
*/
|
*/
|
||||||
|
internal class LocalEchoEventFactory @Inject constructor(private val credentials: Credentials,
|
||||||
internal class LocalEchoEventFactory @Inject constructor(private val credentials: Credentials, private val stringProvider: StringProvider) {
|
private val stringProvider: StringProvider,
|
||||||
|
private val roomSummaryUpdater: RoomSummaryUpdater) {
|
||||||
|
|
||||||
fun createTextEvent(roomId: String, msgType: String, text: String, autoMarkdown: Boolean): Event {
|
fun createTextEvent(roomId: String, msgType: String, text: String, autoMarkdown: Boolean): Event {
|
||||||
if (autoMarkdown && msgType == MessageType.MSGTYPE_TEXT) {
|
if (autoMarkdown && msgType == MessageType.MSGTYPE_TEXT) {
|
||||||
|
@ -342,10 +344,12 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveLocalEcho(monarchy: Monarchy, event: Event) {
|
fun saveLocalEcho(monarchy: Monarchy, event: Event) {
|
||||||
|
if (event.roomId == null) throw IllegalStateException("Your event should have a roomId")
|
||||||
monarchy.tryTransactionAsync { realm ->
|
monarchy.tryTransactionAsync { realm ->
|
||||||
val roomEntity = RoomEntity.where(realm, roomId = event.roomId!!).findFirst()
|
val roomEntity = RoomEntity.where(realm, roomId = event.roomId).findFirst()
|
||||||
?: return@tryTransactionAsync
|
?: return@tryTransactionAsync
|
||||||
roomEntity.addSendingEvent(event)
|
roomEntity.addSendingEvent(event)
|
||||||
|
roomSummaryUpdater.update(realm, event.roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ internal class DefaultTimeline(
|
||||||
private val realmConfiguration: RealmConfiguration,
|
private val realmConfiguration: RealmConfiguration,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val contextOfEventTask: GetContextOfEventTask,
|
private val contextOfEventTask: GetContextOfEventTask,
|
||||||
private val timelineEventFactory: TimelineEventFactory,
|
private val timelineEventFactory: CacheableTimelineEventFactory,
|
||||||
private val paginationTask: PaginationTask,
|
private val paginationTask: PaginationTask,
|
||||||
private val allowedTypes: List<String>?
|
private val allowedTypes: List<String>?
|
||||||
) : Timeline {
|
) : Timeline {
|
||||||
|
@ -129,7 +129,7 @@ internal class DefaultTimeline(
|
||||||
builtEventsIdMap[eventId]?.let { builtIndex ->
|
builtEventsIdMap[eventId]?.let { builtIndex ->
|
||||||
//Update the relation of existing event
|
//Update the relation of existing event
|
||||||
builtEvents[builtIndex]?.let { te ->
|
builtEvents[builtIndex]?.let { te ->
|
||||||
builtEvents[builtIndex] = timelineEventFactory.create(eventEntity)
|
builtEvents[builtIndex] = timelineEventFactory.create(eventEntity, eventEntity.realm)
|
||||||
hasChanged = true
|
hasChanged = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -290,7 +290,7 @@ internal class DefaultTimeline(
|
||||||
roomEntity?.sendingTimelineEvents
|
roomEntity?.sendingTimelineEvents
|
||||||
?.filter { allowedTypes?.contains(it.type) ?: false }
|
?.filter { allowedTypes?.contains(it.type) ?: false }
|
||||||
?.forEach {
|
?.forEach {
|
||||||
val timelineEvent = timelineEventFactory.create(it)
|
val timelineEvent = timelineEventFactory.create(it, it.realm)
|
||||||
sendingEvents.add(timelineEvent)
|
sendingEvents.add(timelineEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -418,7 +418,7 @@ internal class DefaultTimeline(
|
||||||
nextDisplayIndex = offsetIndex + 1
|
nextDisplayIndex = offsetIndex + 1
|
||||||
}
|
}
|
||||||
offsetResults.forEach { eventEntity ->
|
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
|
val position = if (direction == Timeline.Direction.FORWARDS) 0 else builtEvents.size
|
||||||
builtEvents.add(position, timelineEvent)
|
builtEvents.add(position, timelineEvent)
|
||||||
//Need to shift :/
|
//Need to shift :/
|
||||||
|
|
|
@ -33,7 +33,7 @@ import javax.inject.Inject
|
||||||
internal class DefaultTimelineService @Inject constructor(private val roomId: String,
|
internal class DefaultTimelineService @Inject constructor(private val roomId: String,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val timelineEventFactory: TimelineEventFactory,
|
private val timelineEventFactory: CacheableTimelineEventFactory,
|
||||||
private val contextOfEventTask: GetContextOfEventTask,
|
private val contextOfEventTask: GetContextOfEventTask,
|
||||||
private val paginationTask: PaginationTask
|
private val paginationTask: PaginationTask
|
||||||
) : TimelineService {
|
) : TimelineService {
|
||||||
|
@ -60,14 +60,14 @@ internal class DefaultTimelineService @Inject constructor(private val roomId: St
|
||||||
}
|
}
|
||||||
val result = MediatorLiveData<TimelineEvent>()
|
val result = MediatorLiveData<TimelineEvent>()
|
||||||
result.addSource(liveEventEntity) { realmResults ->
|
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) {
|
result.addSource(liveAnnotationsEntity) {
|
||||||
liveEventEntity.value?.let {
|
liveEventEntity.value?.let {
|
||||||
result.value = liveEventEntity.value?.let { realmResults ->
|
result.value = liveEventEntity.value?.let { realmResults ->
|
||||||
//recreate the timeline event
|
//recreate the timeline event
|
||||||
realmResults.firstOrNull()?.let { timelineEventFactory.create(it) }
|
realmResults.firstOrNull()?.let { timelineEventFactory.create(it, it.realm) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,20 +33,69 @@ import timber.log.Timber
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
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]
|
* 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.
|
* It handles decryption, extracting additional data around an event as sender data and relation.
|
||||||
*/
|
*/
|
||||||
internal class TimelineEventFactory @Inject constructor(
|
internal class SimpleTimelineEventFactory @Inject constructor(private val roomMemberExtractor: SenderRoomMemberExtractor,
|
||||||
private val roomMemberExtractor: SenderRoomMemberExtractor,
|
private val relationExtractor: EventRelationExtractor,
|
||||||
private val relationExtractor: EventRelationExtractor,
|
private val cryptoService: CryptoService
|
||||||
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 timelineId = UUID.randomUUID().toString()
|
||||||
private val senderCache = mutableMapOf<String, SenderData>()
|
private val senderCache = mutableMapOf<String, SenderData>()
|
||||||
private val decryptionCache = mutableMapOf<String, MXEventDecryptionResult>()
|
private val decryptionCache = mutableMapOf<String, MXEventDecryptionResult>()
|
||||||
|
|
||||||
fun create(eventEntity: EventEntity, realm: Realm = eventEntity.realm): TimelineEvent {
|
override fun create(eventEntity: EventEntity, realm: Realm): TimelineEvent {
|
||||||
val sender = eventEntity.sender
|
val sender = eventEntity.sender
|
||||||
val cacheKey = sender + eventEntity.localId
|
val cacheKey = sender + eventEntity.localId
|
||||||
val senderData = senderCache.getOrPut(cacheKey) {
|
val senderData = senderCache.getOrPut(cacheKey) {
|
||||||
|
@ -97,8 +146,9 @@ internal class TimelineEventFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear() {
|
override fun clear() {
|
||||||
senderCache.clear()
|
senderCache.clear()
|
||||||
|
decryptionCache.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class SenderData(
|
private data class SenderData(
|
||||||
|
|
|
@ -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
|
* 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
|
if (newText == null
|
||||||
|| (newText.isBlank() && hideWhenBlank)) {
|
|| (newText.isBlank() && hideWhenBlank)) {
|
||||||
isVisible = false
|
isVisible = false
|
||||||
|
|
|
@ -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.command.CommandAutocompletePolicy
|
||||||
import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserPresenter
|
import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserPresenter
|
||||||
import im.vector.riotredesign.features.command.Command
|
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.TextComposerActions
|
||||||
import im.vector.riotredesign.features.home.room.detail.composer.TextComposerView
|
import im.vector.riotredesign.features.home.room.detail.composer.TextComposerView
|
||||||
import im.vector.riotredesign.features.home.room.detail.composer.TextComposerViewModel
|
import im.vector.riotredesign.features.home.room.detail.composer.TextComposerViewModel
|
||||||
|
@ -509,6 +512,9 @@ class RoomDetailFragment :
|
||||||
} else if (summary?.membership == Membership.INVITE && inviter != null) {
|
} else if (summary?.membership == Membership.INVITE && inviter != null) {
|
||||||
inviteView.visibility = View.VISIBLE
|
inviteView.visibility = View.VISIBLE
|
||||||
inviteView.render(inviter, VectorInviteView.Mode.LARGE)
|
inviteView.render(inviter, VectorInviteView.Mode.LARGE)
|
||||||
|
|
||||||
|
// Intercept click event
|
||||||
|
inviteView.setOnClickListener { }
|
||||||
} else if (state.asyncInviter.complete) {
|
} else if (state.asyncInviter.complete) {
|
||||||
vectorBaseActivity.finish()
|
vectorBaseActivity.finish()
|
||||||
}
|
}
|
||||||
|
|
|
@ -504,7 +504,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
private fun observeInvitationState() {
|
private fun observeInvitationState() {
|
||||||
asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary ->
|
asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary ->
|
||||||
if (summary.membership == Membership.INVITE) {
|
if (summary.membership == Membership.INVITE) {
|
||||||
summary.lastMessage?.senderId?.let { senderId ->
|
summary.latestEvent?.root?.senderId?.let { senderId ->
|
||||||
session.getUser(senderId)
|
session.getUser(senderId)
|
||||||
}?.also {
|
}?.also {
|
||||||
setState { copy(asyncInviter = Success(it)) }
|
setState { copy(asyncInviter = Success(it)) }
|
||||||
|
|
|
@ -25,14 +25,14 @@ class ChronologicalRoomComparator @Inject constructor() : Comparator<RoomSummary
|
||||||
var rightTimestamp = 0L
|
var rightTimestamp = 0L
|
||||||
var leftTimestamp = 0L
|
var leftTimestamp = 0L
|
||||||
if (null != leftRoomSummary) {
|
if (null != leftRoomSummary) {
|
||||||
leftTimestamp = leftRoomSummary.lastMessage?.originServerTs ?: 0
|
leftTimestamp = leftRoomSummary.latestEvent?.root?.originServerTs ?: 0
|
||||||
}
|
}
|
||||||
if (null != rightRoomSummary) {
|
if (null != rightRoomSummary) {
|
||||||
rightTimestamp = rightRoomSummary.lastMessage?.originServerTs ?: 0
|
rightTimestamp = rightRoomSummary.latestEvent?.root?.originServerTs ?: 0
|
||||||
}
|
}
|
||||||
return if (rightRoomSummary?.lastMessage == null) {
|
return if (rightRoomSummary?.latestEvent?.root == null) {
|
||||||
-1
|
-1
|
||||||
} else if (leftRoomSummary?.lastMessage == null) {
|
} else if (leftRoomSummary?.latestEvent?.root == null) {
|
||||||
1
|
1
|
||||||
} else {
|
} else {
|
||||||
val deltaTimestamp = rightTimestamp - leftTimestamp
|
val deltaTimestamp = rightTimestamp - leftTimestamp
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* 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 android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.riotredesign.R
|
||||||
|
import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.riotredesign.core.extensions.setTextOrHide
|
||||||
|
import im.vector.riotredesign.core.platform.ButtonStateView
|
||||||
|
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||||
|
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_room_invitation)
|
||||||
|
abstract class RoomInvitationItem : VectorEpoxyModel<RoomInvitationItem.Holder>() {
|
||||||
|
|
||||||
|
@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<TextView>(R.id.roomInvitationNameView)
|
||||||
|
val subtitleView by bind<TextView>(R.id.roomInvitationSubTitle)
|
||||||
|
val acceptView by bind<ButtonStateView>(R.id.roomInvitationAccept)
|
||||||
|
val rejectView by bind<ButtonStateView>(R.id.roomInvitationReject)
|
||||||
|
val avatarImageView by bind<ImageView>(R.id.roomInvitationAvatarImageView)
|
||||||
|
val rootView by bind<ViewGroup>(R.id.itemRoomInvitationLayout)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -22,8 +22,10 @@ sealed class RoomListActions {
|
||||||
|
|
||||||
data class SelectRoom(val roomSummary: RoomSummary) : 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 ToggleCategory(val category: RoomCategory) : RoomListActions()
|
||||||
|
|
||||||
|
data class AcceptInvitation(val roomSummary: RoomSummary) : RoomListActions()
|
||||||
|
|
||||||
|
data class RejectInvitation(val roomSummary: RoomSummary) : RoomListActions()
|
||||||
|
|
||||||
}
|
}
|
|
@ -24,12 +24,15 @@ import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.airbnb.mvrx.*
|
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.failure.Failure
|
||||||
import im.vector.matrix.android.api.session.room.model.Membership
|
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.RoomSummary
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
import im.vector.riotredesign.core.di.ScreenComponent
|
import im.vector.riotredesign.core.di.ScreenComponent
|
||||||
import im.vector.riotredesign.core.epoxy.LayoutManagerStateRestorer
|
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.extensions.observeEventDebounced
|
||||||
import im.vector.riotredesign.core.platform.OnBackPressed
|
import im.vector.riotredesign.core.platform.OnBackPressed
|
||||||
import im.vector.riotredesign.core.platform.StateView
|
import im.vector.riotredesign.core.platform.StateView
|
||||||
|
@ -45,7 +48,7 @@ data class RoomListParams(
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
|
|
||||||
class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, OnBackPressed, FabMenuView.Listener {
|
class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, FabMenuView.Listener {
|
||||||
|
|
||||||
enum class DisplayMode(@StringRes val titleRes: Int) {
|
enum class DisplayMode(@StringRes val titleRes: Int) {
|
||||||
HOME(R.string.bottom_action_home),
|
HOME(R.string.bottom_action_home),
|
||||||
|
@ -64,6 +67,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O
|
||||||
private val roomListParams: RoomListParams by args()
|
private val roomListParams: RoomListParams by args()
|
||||||
@Inject lateinit var roomController: RoomSummaryController
|
@Inject lateinit var roomController: RoomSummaryController
|
||||||
@Inject lateinit var roomListViewModelFactory: RoomListViewModel.Factory
|
@Inject lateinit var roomListViewModelFactory: RoomListViewModel.Factory
|
||||||
|
@Inject lateinit var errorFormatter: ErrorFormatter
|
||||||
private val roomListViewModel: RoomListViewModel by fragmentViewModel()
|
private val roomListViewModel: RoomListViewModel by fragmentViewModel()
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_room_list
|
override fun getLayoutResId() = R.layout.fragment_room_list
|
||||||
|
@ -82,6 +86,13 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O
|
||||||
}
|
}
|
||||||
|
|
||||||
createChatFabMenu.listener = this
|
createChatFabMenu.listener = this
|
||||||
|
|
||||||
|
roomListViewModel.invitationAnswerErrorLiveData.observeEvent(this) { throwable ->
|
||||||
|
vectorBaseActivity.coordinatorLayout?.let {
|
||||||
|
Snackbar.make(it, errorFormatter.toHumanReadable(throwable), Snackbar.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupCreateRoomButton() {
|
private fun setupCreateRoomButton() {
|
||||||
|
@ -135,7 +146,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O
|
||||||
val stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
|
val stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
|
||||||
epoxyRecyclerView.layoutManager = layoutManager
|
epoxyRecyclerView.layoutManager = layoutManager
|
||||||
epoxyRecyclerView.itemAnimator = RoomListAnimator()
|
epoxyRecyclerView.itemAnimator = RoomListAnimator()
|
||||||
roomController.callback = this
|
roomController.listener = this
|
||||||
roomController.addModelBuildListener { it.dispatchTo(stateRestorer) }
|
roomController.addModelBuildListener { it.dispatchTo(stateRestorer) }
|
||||||
stateView.contentView = epoxyRecyclerView
|
stateView.contentView = epoxyRecyclerView
|
||||||
epoxyRecyclerView.setController(roomController)
|
epoxyRecyclerView.setController(roomController)
|
||||||
|
@ -233,6 +244,14 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O
|
||||||
roomListViewModel.accept(RoomListActions.SelectRoom(room))
|
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) {
|
override fun onToggleRoomCategory(roomCategory: RoomCategory) {
|
||||||
roomListViewModel.accept(RoomListActions.ToggleCategory(roomCategory))
|
roomListViewModel.accept(RoomListActions.ToggleCategory(roomCategory))
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,13 +18,12 @@ package im.vector.riotredesign.features.home.room.list
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import arrow.core.Option
|
|
||||||
import com.airbnb.mvrx.FragmentViewModelContext
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
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.Session
|
||||||
import im.vector.matrix.android.api.session.room.model.Membership
|
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.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.platform.VectorViewModel
|
||||||
import im.vector.riotredesign.core.utils.LiveEvent
|
import im.vector.riotredesign.core.utils.LiveEvent
|
||||||
import im.vector.riotredesign.features.home.HomeRoomListObservableStore
|
import im.vector.riotredesign.features.home.HomeRoomListObservableStore
|
||||||
|
import timber.log.Timber
|
||||||
typealias RoomListFilterName = CharSequence
|
|
||||||
|
|
||||||
class RoomListViewModel @AssistedInject constructor(@Assisted initialState: RoomListViewState,
|
class RoomListViewModel @AssistedInject constructor(@Assisted initialState: RoomListViewState,
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
|
@ -57,22 +55,25 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
|
||||||
}
|
}
|
||||||
|
|
||||||
private val displayMode = initialState.displayMode
|
private val displayMode = initialState.displayMode
|
||||||
private val roomListDisplayModeFilter = RoomListDisplayModeFilter(displayMode)
|
|
||||||
private val roomListFilter = BehaviorRelay.createDefault<Option<RoomListFilterName>>(Option.empty())
|
|
||||||
|
|
||||||
private val _openRoomLiveData = MutableLiveData<LiveEvent<String>>()
|
private val _openRoomLiveData = MutableLiveData<LiveEvent<String>>()
|
||||||
val openRoomLiveData: LiveData<LiveEvent<String>>
|
val openRoomLiveData: LiveData<LiveEvent<String>>
|
||||||
get() = _openRoomLiveData
|
get() = _openRoomLiveData
|
||||||
|
|
||||||
|
private val _invitationAnswerErrorLiveData = MutableLiveData<LiveEvent<Throwable>>()
|
||||||
|
val invitationAnswerErrorLiveData: LiveData<LiveEvent<Throwable>>
|
||||||
|
get() = _invitationAnswerErrorLiveData
|
||||||
|
|
||||||
init {
|
init {
|
||||||
observeRoomSummaries()
|
observeRoomSummaries()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun accept(action: RoomListActions) {
|
fun accept(action: RoomListActions) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is RoomListActions.SelectRoom -> handleSelectRoom(action)
|
is RoomListActions.SelectRoom -> handleSelectRoom(action)
|
||||||
is RoomListActions.FilterRooms -> handleFilterRooms(action)
|
is RoomListActions.ToggleCategory -> handleToggleCategory(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))
|
_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 {
|
private fun handleToggleCategory(action: RoomListActions.ToggleCategory) = setState {
|
||||||
this.toggle(action.category)
|
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<Unit> {
|
||||||
|
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<Unit> {
|
||||||
|
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<RoomSummary>): RoomSummaries {
|
private fun buildRoomSummaries(rooms: List<RoomSummary>): RoomSummaries {
|
||||||
val invites = ArrayList<RoomSummary>()
|
val invites = ArrayList<RoomSummary>()
|
||||||
val favourites = ArrayList<RoomSummary>()
|
val favourites = ArrayList<RoomSummary>()
|
||||||
|
|
|
@ -27,6 +27,14 @@ data class RoomListViewState(
|
||||||
val displayMode: RoomListFragment.DisplayMode,
|
val displayMode: RoomListFragment.DisplayMode,
|
||||||
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
|
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
|
||||||
val asyncFilteredRooms: Async<RoomSummaries> = Uninitialized,
|
val asyncFilteredRooms: Async<RoomSummaries> = Uninitialized,
|
||||||
|
// List of roomIds that the user wants to join
|
||||||
|
val joiningRoomsIds: Set<String> = emptySet(),
|
||||||
|
// List of roomIds that the user wants to join, but an error occurred
|
||||||
|
val joiningErrorRoomsIds: Set<String> = emptySet(),
|
||||||
|
// List of roomIds that the user wants to join
|
||||||
|
val rejectingRoomsIds: Set<String> = emptySet(),
|
||||||
|
// List of roomIds that the user wants to reject, but an error occurred
|
||||||
|
val rejectingErrorRoomsIds: Set<String> = emptySet(),
|
||||||
val isInviteExpanded: Boolean = true,
|
val isInviteExpanded: Boolean = true,
|
||||||
val isFavouriteRoomsExpanded: Boolean = true,
|
val isFavouriteRoomsExpanded: Boolean = true,
|
||||||
val isDirectRoomsExpanded: Boolean = true,
|
val isDirectRoomsExpanded: Boolean = true,
|
||||||
|
|
|
@ -18,25 +18,15 @@ package im.vector.riotredesign.features.home.room.list
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
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.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.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
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomSummaryController @Inject constructor(private val stringProvider: StringProvider,
|
class RoomSummaryController @Inject constructor(private val stringProvider: StringProvider,
|
||||||
private val eventFormatter: NoticeEventFormatter,
|
private val roomSummaryItemFactory: RoomSummaryItemFactory
|
||||||
private val timelineDateFormatter: TimelineDateFormatter,
|
|
||||||
private val avatarRenderer: AvatarRenderer
|
|
||||||
) : TypedEpoxyController<RoomListViewState>() {
|
) : TypedEpoxyController<RoomListViewState>() {
|
||||||
|
|
||||||
var callback: Callback? = null
|
var listener: Listener? = null
|
||||||
|
|
||||||
override fun buildModels(viewState: RoomListViewState) {
|
override fun buildModels(viewState: RoomListViewState) {
|
||||||
val roomSummaries = viewState.asyncFilteredRooms()
|
val roomSummaries = viewState.asyncFilteredRooms()
|
||||||
|
@ -46,10 +36,14 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
|
||||||
} else {
|
} else {
|
||||||
val isExpanded = viewState.isCategoryExpanded(category)
|
val isExpanded = viewState.isCategoryExpanded(category)
|
||||||
buildRoomCategory(viewState, summaries, category.titleRes, viewState.isCategoryExpanded(category)) {
|
buildRoomCategory(viewState, summaries, category.titleRes, viewState.isCategoryExpanded(category)) {
|
||||||
callback?.onToggleRoomCategory(category)
|
listener?.onToggleRoomCategory(category)
|
||||||
}
|
}
|
||||||
if (isExpanded) {
|
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<RoomSummary>) {
|
private fun buildRoomModels(summaries: List<RoomSummary>,
|
||||||
|
joiningRoomsIds: Set<String>,
|
||||||
|
joiningErrorRoomsIds: Set<String>,
|
||||||
|
rejectingRoomsIds: Set<String>,
|
||||||
|
rejectingErrorRoomsIds: Set<String>) {
|
||||||
summaries.forEach { roomSummary ->
|
summaries.forEach { roomSummary ->
|
||||||
val unreadCount = roomSummary.notificationCount
|
roomSummaryItemFactory
|
||||||
val showHighlighted = roomSummary.highlightCount > 0
|
.create(roomSummary, joiningRoomsIds, joiningErrorRoomsIds, rejectingRoomsIds, rejectingErrorRoomsIds, listener)
|
||||||
|
.addTo(this)
|
||||||
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<MessageContent>()
|
|
||||||
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) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Callback {
|
interface Listener {
|
||||||
fun onToggleRoomCategory(roomCategory: RoomCategory)
|
fun onToggleRoomCategory(roomCategory: RoomCategory)
|
||||||
fun onRoomSelected(room: RoomSummary)
|
fun onRoomSelected(room: RoomSummary)
|
||||||
|
fun onRejectRoomInvitation(room: RoomSummary)
|
||||||
|
fun onAcceptRoomInvitation(room: RoomSummary)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<String>,
|
||||||
|
joiningErrorRoomsIds: Set<String>,
|
||||||
|
rejectingRoomsIds: Set<String>,
|
||||||
|
rejectingErrorRoomsIds: Set<String>,
|
||||||
|
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<String>,
|
||||||
|
joiningErrorRoomsIds: Set<String>,
|
||||||
|
rejectingRoomsIds: Set<String>,
|
||||||
|
rejectingErrorRoomsIds: Set<String>,
|
||||||
|
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<MessageContent>()
|
||||||
|
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) }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -28,11 +28,11 @@ data class PublicRoomsViewState(
|
||||||
val asyncPublicRoomsRequest: Async<List<PublicRoom>> = Uninitialized,
|
val asyncPublicRoomsRequest: Async<List<PublicRoom>> = Uninitialized,
|
||||||
// True if more result are available server side
|
// True if more result are available server side
|
||||||
val hasMore: Boolean = false,
|
val hasMore: Boolean = false,
|
||||||
// List of roomIds that the user wants to join
|
// Set of roomIds that the user wants to join
|
||||||
val joiningRoomsIds: List<String> = emptyList(),
|
val joiningRoomsIds: Set<String> = emptySet(),
|
||||||
// List of roomIds that the user wants to join, but an error occurred
|
// Set of roomIds that the user wants to join, but an error occurred
|
||||||
val joiningErrorRoomsIds: List<String> = emptyList(),
|
val joiningErrorRoomsIds: Set<String> = emptySet(),
|
||||||
// List of joined roomId,
|
// Set of joined roomId,
|
||||||
val joinedRoomsIds: List<String> = emptyList(),
|
val joinedRoomsIds: Set<String> = emptySet(),
|
||||||
val roomDirectoryDisplayName: String? = null
|
val roomDirectoryDisplayName: String? = null
|
||||||
) : MvRxState
|
) : MvRxState
|
|
@ -18,13 +18,7 @@ package im.vector.riotredesign.features.roomdirectory
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import com.airbnb.mvrx.ActivityViewModelContext
|
import com.airbnb.mvrx.*
|
||||||
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.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
@ -95,19 +89,19 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
||||||
.liveRoomSummaries()
|
.liveRoomSummaries()
|
||||||
.subscribe { list ->
|
.subscribe { list ->
|
||||||
val joinedRoomIds = list
|
val joinedRoomIds = list
|
||||||
// Keep only joined room
|
// Keep only joined room
|
||||||
?.filter { it.membership == Membership.JOIN }
|
?.filter { it.membership == Membership.JOIN }
|
||||||
?.map { it.roomId }
|
?.map { it.roomId }
|
||||||
?.toList()
|
?.toSet()
|
||||||
?: emptyList()
|
?: emptySet()
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
joinedRoomsIds = joinedRoomIds,
|
joinedRoomsIds = joinedRoomIds,
|
||||||
// Remove (newly) joined room id from the joining room list
|
// 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
|
// 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() {
|
private fun load() {
|
||||||
currentTask = session.getPublicRooms(roomDirectoryData.homeServer,
|
currentTask = session.getPublicRooms(roomDirectoryData.homeServer,
|
||||||
PublicRoomsParams(
|
PublicRoomsParams(
|
||||||
limit = PUBLIC_ROOMS_LIMIT,
|
limit = PUBLIC_ROOMS_LIMIT,
|
||||||
filter = PublicRoomsFilter(searchTerm = currentFilter),
|
filter = PublicRoomsFilter(searchTerm = currentFilter),
|
||||||
includeAllNetworks = roomDirectoryData.includeAllNetworks,
|
includeAllNetworks = roomDirectoryData.includeAllNetworks,
|
||||||
since = since,
|
since = since,
|
||||||
thirdPartyInstanceId = roomDirectoryData.thirdPartyInstanceId
|
thirdPartyInstanceId = roomDirectoryData.thirdPartyInstanceId
|
||||||
),
|
),
|
||||||
object : MatrixCallback<PublicRoomsResponse> {
|
object : MatrixCallback<PublicRoomsResponse> {
|
||||||
override fun onSuccess(data: PublicRoomsResponse) {
|
override fun onSuccess(data: PublicRoomsResponse) {
|
||||||
currentTask = null
|
currentTask = null
|
||||||
|
|
||||||
since = data.nextBatch
|
since = data.nextBatch
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
asyncPublicRoomsRequest = Success(data.chunk!!),
|
asyncPublicRoomsRequest = Success(data.chunk!!),
|
||||||
// It's ok to append at the end of the list, so I use publicRooms.size()
|
// It's ok to append at the end of the list, so I use publicRooms.size()
|
||||||
publicRooms = publicRooms.appendAt(data.chunk!!, publicRooms.size),
|
publicRooms = publicRooms.appendAt(data.chunk!!, publicRooms.size),
|
||||||
hasMore = since != null
|
hasMore = since != null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
currentTask = null
|
currentTask = null
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
asyncPublicRoomsRequest = Fail(failure)
|
asyncPublicRoomsRequest = Fail(failure)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun joinRoom(publicRoom: PublicRoom) = withState { state ->
|
fun joinRoom(publicRoom: PublicRoom) = withState { state ->
|
||||||
|
@ -210,7 +204,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
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 {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
joiningRoomsIds = joiningRoomsIds.toMutableList().apply { remove(publicRoom.roomId) },
|
joiningRoomsIds = joiningRoomsIds.toMutableSet().apply { remove(publicRoom.roomId) },
|
||||||
joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableList().apply { add(publicRoom.roomId) }
|
joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableSet().apply { add(publicRoom.roomId) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
tools:openDrawer="start">
|
tools:openDrawer="start">
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
android:id="@+id/coordinatorLayout"
|
android:id="@+id/vector_coordinator_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
|
|
@ -93,6 +93,7 @@
|
||||||
android:id="@+id/inviteView"
|
android:id="@+id/inviteView"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?riotx_background"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
|
117
vector/src/main/res/layout/item_room_invitation.xml
Normal file
117
vector/src/main/res/layout/item_room_invitation.xml
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/itemRoomInvitationLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?riotx_background"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:foreground="?attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/roomInvitationAvatarImageView"
|
||||||
|
android:layout_width="56dp"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<!-- Margin bottom does not work, so I use space -->
|
||||||
|
<Space
|
||||||
|
android:id="@+id/roomInvitationAvatarBottomSpace"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="12dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/roomInvitationAvatarImageView"
|
||||||
|
tools:layout_marginStart="20dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/roomInvitationNameView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||||
|
android:layout_marginLeft="@dimen/layout_horizontal_margin"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:layout_marginRight="20dp"
|
||||||
|
android:drawableEnd="@drawable/ic_arrow_right"
|
||||||
|
android:drawablePadding="8dp"
|
||||||
|
android:duplicateParentState="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/roomInvitationAvatarImageView"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="@sample/matrix.json/data/displayName" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/roomInvitationSubTitle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="3dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:textColor="?riotx_text_secondary"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/roomInvitationNameView"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/roomInvitationNameView"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/roomInvitationNameView"
|
||||||
|
tools:text="@sample/matrix.json/data/message" />
|
||||||
|
|
||||||
|
<!-- Margin bottom does not work, so I use space -->
|
||||||
|
<Space
|
||||||
|
android:id="@+id/roomLastEventBottomSpace"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="7dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/roomInvitationSubTitle"
|
||||||
|
tools:layout_marginStart="120dp" />
|
||||||
|
|
||||||
|
<im.vector.riotredesign.core.platform.ButtonStateView
|
||||||
|
android:id="@+id/roomInvitationAccept"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:minWidth="122dp"
|
||||||
|
app:bsv_button_text="@string/accept"
|
||||||
|
app:bsv_loaded_image_src="@drawable/ic_tick"
|
||||||
|
app:bsv_use_flat_button="false"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/roomInvitationNameView"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/roomLastEventBottomSpace" />
|
||||||
|
|
||||||
|
<im.vector.riotredesign.core.platform.ButtonStateView
|
||||||
|
android:id="@+id/roomInvitationReject"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="@dimen/layout_vertical_margin"
|
||||||
|
android:minWidth="122dp"
|
||||||
|
app:bsv_button_text="@string/reject"
|
||||||
|
app:bsv_loaded_image_src="@drawable/ic_tick"
|
||||||
|
app:bsv_use_flat_button="true"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/roomInvitationAccept"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/roomInvitationAccept" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/roomInvitationDividerView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:background="?riotx_header_panel_border_mobile"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/roomInvitationAccept" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -142,6 +142,16 @@
|
||||||
<item name="colorControlHighlight">?colorAccent</item>
|
<item name="colorControlHighlight">?colorAccent</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="VectorButtonStyleOutlined" parent="Widget.MaterialComponents.Button.OutlinedButton">
|
||||||
|
<item name="android:textStyle">bold</item>
|
||||||
|
<item name="android:textAllCaps">false</item>
|
||||||
|
<item name="android:layout_width">wrap_content</item>
|
||||||
|
<item name="android:layout_height">wrap_content</item>
|
||||||
|
<item name="android:background">@null</item>
|
||||||
|
<!--item name="android:textColor">?colorAccent</item-->
|
||||||
|
<item name="colorControlHighlight">?colorAccent</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="AlerterButton" parent="Widget.AppCompat.Button.Borderless.Colored">
|
<style name="AlerterButton" parent="Widget.AppCompat.Button.Borderless.Colored">
|
||||||
<item name="colorAccent">@android:color/white</item>
|
<item name="colorAccent">@android:color/white</item>
|
||||||
<item name="android:textColor">@android:color/white</item>
|
<item name="android:textColor">@android:color/white</item>
|
||||||
|
|
Loading…
Reference in a new issue