Start introducing theme on app + branch toolbar to drawer, still requires refinements

This commit is contained in:
ganfra
2018-10-29 14:57:36 +01:00
parent e5fc1e3412
commit cc29a387c7
32 changed files with 315 additions and 74 deletions

View File

@ -1,15 +1,18 @@
package im.vector.matrix.android.api.session.room
import android.arch.lifecycle.LiveData
import im.vector.matrix.android.api.session.room.model.MyMembership
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.util.Cancelable
interface Room: TimelineHolder {
interface Room : TimelineHolder {
val roomId: String
val myMembership: MyMembership
fun getNumberOfJoinedMembers(): Int
val roomSummary: LiveData<RoomSummary>
fun loadRoomMembersIfNeeded(): Cancelable
}

View File

@ -0,0 +1,9 @@
package im.vector.matrix.android.api.session.room.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class RoomAvatarContent(
@Json(name = "url") val avatarUrl: String? = null
)

View File

@ -2,6 +2,7 @@ package im.vector.matrix.android.api.session.room.model
data class RoomSummary(
val roomId: String,
var displayName: String = "",
var topic: String = ""
val displayName: String = "",
val topic: String = "",
val avatarUrl: String = ""
)

View File

@ -10,7 +10,8 @@ object RoomSummaryMapper {
return RoomSummary(
roomSummaryEntity.roomId,
roomSummaryEntity.displayName ?: "",
roomSummaryEntity.topic ?: ""
roomSummaryEntity.topic ?: "",
roomSummaryEntity.avatarUrl ?: ""
)
}
@ -18,7 +19,8 @@ object RoomSummaryMapper {
return RoomSummaryEntity(
roomSummary.roomId,
roomSummary.displayName,
roomSummary.topic
roomSummary.topic,
roomSummary.avatarUrl
)
}
}

View File

@ -7,6 +7,7 @@ import io.realm.annotations.PrimaryKey
// TODO to be completed
open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
var displayName: String? = "",
var avatarUrl: String? = "",
var topic: String? = "",
var lastMessage: EventEntity? = null,
var heroes: RealmList<String> = RealmList(),

View File

