From f747d268c937d0911a8944230b3efbc69288e970 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 23 Oct 2018 18:25:28 +0200 Subject: [PATCH] First attempt to handle room name + make app listen to room summaries instead of rooms --- .../main/java/im/vector/riotredesign/Riot.kt | 3 +- .../features/home/EventDiffUtilCallback.kt | 4 +- .../features/home/RoomController.kt | 20 --- .../features/home/RoomDetailFragment.kt | 3 +- .../riotredesign/features/home/RoomItem.kt | 2 +- .../features/home/RoomListFragment.kt | 14 +- .../features/home/RoomSummaryController.kt | 21 +++ .../features/home/TimelineEventAdapter.kt | 8 +- .../matrix/android/api/session/Session.kt | 5 +- .../api/session/events/model/EnrichedEvent.kt | 2 +- .../matrix/android/api/session/room/Room.kt | 5 + .../android/api/session/room/RoomService.kt | 4 + .../api/session/room/model/MyMembership.kt | 8 ++ .../session/room/model/RoomAliasesContent.kt | 9 ++ .../room/model/RoomCanonicalAliasContent.kt | 9 ++ .../api/session/room/model/RoomMember.kt | 2 +- .../api/session/room/model/RoomNameContent.kt | 2 +- .../api/session/room/model/RoomSummary.kt | 7 + .../internal/database/mapper/RoomMapper.kt | 21 +++ .../database/mapper/RoomSummaryMapper.kt | 20 +++ .../internal/database/model/RoomEntity.kt | 12 +- .../database/model/RoomSummaryEntity.kt | 12 +- .../database/query/EventEntityQueries.kt | 27 +++- .../database/query/RoomEntityQueries.kt | 3 +- .../query/RoomSummaryEntityQueries.kt | 13 ++ .../android/internal/di/NetworkModule.kt | 4 +- .../internal/session/DefaultSession.kt | 11 +- .../android/internal/session/SessionModule.kt | 14 +- .../interceptor/MessageEventInterceptor.kt | 9 +- .../internal/session/room/DefaultRoom.kt | 13 +- .../session/room/DefaultRoomService.kt | 17 ++- .../session/room/RoomDisplayNameResolver.kt | 130 ++++++++++++++++++ .../room/RoomMemberDisplayNameResolver.kt | 37 +++++ .../session/room/RoomSummaryObserver.kt | 63 --------- .../session/room/RoomSummaryUpdater.kt | 80 +++++++++++ .../room/timeline/TimelineBoundaryCallback.kt | 8 +- .../internal/session/sync/RoomSyncHandler.kt | 36 ++++- .../internal/session/sync/model/RoomSync.kt | 8 +- .../session/sync/model/RoomSyncSummary.kt | 32 +++++ .../src/main/res/values/strings.xml | 14 ++ 40 files changed, 568 insertions(+), 144 deletions(-) delete mode 100644 app/src/main/java/im/vector/riotredesign/features/home/RoomController.kt create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/RoomSummaryController.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/MyMembership.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomAliasesContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomCanonicalAliasContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMapper.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomSummaryEntityQueries.kt rename matrix-sdk-android/src/main/java/im/vector/matrix/android/{api => internal}/session/events/interceptor/MessageEventInterceptor.kt (73%) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomDisplayNameResolver.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomMemberDisplayNameResolver.kt delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryObserver.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/RoomSyncSummary.kt diff --git a/app/src/main/java/im/vector/riotredesign/Riot.kt b/app/src/main/java/im/vector/riotredesign/Riot.kt index 7a589a12..c83d0da7 100644 --- a/app/src/main/java/im/vector/riotredesign/Riot.kt +++ b/app/src/main/java/im/vector/riotredesign/Riot.kt @@ -3,6 +3,7 @@ package im.vector.riotredesign import android.app.Application import im.vector.matrix.android.BuildConfig import im.vector.riotredesign.core.di.AppModule +import org.koin.log.EmptyLogger import org.koin.standalone.StandAloneContext.startKoin import timber.log.Timber @@ -13,7 +14,7 @@ class Riot : Application() { if (BuildConfig.DEBUG) { Timber.plant(Timber.DebugTree()) } - startKoin(listOf(AppModule(this))) + startKoin(listOf(AppModule(this)), logger = EmptyLogger()) } } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/EventDiffUtilCallback.kt b/app/src/main/java/im/vector/riotredesign/features/home/EventDiffUtilCallback.kt index 1a54f399..6ac10192 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/EventDiffUtilCallback.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/EventDiffUtilCallback.kt @@ -5,11 +5,11 @@ import im.vector.matrix.android.api.session.events.model.EnrichedEvent class EventDiffUtilCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame(p0: EnrichedEvent, p1: EnrichedEvent): Boolean { - return p0.core.eventId == p1.core.eventId + return p0.root.eventId == p1.root.eventId } override fun areContentsTheSame(p0: EnrichedEvent, p1: EnrichedEvent): Boolean { - return p0.core == p1.core + return p0.root == p1.root && p0.getMetaEvents() .zip(p1.getMetaEvents()) { a, b -> a.eventId == b.eventId diff --git a/app/src/main/java/im/vector/riotredesign/features/home/RoomController.kt b/app/src/main/java/im/vector/riotredesign/features/home/RoomController.kt deleted file mode 100644 index 00a3035b..00000000 --- a/app/src/main/java/im/vector/riotredesign/features/home/RoomController.kt +++ /dev/null @@ -1,20 +0,0 @@ -package im.vector.riotredesign.features.home - -import com.airbnb.epoxy.TypedEpoxyController -import im.vector.matrix.android.api.session.room.Room - -class RoomController(private val callback: Callback? = null) : TypedEpoxyController>() { - - override fun buildModels(data: List?) { - data?.forEach { - RoomItem(it.roomId, listener = { callback?.onRoomSelected(it) }) - .id(it.roomId) - .addTo(this) - } - } - - interface Callback { - fun onRoomSelected(room: Room) - } - -} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/RoomDetailFragment.kt index 9912ec1a..892b741e 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/RoomDetailFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/RoomDetailFragment.kt @@ -55,9 +55,10 @@ class RoomDetailFragment : RiotFragment(), TimelineEventAdapter.Callback { layoutManager.stackFromEnd = true timelineAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - if (layoutManager.findFirstVisibleItemPosition() == 0) { + /*if (layoutManager.findFirstVisibleItemPosition() == 0) { layoutManager.scrollToPosition(0) } + */ } }) recyclerView.layoutManager = layoutManager diff --git a/app/src/main/java/im/vector/riotredesign/features/home/RoomItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/RoomItem.kt index c697c5af..ea744bd5 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/RoomItem.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/RoomItem.kt @@ -5,7 +5,7 @@ import im.vector.riotredesign.R import im.vector.riotredesign.core.epoxy.KotlinModel data class RoomItem( - val title: String, + val title: CharSequence, val listener: (() -> Unit)? = null ) : KotlinModel(R.layout.item_room) { diff --git a/app/src/main/java/im/vector/riotredesign/features/home/RoomListFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/RoomListFragment.kt index 4772660b..eb0b6a61 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/RoomListFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/RoomListFragment.kt @@ -2,19 +2,18 @@ package im.vector.riotredesign.features.home import android.arch.lifecycle.Observer import android.os.Bundle -import android.support.v7.widget.LinearLayoutManager import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import im.vector.matrix.android.api.Matrix -import im.vector.matrix.android.api.session.room.Room +import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotredesign.R import im.vector.riotredesign.core.extensions.addFragmentToBackstack import im.vector.riotredesign.core.platform.RiotFragment import kotlinx.android.synthetic.main.fragment_room_list.* import org.koin.android.ext.android.inject -class RoomListFragment : RiotFragment(), RoomController.Callback { +class RoomListFragment : RiotFragment(), RoomSummaryController.Callback { companion object { @@ -26,7 +25,7 @@ class RoomListFragment : RiotFragment(), RoomController.Callback { private val matrix by inject() private val currentSession = matrix.currentSession!! - private val roomController = RoomController(this) + private lateinit var roomController: RoomSummaryController override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_room_list, container, false) @@ -34,15 +33,16 @@ class RoomListFragment : RiotFragment(), RoomController.Callback { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) + roomController = RoomSummaryController(this) epoxyRecyclerView.setController(roomController) - currentSession.liveRooms().observe(this, Observer> { renderRooms(it) }) + currentSession.liveRoomSummaries().observe(this, Observer> { renderRooms(it) }) } - private fun renderRooms(rooms: List?) { + private fun renderRooms(rooms: List?) { roomController.setData(rooms) } - override fun onRoomSelected(room: Room) { + override fun onRoomSelected(room: RoomSummary) { val detailFragment = RoomDetailFragment.newInstance(room.roomId) addFragmentToBackstack(detailFragment, R.id.homeFragmentContainer) } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/RoomSummaryController.kt b/app/src/main/java/im/vector/riotredesign/features/home/RoomSummaryController.kt new file mode 100644 index 00000000..0af1999b --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/RoomSummaryController.kt @@ -0,0 +1,21 @@ +package im.vector.riotredesign.features.home + +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.matrix.android.api.session.room.model.RoomSummary + +class RoomSummaryController(private val callback: Callback? = null +) : TypedEpoxyController>() { + + override fun buildModels(data: List?) { + data?.forEach { + RoomItem(it.displayName, listener = { callback?.onRoomSelected(it) }) + .id(it.roomId) + .addTo(this) + } + } + + interface Callback { + fun onRoomSelected(room: RoomSummary) + } + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/TimelineEventAdapter.kt b/app/src/main/java/im/vector/riotredesign/features/home/TimelineEventAdapter.kt index 750256e3..ebf967cb 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/TimelineEventAdapter.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/TimelineEventAdapter.kt @@ -38,10 +38,10 @@ class TimelineEventAdapter(private val callback: Callback? = null) val titleView = view.findViewById(R.id.titleView)!! fun bind(event: EnrichedEvent?) { - if (event == null || event.core.type != EventType.MESSAGE) { + if (event == null) { titleView.text = null - } else { - val messageContent = event.core.content() + } else if (event.root.type == EventType.MESSAGE) { + val messageContent = event.root.content() val roomMember = event.getMetaEvents(EventType.STATE_ROOM_MEMBER).firstOrNull()?.content() if (messageContent == null || roomMember == null) { titleView.text = null @@ -49,6 +49,8 @@ class TimelineEventAdapter(private val callback: Callback? = null) val text = "${roomMember.displayName} : ${messageContent.body}" titleView.text = text } + } else { + titleView.text = event.root.toString() } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt index 46a5d50a..38210279 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt @@ -2,11 +2,12 @@ package im.vector.matrix.android.api.session import android.support.annotation.MainThread import im.vector.matrix.android.api.session.room.RoomService -import im.vector.matrix.android.internal.database.SessionRealmHolder -import im.vector.matrix.android.internal.session.sync.job.SyncThread +import im.vector.matrix.android.internal.auth.data.SessionParams interface Session : RoomService { + val sessionParams: SessionParams + @MainThread fun open() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EnrichedEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EnrichedEvent.kt index 53184219..ba7ff484 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EnrichedEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EnrichedEvent.kt @@ -1,6 +1,6 @@ package im.vector.matrix.android.api.session.events.model -data class EnrichedEvent(val core: Event) { +data class EnrichedEvent(val root: Event) { private val metaEventsByType = HashMap>() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt index 1ca2497f..a9055c4d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt @@ -3,11 +3,16 @@ package im.vector.matrix.android.api.session.room import android.arch.lifecycle.LiveData import android.arch.paging.PagedList import im.vector.matrix.android.api.session.events.model.EnrichedEvent +import im.vector.matrix.android.api.session.room.model.MyMembership interface Room { val roomId: String + val myMembership: MyMembership + fun liveTimeline(): LiveData> + fun getNumberOfJoinedMembers(): Int + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt index f2504a7c..8c722447 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt @@ -1,6 +1,7 @@ package im.vector.matrix.android.api.session.room import android.arch.lifecycle.LiveData +import im.vector.matrix.android.api.session.room.model.RoomSummary interface RoomService { @@ -10,4 +11,7 @@ interface RoomService { fun liveRooms(): LiveData> + fun liveRoomSummaries(): LiveData> + + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/MyMembership.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/MyMembership.kt new file mode 100644 index 00000000..85e21b52 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/MyMembership.kt @@ -0,0 +1,8 @@ +package im.vector.matrix.android.api.session.room.model + +enum class MyMembership { + JOINED, + LEFT, + INVITED, + NONE +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomAliasesContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomAliasesContent.kt new file mode 100644 index 00000000..ff36bfe8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomAliasesContent.kt @@ -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 RoomAliasesContent( + @Json(name = "aliases") val aliases: List = emptyList() +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomCanonicalAliasContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomCanonicalAliasContent.kt new file mode 100644 index 00000000..270a57f2 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomCanonicalAliasContent.kt @@ -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 RoomCanonicalAliasContent( + @Json(name = "alias") val canonicalAlias: String? = null +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt index 9d610d4f..0176d089 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt @@ -12,4 +12,4 @@ data class RoomMember( @Json(name = "is_direct") val isDirect: Boolean = false, @Json(name = "third_party_invite") val thirdPartyInvite: Invite? = null, @Json(name = "unsigned") val unsignedData: UnsignedData? = null -) +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomNameContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomNameContent.kt index 05593561..b9b53351 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomNameContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomNameContent.kt @@ -5,5 +5,5 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class RoomNameContent( - @Json(name = "name") val name: String + @Json(name = "name") val name: String? = null ) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt new file mode 100644 index 00000000..8898eade --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt @@ -0,0 +1,7 @@ +package im.vector.matrix.android.api.session.room.model + +data class RoomSummary( + val roomId: String, + var displayName: String = "", + var topic: String = "" +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMapper.kt new file mode 100644 index 00000000..254cdab5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMapper.kt @@ -0,0 +1,21 @@ +package im.vector.matrix.android.internal.database.mapper + +import im.vector.matrix.android.api.session.room.Room +import im.vector.matrix.android.internal.database.model.RoomEntity +import im.vector.matrix.android.internal.session.room.DefaultRoom + + +object RoomMapper { + + + internal fun map(roomEntity: RoomEntity): Room { + return DefaultRoom( + roomEntity.roomId, + roomEntity.membership + ) + } +} + +fun RoomEntity.asDomain(): Room { + return RoomMapper.map(this) +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt new file mode 100644 index 00000000..31fdca03 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt @@ -0,0 +1,20 @@ +package im.vector.matrix.android.internal.database.mapper + +import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.android.internal.database.model.RoomSummaryEntity + + +object RoomSummaryMapper { + + internal fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary { + return RoomSummary( + roomSummaryEntity.roomId, + roomSummaryEntity.displayName ?: "", + roomSummaryEntity.topic ?: "" + ) + } +} + +fun RoomSummaryEntity.asDomain(): RoomSummary { + return RoomSummaryMapper.map(this) +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomEntity.kt index 74c6debd..ddaec902 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomEntity.kt @@ -1,5 +1,6 @@ package im.vector.matrix.android.internal.database.model +import im.vector.matrix.android.api.session.room.model.MyMembership import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.Ignore @@ -10,19 +11,12 @@ open class RoomEntity(@PrimaryKey var roomId: String = "", var chunks: RealmList = RealmList() ) : RealmObject() { - private var membershipStr: String = Membership.NONE.name + private var membershipStr: String = MyMembership.NONE.name - @delegate:Ignore var membership: Membership by Delegates.observable(Membership.valueOf(membershipStr)) { _, _, newValue -> + @delegate:Ignore var membership: MyMembership by Delegates.observable(MyMembership.valueOf(membershipStr)) { _, _, newValue -> membershipStr = newValue.name } companion object; - - enum class Membership { - JOINED, - LEFT, - INVITED, - NONE - } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt index c855be2b..1822e310 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt @@ -1,5 +1,6 @@ package im.vector.matrix.android.internal.database.model +import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.PrimaryKey @@ -7,5 +8,12 @@ import io.realm.annotations.PrimaryKey open class RoomSummaryEntity(@PrimaryKey var roomId: String = "", var displayName: String? = "", var topic: String? = "", - var lastMessage: EventEntity? = null -) : RealmObject() \ No newline at end of file + var lastMessage: EventEntity? = null, + var heroes: RealmList = RealmList(), + var joinedMembersCount: Int? = 0, + var invitedMembersCount: Int? = 0 +) : RealmObject() { + + companion object + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt index 0752b386..8abba422 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt @@ -1,26 +1,43 @@ 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.EventEntity import io.realm.Realm import io.realm.RealmQuery import io.realm.Sort fun EventEntity.Companion.where(realm: Realm, roomId: String, type: String? = null): RealmQuery { - var query = realm.where(EventEntity::class.java) + val query = realm.where(EventEntity::class.java) .equalTo("chunk.room.roomId", roomId) if (type != null) { - query = query.equalTo("type", type) + query.equalTo("type", type) } return query } +fun EventEntity.Companion.stateEvents(realm: Realm, roomId: String): RealmQuery { + return realm.where(EventEntity::class.java) + .equalTo("chunk.room.roomId", roomId) + .isNotNull("stateKey") +} fun RealmQuery.last(from: Long? = null): EventEntity? { - var query = this if (from != null) { - query = query.lessThanOrEqualTo("originServerTs", from) + this.lessThanOrEqualTo("originServerTs", from) } - return query + return this .sort("originServerTs", Sort.DESCENDING) .findFirst() } + +fun EventEntity.Companion.findAllRoomMembers(realm: Realm, roomId: String): Map { + return EventEntity + .where(realm, roomId, EventType.STATE_ROOM_MEMBER) + .sort("originServerTs") + .findAll() + .map { it.asDomain() } + .associateBy { it.stateKey!! } + .mapValues { it.value.content()!! } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomEntityQueries.kt index 0dba5dc0..d53e47f7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomEntityQueries.kt @@ -1,5 +1,6 @@ package im.vector.matrix.android.internal.database.query +import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.internal.database.model.RoomEntity import io.realm.Realm import io.realm.RealmQuery @@ -8,7 +9,7 @@ fun RoomEntity.Companion.where(realm: Realm, roomId: String): RealmQuery(RoomEntity::class.java).equalTo("roomId", roomId) } -fun RoomEntity.Companion.where(realm: Realm, membership: RoomEntity.Membership? = null): RealmQuery { +fun RoomEntity.Companion.where(realm: Realm, membership: MyMembership? = null): RealmQuery { val query = realm.where(RoomEntity::class.java) if (membership != null) { query.equalTo("membership", membership.name) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomSummaryEntityQueries.kt new file mode 100644 index 00000000..aeccc794 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomSummaryEntityQueries.kt @@ -0,0 +1,13 @@ +package im.vector.matrix.android.internal.database.query + +import im.vector.matrix.android.internal.database.model.RoomSummaryEntity +import io.realm.Realm +import io.realm.RealmQuery + +fun RoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery { + val query = realm.where(RoomSummaryEntity::class.java) + if (roomId != null) { + query.equalTo("roomId", roomId) + } + return query +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt index 7300242d..04a97ea0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/NetworkModule.kt @@ -25,7 +25,9 @@ class NetworkModule : Module { single { val logger = HttpLoggingInterceptor.Logger { message -> Timber.v(message) } - HttpLoggingInterceptor(logger).apply { level = HttpLoggingInterceptor.Level.BASIC } + val interceptor = HttpLoggingInterceptor(logger) + interceptor.level = HttpLoggingInterceptor.Level.BASIC + interceptor } single { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index a318e102..3cc29b6d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -6,9 +6,10 @@ import android.support.annotation.MainThread import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.RoomService +import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.internal.auth.data.SessionParams import im.vector.matrix.android.internal.session.room.RoomModule -import im.vector.matrix.android.internal.session.room.RoomSummaryObserver +import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.session.sync.SyncModule import im.vector.matrix.android.internal.session.sync.job.SyncThread import org.koin.core.scope.Scope @@ -18,7 +19,7 @@ import org.koin.standalone.getKoin import org.koin.standalone.inject -class DefaultSession(private val sessionParams: SessionParams) : Session, KoinComponent, RoomService { +class DefaultSession(override val sessionParams: SessionParams) : Session, KoinComponent, RoomService { companion object { const val SCOPE: String = "session" @@ -26,7 +27,7 @@ class DefaultSession(private val sessionParams: SessionParams) : Session, KoinCo private lateinit var scope: Scope - private val roomSummaryObserver by inject() + private val roomSummaryObserver by inject() private val roomService by inject() private val syncThread by inject() private var isOpen = false @@ -70,6 +71,10 @@ class DefaultSession(private val sessionParams: SessionParams) : Session, KoinCo return roomService.liveRooms() } + override fun liveRoomSummaries(): LiveData> { + return roomService.liveRoomSummaries() + } + // Private methods ***************************************************************************** private fun checkIsMainThread() { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 4e5b1d92..360e6dde 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -7,7 +7,9 @@ import im.vector.matrix.android.internal.legacy.MXDataHandler import im.vector.matrix.android.internal.legacy.MXSession import im.vector.matrix.android.internal.legacy.data.store.MXFileStore import im.vector.matrix.android.internal.session.room.DefaultRoomService -import im.vector.matrix.android.internal.session.room.RoomSummaryObserver +import im.vector.matrix.android.internal.session.room.RoomDisplayNameResolver +import im.vector.matrix.android.internal.session.room.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 @@ -34,7 +36,15 @@ class SessionModule(private val sessionParams: SessionParams) : Module { } scope(DefaultSession.SCOPE) { - RoomSummaryObserver(get()) + RoomMemberDisplayNameResolver() + } + + scope(DefaultSession.SCOPE) { + RoomDisplayNameResolver(get(), get(), sessionParams) + } + + scope(DefaultSession.SCOPE) { + RoomSummaryUpdater(get(), get(), get()) } scope(DefaultSession.SCOPE) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/interceptor/MessageEventInterceptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/interceptor/MessageEventInterceptor.kt similarity index 73% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/interceptor/MessageEventInterceptor.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/interceptor/MessageEventInterceptor.kt index 86ae000c..91e0097c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/interceptor/MessageEventInterceptor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/interceptor/MessageEventInterceptor.kt @@ -1,6 +1,7 @@ -package im.vector.matrix.android.api.session.events.interceptor +package im.vector.matrix.android.internal.session.events.interceptor import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.events.interceptor.EnrichedEventInterceptor import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.internal.database.mapper.asDomain @@ -11,15 +12,15 @@ import im.vector.matrix.android.internal.database.query.where class MessageEventInterceptor(val monarchy: Monarchy) : EnrichedEventInterceptor { override fun canEnrich(event: EnrichedEvent): Boolean { - return event.core.type == EventType.MESSAGE + return event.root.type == EventType.MESSAGE } override fun enrich(roomId: String, event: EnrichedEvent) { monarchy.doWithRealm { realm -> val roomMember = EventEntity .where(realm, roomId, EventType.STATE_ROOM_MEMBER) - .equalTo("stateKey", event.core.sender) - .last(from = event.core.originServerTs) + .equalTo("stateKey", event.root.sender) + .last(from = event.root.originServerTs) ?.asDomain() event.enrichWith(roomMember) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt index bbb9921b..fb63b721 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt @@ -5,12 +5,14 @@ import android.arch.paging.LivePagedListBuilder import android.arch.paging.PagedList import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.events.interceptor.EnrichedEventInterceptor -import im.vector.matrix.android.api.session.events.interceptor.MessageEventInterceptor import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.room.Room +import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.ChunkEntity +import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.session.events.interceptor.MessageEventInterceptor import im.vector.matrix.android.internal.session.room.timeline.PaginationRequest import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback import io.realm.Sort @@ -19,7 +21,8 @@ import org.koin.standalone.inject import java.util.concurrent.Executors data class DefaultRoom( - override val roomId: String + override val roomId: String, + override val myMembership: MyMembership ) : Room, KoinComponent { private val paginationRequest by inject() @@ -63,4 +66,10 @@ data class DefaultRoom( return monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder) } + override fun getNumberOfJoinedMembers(): Int { + val roomSummary = monarchy.fetchAllCopiedSync { realm -> RoomSummaryEntity.where(realm, roomId) }.firstOrNull() + return roomSummary?.joinedMembersCount ?: 0 + } + + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt index ab9fca1c..c1058f2a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt @@ -4,7 +4,10 @@ import android.arch.lifecycle.LiveData import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.RoomService +import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.android.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 class DefaultRoomService(private val monarchy: Monarchy) : RoomService { @@ -12,7 +15,7 @@ class DefaultRoomService(private val monarchy: Monarchy) : RoomService { override fun getAllRooms(): List { var rooms: List = emptyList() monarchy.doWithRealm { realm -> - rooms = RoomEntity.where(realm).findAll().map { DefaultRoom(it.roomId) } + rooms = RoomEntity.where(realm).findAll().map { it.asDomain() } } return rooms } @@ -20,7 +23,7 @@ class DefaultRoomService(private val monarchy: Monarchy) : RoomService { override fun getRoom(roomId: String): Room? { var room: Room? = null monarchy.doWithRealm { realm -> - room = RoomEntity.where(realm, roomId).findFirst()?.let { DefaultRoom(it.roomId) } + room = RoomEntity.where(realm, roomId).findFirst()?.let { it.asDomain() } } return room } @@ -28,8 +31,16 @@ class DefaultRoomService(private val monarchy: Monarchy) : RoomService { override fun liveRooms(): LiveData> { return monarchy.findAllMappedWithChanges( { realm -> RoomEntity.where(realm) }, - { DefaultRoom(it.roomId) } + { it.asDomain() } ) } + override fun liveRoomSummaries(): LiveData> { + return monarchy.findAllMappedWithChanges( + { realm -> RoomSummaryEntity.where(realm) }, + { it.asDomain() } + ) + } + + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomDisplayNameResolver.kt new file mode 100644 index 00000000..11ec0f1a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomDisplayNameResolver.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2018 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.matrix.android.internal.session.room + +import android.content.Context +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.R +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.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.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 +) { + + /** + * Compute the room display name + * + * @param context + * @param room: the room to resolve the name of. + * @return the room display name + */ + fun resolve(context: Context, room: Room): CharSequence { + // this algorithm is the one defined in + // https://github.com/matrix-org/matrix-js-sdk/blob/develop/lib/models/room.js#L617 + // calculateRoomName(room, userId) + + // For Lazy Loaded room, see algorithm here: + // https://docs.google.com/document/d/11i14UI1cUz-OJ0knD5BFu7fmT6Fo327zvMYqfSAR7xs/edit#heading=h.qif6pkqyjgzn + var name: CharSequence? = null + monarchy.doWithRealm { realm -> + val roomName = EventEntity.where(realm, room.roomId, EventType.STATE_ROOM_NAME).last()?.asDomain() + name = roomName?.content()?.name + if (!name.isNullOrEmpty()) { + return@doWithRealm + } + + val canonicalAlias = EventEntity.where(realm, room.roomId, EventType.STATE_CANONICAL_ALIAS).last()?.asDomain() + name = canonicalAlias?.content()?.canonicalAlias + if (!name.isNullOrEmpty()) { + return@doWithRealm + } + + val aliases = EventEntity.where(realm, room.roomId, EventType.STATE_ROOM_ALIASES).last()?.asDomain() + name = aliases?.content()?.aliases?.firstOrNull() + if (!name.isNullOrEmpty()) { + return@doWithRealm + } + + val otherRoomMembers = EventEntity + .findAllRoomMembers(realm, room.roomId) + .filterKeys { it != sessionParams.credentials.userId } + + if (room.myMembership == MyMembership.INVITED) { + //TODO handle invited + /* + if (currentUser != null + && !othersActiveMembers.isEmpty() + && !TextUtils.isEmpty(currentUser!!.mSender)) { + // extract who invited us to the room + name = context.getString(R.string.room_displayname_invite_from, roomState.resolve(currentUser!!.mSender)) + } else { + name = context.getString(R.string.room_displayname_room_invite) + } + */ + name = context.getString(R.string.room_displayname_room_invite) + } else { + + val roomSummary = RoomSummaryEntity.where(realm, room.roomId).findFirst() + val memberIds = if (roomSummary?.heroes?.isNotEmpty() == true) { + roomSummary.heroes + } else { + otherRoomMembers.keys.toList() + } + + val nbOfOtherMembers = memberIds.size + + when (nbOfOtherMembers) { + 0 -> name = context.getString(R.string.room_displayname_empty_room) + 1 -> name = roomMemberDisplayNameResolver.resolve(memberIds[0], otherRoomMembers) + 2 -> { + val member1 = memberIds[0] + val member2 = memberIds[1] + name = context.getString(R.string.room_displayname_two_members, + roomMemberDisplayNameResolver.resolve(member1, otherRoomMembers), + roomMemberDisplayNameResolver.resolve(member2, otherRoomMembers) + ) + } + else -> { + val member = memberIds[0] + name = context.resources.getQuantityString(R.plurals.room_displayname_three_and_more_members, + room.getNumberOfJoinedMembers() - 1, + roomMemberDisplayNameResolver.resolve(member, otherRoomMembers), + room.getNumberOfJoinedMembers() - 1) + } + } + } + return@doWithRealm + } + return name ?: room.roomId + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomMemberDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomMemberDisplayNameResolver.kt new file mode 100644 index 00000000..f16490dd --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomMemberDisplayNameResolver.kt @@ -0,0 +1,37 @@ +package im.vector.matrix.android.internal.session.room + +import im.vector.matrix.android.api.session.room.model.RoomMember + +class RoomMemberDisplayNameResolver { + + fun resolve(userId: String, members: Map): String? { + var displayName: String? = null + val currentMember = members[userId] + // Get the user display name from the member list of the room + // Do not consider null display name + if (currentMember != null && !currentMember.displayName.isNullOrEmpty()) { + val hasNameCollision = members + .filterValues { it != currentMember && it.displayName == currentMember.displayName } + .isNotEmpty() + displayName = if (hasNameCollision) { + "${currentMember.displayName} ( $userId )" + } else { + currentMember.displayName + } + } + // TODO handle invited users + /*else if (null != member && TextUtils.equals(member!!.membership, RoomMember.MEMBERSHIP_INVITE)) { + val user = (mDataHandler as MXDataHandler).getUser(userId) + if (null != user) { + displayName = user!!.displayname + } + } + */ + if (displayName == null) { + // By default, use the user ID + displayName = userId + } + return displayName + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryObserver.kt deleted file mode 100644 index c7195962..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryObserver.kt +++ /dev/null @@ -1,63 +0,0 @@ -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.model.RoomNameContent -import im.vector.matrix.android.api.session.room.model.RoomTopicContent -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.RoomEntity -import im.vector.matrix.android.internal.database.model.RoomSummaryEntity -import im.vector.matrix.android.internal.database.query.last -import im.vector.matrix.android.internal.database.query.where -import io.realm.RealmResults -import java.util.concurrent.atomic.AtomicBoolean - -internal class RoomSummaryObserver(private val monarchy: Monarchy) { - - private lateinit var roomResults: RealmResults - private var isStarted = AtomicBoolean(false) - - fun start() { - if (isStarted.compareAndSet(false, true)) { - monarchy.doWithRealm { - roomResults = RoomEntity.where(it).findAllAsync() - roomResults.addChangeListener { rooms, changeSet -> - manageRoomResults(rooms, changeSet.changes) - manageRoomResults(rooms, changeSet.insertions) - } - } - } - } - - fun dispose() { - if (isStarted.compareAndSet(true, false)) { - roomResults.removeAllChangeListeners() - } - } - - // PRIVATE - - private fun manageRoomResults(rooms: RealmResults, indexes: IntArray) { - indexes.forEach { - val room = rooms[it] - if (room != null) { - manageRoom(room.roomId) - } - } - } - - private fun manageRoom(roomId: String) { - monarchy.writeAsync { realm -> - val lastNameEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_NAME).last()?.asDomain() - val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).last()?.asDomain() - val lastMessageEvent = EventEntity.where(realm, roomId, EventType.MESSAGE).last() - - val roomSummary = realm.copyToRealmOrUpdate(RoomSummaryEntity(roomId)) - roomSummary.displayName = lastNameEvent?.content()?.name - roomSummary.topic = lastTopicEvent?.content()?.topic - roomSummary.lastMessage = lastMessageEvent - } - } - -} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt new file mode 100644 index 00000000..6fb67643 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -0,0 +1,80 @@ +package im.vector.matrix.android.internal.session.room + +import android.arch.lifecycle.Observer +import android.content.Context +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.RoomTopicContent +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.RoomEntity +import im.vector.matrix.android.internal.database.model.RoomSummaryEntity +import im.vector.matrix.android.internal.database.query.last +import im.vector.matrix.android.internal.database.query.where +import io.realm.RealmResults +import timber.log.Timber +import java.util.concurrent.atomic.AtomicBoolean + +internal class RoomSummaryUpdater(private val monarchy: Monarchy, + private val roomDisplayNameResolver: RoomDisplayNameResolver, + private val context: Context +) : Observer> { + + private var isStarted = AtomicBoolean(false) + private val liveResults = monarchy.findAllManagedWithChanges { RoomEntity.where(it) } + + fun start() { + if (isStarted.compareAndSet(false, true)) { + liveResults.observeForever(this) + } + } + + fun dispose() { + if (isStarted.compareAndSet(true, false)) { + liveResults.removeObserver(this) + } + } + + // PRIVATE + + override fun onChanged(changeSet: Monarchy.ManagedChangeSet?) { + if (changeSet == null) { + return + } + manageRoomResults(changeSet.realmResults, changeSet.orderedCollectionChangeSet.changes) + manageRoomResults(changeSet.realmResults, changeSet.orderedCollectionChangeSet.insertions) + } + + + private fun manageRoomResults(rooms: RealmResults, indexes: IntArray) { + indexes.forEach { + val room = rooms[it]?.asDomain() + try { + manageRoom(room) + } catch (e: Exception) { + Timber.e(e, "An error occured when updating room summaries") + } + } + } + + private fun manageRoom(room: Room?) { + if (room == null) { + return + } + + + monarchy.writeAsync { realm -> + val roomSummary = RoomSummaryEntity.where(realm, room.roomId).findFirst() + ?: RoomSummaryEntity(room.roomId) + + val lastMessageEvent = EventEntity.where(realm, room.roomId, EventType.MESSAGE).last() + val lastTopicEvent = EventEntity.where(realm, room.roomId, EventType.STATE_ROOM_TOPIC).last()?.asDomain() + + roomSummary.displayName = roomDisplayNameResolver.resolve(context, room).toString() + roomSummary.topic = lastTopicEvent?.content()?.topic + roomSummary.lastMessage = lastMessageEvent + } + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt index eab31a36..92e7d5af 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt @@ -28,10 +28,10 @@ class TimelineBoundaryCallback(private val paginationRequest: PaginationRequest, override fun onItemAtEndLoaded(itemAtEnd: EnrichedEvent) { helper.runIfNotRunning(PagingRequestHelper.RequestType.AFTER) { monarchy.doWithRealm { realm -> - if (itemAtEnd.core.eventId == null) { + if (itemAtEnd.root.eventId == null) { return@doWithRealm } - val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(itemAtEnd.core.eventId)).firstOrNull() + val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(itemAtEnd.root.eventId)).firstOrNull() paginationRequest.execute(roomId, chunkEntity?.prevToken, PaginationDirection.BACKWARDS, callback = createCallback(it)) } } @@ -40,10 +40,10 @@ class TimelineBoundaryCallback(private val paginationRequest: PaginationRequest, override fun onItemAtFrontLoaded(itemAtFront: EnrichedEvent) { helper.runIfNotRunning(PagingRequestHelper.RequestType.BEFORE) { monarchy.doWithRealm { realm -> - if (itemAtFront.core.eventId == null) { + if (itemAtFront.root.eventId == null) { return@doWithRealm } - val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(itemAtFront.core.eventId)).firstOrNull() + val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(itemAtFront.root.eventId)).firstOrNull() paginationRequest.execute(roomId, chunkEntity?.nextToken, PaginationDirection.FORWARDS, callback = createCallback(it)) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt index de8049bc..ca9a03b7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt @@ -2,14 +2,17 @@ package im.vector.matrix.android.internal.session.sync import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.internal.database.mapper.asEntity import im.vector.matrix.android.internal.database.model.ChunkEntity 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.findAllIncludingEvents import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync import im.vector.matrix.android.internal.session.sync.model.RoomSync +import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary import io.realm.Realm @@ -41,11 +44,16 @@ class RoomSyncHandler(private val monarchy: Monarchy) { val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: RoomEntity(roomId) - if (roomEntity.membership == RoomEntity.Membership.INVITED) { + if (roomEntity.membership == MyMembership.INVITED) { roomEntity.chunks.deleteAllFromRealm() } - roomEntity.membership = RoomEntity.Membership.JOINED + + 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)) { @@ -67,7 +75,7 @@ class RoomSyncHandler(private val monarchy: Monarchy) { InvitedRoomSync): RoomEntity { val roomEntity = RoomEntity() roomEntity.roomId = roomId - roomEntity.membership = RoomEntity.Membership.INVITED + roomEntity.membership = MyMembership.INVITED if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) { val chunkEntity = handleListOfEvent(realm, roomId, roomSync.inviteState.events) if (!roomEntity.chunks.contains(chunkEntity)) { @@ -82,10 +90,30 @@ class RoomSyncHandler(private val monarchy: Monarchy) { roomSync: RoomSync): RoomEntity { return RoomEntity().apply { this.roomId = roomId - this.membership = RoomEntity.Membership.LEFT + this.membership = MyMembership.LEFT } } + private fun handleRoomSummary(realm: Realm, + roomId: String, + roomSummary: RoomSyncSummary) { + + val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() + ?: RoomSummaryEntity(roomId) + + if (roomSummary.heroes.isNotEmpty()) { + roomSummaryEntity.heroes.clear() + roomSummaryEntity.heroes.addAll(roomSummary.heroes) + } + if (roomSummary.invitedMembersCount != null) { + roomSummaryEntity.invitedMembersCount = roomSummary.invitedMembersCount + } + if (roomSummary.joinedMembersCount != null) { + roomSummaryEntity.joinedMembersCount = roomSummary.joinedMembersCount + } + realm.insertOrUpdate(roomSummaryEntity) + } + private fun handleListOfEvent(realm: Realm, roomId: String, eventList: List, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/RoomSync.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/RoomSync.kt index cd3a532c..e2f1b64a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/RoomSync.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/RoomSync.kt @@ -44,6 +44,12 @@ data class RoomSync( /** * The notification counts for the room. */ - @Json(name = "unread_notifications") val unreadNotifications: RoomSyncUnreadNotifications? = null + @Json(name = "unread_notifications") val unreadNotifications: RoomSyncUnreadNotifications? = null, + + /** + * The room summary + */ + @Json(name = "summary") val summary: RoomSyncSummary? = null + ) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/RoomSyncSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/RoomSyncSummary.kt new file mode 100644 index 00000000..a3fc13ed --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/RoomSyncSummary.kt @@ -0,0 +1,32 @@ +package im.vector.matrix.android.internal.session.sync.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + + +@JsonClass(generateAdapter = true) +data class RoomSyncSummary( + + /** + * Present only if the room has no m.room.name or m.room.canonical_alias. + * + * + * Lists the mxids of the first 5 members in the room who are currently joined or invited (ordered by stream ordering as seen on the server, + * to avoid it jumping around if/when topological order changes). As the heroes’ membership status changes, the list changes appropriately + * (sending the whole new list in the next /sync response). This list always excludes the current logged in user. If there are no joined or + * invited users, it lists the parted and banned ones instead. Servers can choose to send more or less than 5 members if they must, but 5 + * seems like a good enough number for most naming purposes. Clients should use all the provided members to name the room, but may truncate + * the list if helpful for UX + */ + @Json(name = "m.heroes") val heroes: List = emptyList(), + + /** + * The number of m.room.members in state 'joined' (including the syncing user) (can be null) + */ + @Json(name = "m.joined_member_count") val joinedMembersCount: Int? = null, + + /** + * The number of m.room.members in state 'invited' (can be null) + */ + @Json(name = "m.invited_member_count") val invitedMembersCount: Int? = null +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/res/values/strings.xml b/matrix-sdk-android/src/main/res/values/strings.xml index 28e684ed..1adced17 100644 --- a/matrix-sdk-android/src/main/res/values/strings.xml +++ b/matrix-sdk-android/src/main/res/values/strings.xml @@ -84,4 +84,18 @@ sent an audio file. sent a file. + + Invite from %s + Room Invite + + + %1$s and %2$s + + + %1$s and 1 other + %1$s and %2$d others + + + Empty room +