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

@ -12,7 +12,7 @@
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/Theme.Riot">
<activity android:name=".features.login.LoginActivity"> <activity android:name=".features.login.LoginActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View File

@ -4,6 +4,11 @@ import com.airbnb.mvrx.BaseMvRxFragment
abstract class RiotFragment : BaseMvRxFragment() { abstract class RiotFragment : BaseMvRxFragment() {
val riotActivity: RiotActivity by lazy {
activity as RiotActivity
}
override fun invalidate() { override fun invalidate() {
//no-ops by default //no-ops by default
} }

View File

@ -0,0 +1,9 @@
package im.vector.riotredesign.core.platform
import android.support.v7.widget.Toolbar
interface ToolbarConfigurable {
fun configure(toolbar: Toolbar)
}

View File

@ -3,10 +3,15 @@ package im.vector.riotredesign.features.home
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.support.v4.view.GravityCompat
import android.support.v7.app.ActionBarDrawerToggle
import android.support.v7.widget.Toolbar
import android.view.Gravity import android.view.Gravity
import android.view.MenuItem
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.replaceFragment import im.vector.riotredesign.core.extensions.replaceFragment
import im.vector.riotredesign.core.platform.RiotActivity import im.vector.riotredesign.core.platform.RiotActivity
import im.vector.riotredesign.core.platform.ToolbarConfigurable
import im.vector.riotredesign.features.home.detail.LoadingRoomDetailFragment import im.vector.riotredesign.features.home.detail.LoadingRoomDetailFragment
import im.vector.riotredesign.features.home.detail.RoomDetailFragment import im.vector.riotredesign.features.home.detail.RoomDetailFragment
import im.vector.riotredesign.features.home.list.RoomListFragment import im.vector.riotredesign.features.home.list.RoomListFragment
@ -14,12 +19,13 @@ import kotlinx.android.synthetic.main.activity_home.*
import org.koin.standalone.StandAloneContext.loadKoinModules import org.koin.standalone.StandAloneContext.loadKoinModules
class HomeActivity : RiotActivity(), HomeNavigator { class HomeActivity : RiotActivity(), HomeNavigator, ToolbarConfigurable {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
loadKoinModules(listOf(HomeModule(this)))
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home) setContentView(R.layout.activity_home)
loadKoinModules(listOf(HomeModule(this)))
if (savedInstanceState == null) { if (savedInstanceState == null) {
val roomListFragment = RoomListFragment.newInstance() val roomListFragment = RoomListFragment.newInstance()
val loadingDetail = LoadingRoomDetailFragment.newInstance() val loadingDetail = LoadingRoomDetailFragment.newInstance()
@ -28,6 +34,35 @@ class HomeActivity : RiotActivity(), HomeNavigator {
} }
} }
override fun configure(toolbar: Toolbar) {
setSupportActionBar(toolbar)
supportActionBar?.setHomeButtonEnabled(true)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val drawerToggle = ActionBarDrawerToggle(this, drawerLayout, toolbar, 0, 0)
drawerLayout.addDrawerListener(drawerToggle)
drawerToggle.syncState()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
// Android home
android.R.id.home -> {
drawerLayout.openDrawer(GravityCompat.START)
return true
}
}
return true
}
override fun onBackPressed() {
if (drawerLayout.isDrawerOpen(Gravity.LEFT)) {
drawerLayout.closeDrawer(Gravity.LEFT)
} else {
super.onBackPressed()
}
}
override fun openRoomDetail(roomId: String) { override fun openRoomDetail(roomId: String) {
val roomDetailFragment = RoomDetailFragment.newInstance(roomId) val roomDetailFragment = RoomDetailFragment.newInstance(roomId)
replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer) replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer)

View File

@ -11,8 +11,10 @@ import android.view.ViewGroup
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.session.events.model.EnrichedEvent 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.Room
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.platform.RiotFragment import im.vector.riotredesign.core.platform.RiotFragment
import im.vector.riotredesign.core.platform.ToolbarConfigurable
import im.vector.riotredesign.core.utils.FragmentArgumentDelegate import im.vector.riotredesign.core.utils.FragmentArgumentDelegate
import kotlinx.android.synthetic.main.fragment_room_detail.* import kotlinx.android.synthetic.main.fragment_room_detail.*
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
@ -41,14 +43,19 @@ class RoomDetailFragment : RiotFragment(), TimelineEventAdapter.Callback {
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
setupRecyclerView()
room = currentSession.getRoom(roomId)!! room = currentSession.getRoom(roomId)!!
setupRecyclerView()
setupToolbar()
room.loadRoomMembersIfNeeded() room.loadRoomMembersIfNeeded()
room.liveTimeline().observe(this, Observer { renderEvents(it) }) room.liveTimeline().observe(this, Observer { renderEvents(it) })
room.roomSummary.observe(this, Observer { renderRoomSummary(it) })
} }
private fun renderEvents(events: PagedList<EnrichedEvent>?) { private fun setupToolbar() {
timelineAdapter.submitList(events) val parentActivity = riotActivity
if (parentActivity is ToolbarConfigurable) {
parentActivity.configure(toolbar)
}
} }
private fun setupRecyclerView() { private fun setupRecyclerView() {
@ -67,7 +74,19 @@ class RoomDetailFragment : RiotFragment(), TimelineEventAdapter.Callback {
//recyclerView.setController(timelineEventController) //recyclerView.setController(timelineEventController)
} }
override fun onEventsListChanged(oldList: List<EnrichedEvent>?, newList: List<EnrichedEvent>?) { private fun renderRoomSummary(roomSummary: RoomSummary?) {
roomSummary?.let {
toolbar.title = it.displayName
}
}
private fun renderEvents(events: PagedList<EnrichedEvent>?) {
timelineAdapter.submitList(events)
}
override
fun onEventsListChanged(oldList: List<EnrichedEvent>?, newList: List<EnrichedEvent>?) {
if (oldList == null && newList != null) { if (oldList == null && newList != null) {
recyclerView.scrollToPosition(0) recyclerView.scrollToPosition(0)
} }

View File

@ -4,7 +4,11 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.airbnb.mvrx.* import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
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.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotredesign.R import im.vector.riotredesign.R
@ -35,16 +39,19 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback {
roomController = RoomSummaryController(this) roomController = RoomSummaryController(this)
stateView.contentView = epoxyRecyclerView stateView.contentView = epoxyRecyclerView
epoxyRecyclerView.setController(roomController) epoxyRecyclerView.setController(roomController)
viewModel.subscribe { renderState(it) } viewModel.subscribe { renderState(it) }
} }
private fun renderState(state: RoomListViewState) { private fun renderState(state: RoomListViewState) {
when (state.roomSummaries) { when (state.roomSummaries) {
is Incomplete -> renderLoading() is Incomplete -> renderLoading()
is Success -> renderSuccess(state.roomSummaries(), state.selectedRoom) is Success -> renderSuccess(state.roomSummaries(), state.selectedRoom)
is Fail -> renderFailure(state.roomSummaries.error) is Fail -> renderFailure(state.roomSummaries.error)
} }
if (state.showLastSelectedRoom && state.selectedRoom != null) { if (state.selectedRoom != null && state.showLastSelectedRoom) {
val position = state.roomSummaries()?.indexOf(state.selectedRoom) ?: 0
epoxyRecyclerView.scrollToPosition(position)
homeNavigator.openRoomDetail(state.selectedRoom.roomId) homeNavigator.openRoomDetail(state.selectedRoom.roomId)
} }
} }
@ -65,7 +72,7 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback {
private fun renderFailure(error: Throwable) { private fun renderFailure(error: Throwable) {
val message = when (error) { val message = when (error) {
is Failure.NetworkConnection -> "Pas de connexion internet" is Failure.NetworkConnection -> "Pas de connexion internet"
else -> "Une erreur est survenue" else -> "Une erreur est survenue"
} }
stateView.state = StateView.State.Error(message) stateView.state = StateView.State.Error(message)
} }

View File

@ -11,6 +11,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
<FrameLayout <FrameLayout
android:id="@+id/homeDrawerFragmentContainer" android:id="@+id/homeDrawerFragmentContainer"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -1,11 +1,27 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="0dp"
android:layout_height="?actionBarSize"
android:background="@color/dark"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
<com.airbnb.epoxy.EpoxyRecyclerView <com.airbnb.epoxy.EpoxyRecyclerView
android:id="@+id/recyclerView" android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="match_parent" /> android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar" />
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Base.V21.Theme.Riot" parent="Base.V1.Theme.Riot"></style>
<style name="Base.Theme.Riot" parent="Base.V21.Theme.Riot" />
</resources>

View File

@ -1,10 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="pale_grey">#f2f5f8</color> <color name="pale_grey">#f2f5f8</color>
<color name="dark">#2e3649</color> <color name="dark">#2e3649</color>
<color name="pale_teal">#7ac9a1</color> <color name="pale_teal">#7ac9a1</color>

View File

@ -1,11 +1,4 @@
<resources> <resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources> </resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Riot" parent="Base.Theme.Riot">
</style>
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Base.V1.Theme.Riot" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/dark</item>
<item name="colorPrimaryDark">@color/dark</item>
<item name="colorAccent">@color/pale_teal</item>
</style>
<style name="Base.Theme.Riot" parent="Base.V1.Theme.Riot" />
</resources>

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View File

@ -1,15 +1,18 @@
package im.vector.matrix.android.api.session.room 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.MyMembership
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
interface Room: TimelineHolder { interface Room : TimelineHolder {
val roomId: String val roomId: String
val myMembership: MyMembership val myMembership: MyMembership
fun getNumberOfJoinedMembers(): Int val roomSummary: LiveData<RoomSummary>
fun loadRoomMembersIfNeeded(): Cancelable 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( data class RoomSummary(
val roomId: String, val roomId: String,
var displayName: String = "", val displayName: String = "",
var topic: String = "" val topic: String = "",
val avatarUrl: String = ""
) )

View File

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

View File

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

View File

@ -1,8 +1,5 @@
package im.vector.matrix.android.internal.database.query 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.ChunkEntityFields
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.EventEntityFields 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 { fun RealmList<EventEntity>.fastContains(eventEntity: EventEntity): Boolean {
return this.where().equalTo(EventEntityFields.EVENT_ID, eventEntity.eventId).findFirst() != null 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.api.session.room.RoomService
import im.vector.matrix.android.internal.auth.data.SessionParams 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.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.RoomDisplayNameResolver
import im.vector.matrix.android.internal.session.room.members.RoomMemberDisplayNameResolver import im.vector.matrix.android.internal.session.room.members.RoomMemberDisplayNameResolver
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import org.koin.dsl.context.ModuleDefinition import org.koin.dsl.context.ModuleDefinition
import org.koin.dsl.module.Module import org.koin.dsl.module.Module
@ -37,11 +38,15 @@ class SessionModule(private val sessionParams: SessionParams) : Module {
} }
scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
RoomDisplayNameResolver(get(), get(), sessionParams) RoomDisplayNameResolver(get(), get(), sessionParams.credentials)
} }
scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
RoomSummaryUpdater(get(), get(), get()) RoomAvatarResolver(get(), sessionParams.credentials)
}
scope(DefaultSession.SCOPE) {
RoomSummaryUpdater(get(), get(), get(), get())
} }
scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {

View File

@ -1,6 +1,7 @@
package im.vector.matrix.android.internal.session.room package im.vector.matrix.android.internal.session.room
import android.arch.lifecycle.LiveData import android.arch.lifecycle.LiveData
import android.arch.lifecycle.Transformations
import android.arch.paging.PagedList import android.arch.paging.PagedList
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback 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.TimelineHolder
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.MyMembership 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.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.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.query.where import im.vector.matrix.android.internal.database.query.where
@ -24,18 +27,25 @@ data class DefaultRoom(
override val myMembership: MyMembership override val myMembership: MyMembership
) : Room, KoinComponent { ) : Room, KoinComponent {
private val loadRoomMembersRequest by inject<LoadRoomMembersRequest>() private val loadRoomMembersRequest by inject<LoadRoomMembersRequest>()
private val syncTokenStore by inject<SyncTokenStore>() private val syncTokenStore by inject<SyncTokenStore>()
private val monarchy by inject<Monarchy>() private val monarchy by inject<Monarchy>()
private val timelineHolder by inject<TimelineHolder>(parameters = { parametersOf(roomId) }) private val timelineHolder by inject<TimelineHolder>(parameters = { parametersOf(roomId) })
override fun liveTimeline(): LiveData<PagedList<EnrichedEvent>> { override val roomSummary: LiveData<RoomSummary> by lazy {
return timelineHolder.liveTimeline() val liveData = monarchy
.findAllMappedWithChanges(
{ realm -> RoomSummaryEntity.where(realm, roomId) },
{ from -> from.asDomain() })
Transformations.map(liveData) {
it.first()
}
} }
override fun getNumberOfJoinedMembers(): Int { override fun liveTimeline(): LiveData<PagedList<EnrichedEvent>> {
val roomSummary = monarchy.fetchAllCopiedSync { realm -> RoomSummaryEntity.where(realm, roomId) }.firstOrNull() return timelineHolder.liveTimeline()
return roomSummary?.joinedMembersCount ?: 0
} }
override fun loadRoomMembersIfNeeded(): Cancelable { override fun loadRoomMembersIfNeeded(): Cancelable {
@ -47,7 +57,6 @@ data class DefaultRoom(
} }
} }
private fun areAllMembersLoaded(): Boolean { private fun areAllMembersLoaded(): Boolean {
return monarchy return monarchy
.fetchAllCopiedSync { RoomEntity.where(it, roomId) } .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, internal class RoomSummaryUpdater(private val monarchy: Monarchy,
private val roomDisplayNameResolver: RoomDisplayNameResolver, private val roomDisplayNameResolver: RoomDisplayNameResolver,
private val roomAvatarResolver: RoomAvatarResolver,
private val context: Context private val context: Context
) : Observer<Monarchy.ManagedChangeSet<RoomEntity>> { ) : Observer<Monarchy.ManagedChangeSet<RoomEntity>> {
@ -47,23 +48,23 @@ internal class RoomSummaryUpdater(private val monarchy: Monarchy,
val rooms = changeSet.realmResults.map { it.asDomain() } val rooms = changeSet.realmResults.map { it.asDomain() }
val indexesToUpdate = changeSet.orderedCollectionChangeSet.changes + changeSet.orderedCollectionChangeSet.insertions val indexesToUpdate = changeSet.orderedCollectionChangeSet.changes + changeSet.orderedCollectionChangeSet.insertions
monarchy.writeAsync { realm -> 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 { indexes.forEach {
val room = rooms[it] val room = rooms[it]
try { try {
insertRoom(realm, room) updateRoom(realm, room)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "An error occured when updating room summaries") 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) { if (room == null) {
return 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() val lastTopicEvent = EventEntity.where(realm, room.roomId, EventType.STATE_ROOM_TOPIC).last()?.asDomain()
roomSummary.displayName = roomDisplayNameResolver.resolve(context, room).toString() roomSummary.displayName = roomDisplayNameResolver.resolve(context, room).toString()
roomSummary.avatarUrl = roomAvatarResolver.resolve(room)
roomSummary.topic = lastTopicEvent?.content<RoomTopicContent>()?.topic roomSummary.topic = lastTopicEvent?.content<RoomTopicContent>()?.topic
roomSummary.lastMessage = lastMessageEvent 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.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.util.Cancelable 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.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.database.query.where
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI 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() val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: throw IllegalStateException("You shouldn't use this method without a room") ?: 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 eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) }
val chunk = stateEventsChunkHandler.handle(realm, roomId, eventsToInsert) 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.RoomAliasesContent
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent 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.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.mapper.asDomain
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.RoomSummaryEntity 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.last
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
/** /**
* This class computes room display name * This class computes room display name
*/ */
class RoomDisplayNameResolver(private val monarchy: Monarchy, internal class RoomDisplayNameResolver(private val monarchy: Monarchy,
private val roomMemberDisplayNameResolver: RoomMemberDisplayNameResolver, private val roomMemberDisplayNameResolver: RoomMemberDisplayNameResolver,
private val sessionParams: SessionParams private val credentials: Credentials
) { ) {
/** /**
@ -75,9 +74,9 @@ class RoomDisplayNameResolver(private val monarchy: Monarchy,
return@doWithRealm return@doWithRealm
} }
val otherRoomMembers = EventEntity val roomMembers = RoomMembers(realm, room.roomId)
.findAllRoomMembers(realm, room.roomId) val otherRoomMembers = roomMembers.getLoaded()
.filterKeys { it != sessionParams.credentials.userId } .filterKeys { it != credentials.userId }
if (room.myMembership == MyMembership.INVITED) { if (room.myMembership == MyMembership.INVITED) {
//TODO handle invited //TODO handle invited
@ -117,9 +116,9 @@ class RoomDisplayNameResolver(private val monarchy: Monarchy,
else -> { else -> {
val member = memberIds[0] val member = memberIds[0]
name = context.resources.getQuantityString(R.plurals.room_displayname_three_and_more_members, name = context.resources.getQuantityString(R.plurals.room_displayname_three_and_more_members,
room.getNumberOfJoinedMembers() - 1, roomMembers.getNumberOfJoinedMembers() - 1,
roomMemberDisplayNameResolver.resolve(member, otherRoomMembers), 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 roomEntity.membership = MyMembership.JOINED
if (roomSync.summary != null) {
handleRoomSummary(realm, roomId, roomSync.summary)
}
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) { if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
val chunkEntity = stateEventsChunkHandler.handle(realm, roomId, roomSync.state.events) val chunkEntity = stateEventsChunkHandler.handle(realm, roomId, roomSync.state.events)
if (!roomEntity.chunks.contains(chunkEntity)) { if (!roomEntity.chunks.contains(chunkEntity)) {
@ -78,6 +75,9 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
roomEntity.chunks.add(chunkEntity) roomEntity.chunks.add(chunkEntity)
} }
} }
if (roomSync.summary != null) {
handleRoomSummary(realm, roomId, roomSync.summary)
}
return roomEntity return roomEntity
} }