@ -1,8 +1,5 @@
package im.vector.matrix.android.internal.database.query
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.ChunkEntityFields
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
@ -45,13 +42,3 @@ fun RealmQuery<EventEntity>.last(from: Long? = null): EventEntity? {
fun RealmList<EventEntity>.fastContains(eventEntity: EventEntity): Boolean {
return this.where().equalTo(EventEntityFields.EVENT_ID, eventEntity.eventId).findFirst() != null
}
fun EventEntity.Companion.findAllRoomMembers(realm: Realm, roomId: String): Map<String, RoomMember> {
return EventEntity
.where(realm, roomId, EventType.STATE_ROOM_MEMBER)
.sort(EventEntityFields.ORIGIN_SERVER_TS)
.findAll()
.map { it.asDomain() }
.associateBy { it.stateKey!! }
.mapValues { it.value.content<RoomMember>()!! }
}

View File

@ -4,9 +4,10 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.internal.auth.data.SessionParams
import im.vector.matrix.android.internal.session.room.DefaultRoomService
import im.vector.matrix.android.internal.session.room.RoomAvatarResolver
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver
import im.vector.matrix.android.internal.session.room.members.RoomMemberDisplayNameResolver
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import io.realm.RealmConfiguration
import org.koin.dsl.context.ModuleDefinition
import org.koin.dsl.module.Module
@ -37,11 +38,15 @@ class SessionModule(private val sessionParams: SessionParams) : Module {
}
scope(DefaultSession.SCOPE) {
RoomDisplayNameResolver(get(), get(), sessionParams)
RoomDisplayNameResolver(get(), get(), sessionParams.credentials)
}
scope(DefaultSession.SCOPE) {
RoomSummaryUpdater(get(), get(), get())
RoomAvatarResolver(get(), sessionParams.credentials)
}
scope(DefaultSession.SCOPE) {
RoomSummaryUpdater(get(), get(), get(), get())
}
scope(DefaultSession.SCOPE) {

View File

@ -1,6 +1,7 @@
package im.vector.matrix.android.internal.session.room
import android.arch.lifecycle.LiveData
import android.arch.lifecycle.Transformations
import android.arch.paging.PagedList
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
@ -9,7 +10,9 @@ import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.TimelineHolder
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.MyMembership
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.mapper.asDomain
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.query.where
@ -24,18 +27,25 @@ data class DefaultRoom(
override val myMembership: MyMembership
) : Room, KoinComponent {
private val loadRoomMembersRequest by inject<LoadRoomMembersRequest>()
private val syncTokenStore by inject<SyncTokenStore>()
private val monarchy by inject<Monarchy>()
private val timelineHolder by inject<TimelineHolder>(parameters = { parametersOf(roomId) })
override fun liveTimeline(): LiveData<PagedList<EnrichedEvent>> {
return timelineHolder.liveTimeline()
override val roomSummary: LiveData<RoomSummary> by lazy {
val liveData = monarchy
.findAllMappedWithChanges(
{ realm -> RoomSummaryEntity.where(realm, roomId) },
{ from -> from.asDomain() })
Transformations.map(liveData) {
it.first()
}
}
override fun getNumberOfJoinedMembers(): Int {
val roomSummary = monarchy.fetchAllCopiedSync { realm -> RoomSummaryEntity.where(realm, roomId) }.firstOrNull()
return roomSummary?.joinedMembersCount ?: 0
override fun liveTimeline(): LiveData<PagedList<EnrichedEvent>> {
return timelineHolder.liveTimeline()
}
override fun loadRoomMembersIfNeeded(): Cancelable {
@ -47,7 +57,6 @@ data class DefaultRoom(
}
}
private fun areAllMembersLoaded(): Boolean {
return monarchy
.fetchAllCopiedSync { RoomEntity.where(it, roomId) }

View File

@ -0,0 +1,53 @@
package im.vector.matrix.android.internal.session.room
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.model.MyMembership
import im.vector.matrix.android.api.session.room.model.RoomAvatarContent
import im.vector.matrix.android.internal.auth.data.Credentials
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.last
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.room.members.RoomMembers
internal class RoomAvatarResolver(private val monarchy: Monarchy,
private val credentials: Credentials) {
/**
* Compute the room avatar url
*
* @return the room avatar url, can be a fallback to a room member avatar or null
*/
fun resolve(room: Room): String? {
var res: String? = null
monarchy.doWithRealm { realm ->
val roomName = EventEntity.where(realm, room.roomId, EventType.STATE_ROOM_AVATAR).last()?.asDomain()
res = roomName?.content<RoomAvatarContent>()?.avatarUrl
if (!res.isNullOrEmpty()) {
return@doWithRealm
}
val roomMembers = RoomMembers(realm, room.roomId)
val members = roomMembers.getLoaded()
if (room.myMembership == MyMembership.INVITED) {
if (members.size == 1) {
res = members.entries.first().value.avatarUrl
} else if (members.size > 1) {
val firstOtherMember = members.filterKeys { it != credentials.userId }.values.firstOrNull()
res = firstOtherMember?.avatarUrl
}
} else {
// detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat)
if (roomMembers.getNumberOfJoinedMembers() == 1 && members.isNotEmpty()) {
res = members.entries.first().value.avatarUrl
} else if (roomMembers.getNumberOfMembers() == 2 && members.size > 1) {
val firstOtherMember = members.filterKeys { it != credentials.userId }.values.firstOrNull()
res = firstOtherMember?.avatarUrl
}
}
}
return res
}
}

View File

@ -20,6 +20,7 @@ import java.util.concurrent.atomic.AtomicBoolean
internal class RoomSummaryUpdater(private val monarchy: Monarchy,
private val roomDisplayNameResolver: RoomDisplayNameResolver,
private val roomAvatarResolver: RoomAvatarResolver,
private val context: Context
) : Observer<Monarchy.ManagedChangeSet<RoomEntity>> {
@ -47,23 +48,23 @@ internal class RoomSummaryUpdater(private val monarchy: Monarchy,
val rooms = changeSet.realmResults.map { it.asDomain() }
val indexesToUpdate = changeSet.orderedCollectionChangeSet.changes + changeSet.orderedCollectionChangeSet.insertions
monarchy.writeAsync { realm ->
insertRoomList(realm, rooms, indexesToUpdate)
updateRoomList(realm, rooms, indexesToUpdate)
}
}
private fun insertRoomList(realm: Realm, rooms: List<Room>, indexes: IntArray) {
private fun updateRoomList(realm: Realm, rooms: List<Room>, indexes: IntArray) {
indexes.forEach {
val room = rooms[it]
try {
insertRoom(realm, room)
updateRoom(realm, room)
} catch (e: Exception) {
Timber.e(e, "An error occured when updating room summaries")
}
}
}
private fun insertRoom(realm: Realm, room: Room?) {
private fun updateRoom(realm: Realm, room: Room?) {
if (room == null) {
return
}
@ -74,6 +75,7 @@ internal class RoomSummaryUpdater(private val monarchy: Monarchy,
val lastTopicEvent = EventEntity.where(realm, room.roomId, EventType.STATE_ROOM_TOPIC).last()?.asDomain()
roomSummary.displayName = roomDisplayNameResolver.resolve(context, room).toString()
roomSummary.avatarUrl = roomAvatarResolver.resolve(room)
roomSummary.topic = lastTopicEvent?.content<RoomTopicContent>()?.topic
roomSummary.lastMessage = lastMessageEvent
}

View File

@ -8,9 +8,7 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.query.findAllRoomMembers
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI
@ -63,7 +61,7 @@ internal class LoadRoomMembersRequest(private val roomAPI: RoomAPI,
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: throw IllegalStateException("You shouldn't use this method without a room")
val roomMembers = EventEntity.findAllRoomMembers(realm, roomId)
val roomMembers = RoomMembers(realm, roomId).getLoaded()
val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) }
val chunk = stateEventsChunkHandler.handle(realm, roomId, eventsToInsert)

View File

@ -25,20 +25,19 @@ import im.vector.matrix.android.api.session.room.model.MyMembership
import im.vector.matrix.android.api.session.room.model.RoomAliasesContent
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
import im.vector.matrix.android.api.session.room.model.RoomNameContent
import im.vector.matrix.android.internal.auth.data.SessionParams
import im.vector.matrix.android.internal.auth.data.Credentials
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.query.findAllRoomMembers
import im.vector.matrix.android.internal.database.query.last
import im.vector.matrix.android.internal.database.query.where
/**
* This class computes room display name
*/
class RoomDisplayNameResolver(private val monarchy: Monarchy,
private val roomMemberDisplayNameResolver: RoomMemberDisplayNameResolver,
private val sessionParams: SessionParams
internal class RoomDisplayNameResolver(private val monarchy: Monarchy,
private val roomMemberDisplayNameResolver: RoomMemberDisplayNameResolver,
private val credentials: Credentials
) {
/**
@ -75,9 +74,9 @@ class RoomDisplayNameResolver(private val monarchy: Monarchy,
return@doWithRealm
}
val otherRoomMembers = EventEntity
.findAllRoomMembers(realm, room.roomId)
.filterKeys { it != sessionParams.credentials.userId }
val roomMembers = RoomMembers(realm, room.roomId)
val otherRoomMembers = roomMembers.getLoaded()
.filterKeys { it != credentials.userId }
if (room.myMembership == MyMembership.INVITED) {
//TODO handle invited
@ -117,9 +116,9 @@ class RoomDisplayNameResolver(private val monarchy: Monarchy,
else -> {
val member = memberIds[0]
name = context.resources.getQuantityString(R.plurals.room_displayname_three_and_more_members,
room.getNumberOfJoinedMembers() - 1,
roomMembers.getNumberOfJoinedMembers() - 1,
roomMemberDisplayNameResolver.resolve(member, otherRoomMembers),
room.getNumberOfJoinedMembers() - 1)
roomMembers.getNumberOfJoinedMembers() - 1)
}
}
}

View File

@ -0,0 +1,47 @@
package im.vector.matrix.android.internal.session.room.members
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.query.where
import io.realm.Realm
internal class RoomMembers(private val realm: Realm,
private val roomId: String
) {
private val roomSummary: RoomSummaryEntity? by lazy {
RoomSummaryEntity.where(realm, roomId).findFirst()
}
fun getLoaded(): Map<String, RoomMember> {
return EventEntity
.where(realm, roomId, EventType.STATE_ROOM_MEMBER)
.sort(EventEntityFields.ORIGIN_SERVER_TS)
.findAll()
.map { it.asDomain() }
.associateBy { it.stateKey!! }
.mapValues { it.value.content<RoomMember>()!! }
}
fun getNumberOfJoinedMembers(): Int {
return roomSummary?.joinedMembersCount
?: getLoaded().filterValues { it.membership == Membership.JOIN }.size
}
fun getNumberOfInvitedMembers(): Int {
return roomSummary?.invitedMembersCount
?: getLoaded().filterValues { it.membership == Membership.INVITE }.size
}
fun getNumberOfMembers(): Int {
return getNumberOfJoinedMembers() + getNumberOfInvitedMembers()
}
}

View File

@ -63,9 +63,6 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
roomEntity.membership = MyMembership.JOINED
if (roomSync.summary != null) {
handleRoomSummary(realm, roomId, roomSync.summary)
}
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
val chunkEntity = stateEventsChunkHandler.handle(realm, roomId, roomSync.state.events)
if (!roomEntity.chunks.contains(chunkEntity)) {
@ -78,6 +75,9 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
roomEntity.chunks.add(chunkEntity)
}
}
if (roomSync.summary != null) {
handleRoomSummary(realm, roomId, roomSync.summary)
}
return roomEntity
}