Merge pull request #679 from vector-im/feature/perf_again

Feature/perf again
This commit is contained in:
ganfra 2019-11-13 19:43:07 +01:00 committed by GitHub
commit 6ce241163e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
68 changed files with 540 additions and 478 deletions

View File

@ -67,5 +67,5 @@ interface Authenticator {
/**
* Create a session after a SSO successful login
*/
fun createSessionFromSso(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session
fun createSessionFromSso(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<Session>): Cancelable
}

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.auth
import android.util.Patterns
import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.auth.data.Credentials
@ -39,10 +40,9 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import javax.inject.Inject
import javax.inject.Provider
internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
private val okHttpClient: Provider<OkHttpClient>,
private val okHttpClient: Lazy<OkHttpClient>,
private val retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionParamsStore: SessionParamsStore,
@ -112,14 +112,27 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
sessionManager.getOrCreateSession(sessionParams)
}
override fun createSessionFromSso(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session {
override fun createSessionFromSso(credentials: Credentials,
homeServerConnectionConfig: HomeServerConnectionConfig,
callback: MatrixCallback<Session>): Cancelable {
val job = GlobalScope.launch(coroutineDispatchers.main) {
val sessionOrFailure = runCatching {
createSessionFromSso(credentials, homeServerConnectionConfig)
}
sessionOrFailure.foldToCallback(callback)
}
return CancelableCoroutine(job)
}
private suspend fun createSessionFromSso(credentials: Credentials,
homeServerConnectionConfig: HomeServerConnectionConfig): Session = withContext(coroutineDispatchers.computation) {
val sessionParams = SessionParams(credentials, homeServerConnectionConfig)
sessionParamsStore.save(sessionParams)
return sessionManager.getOrCreateSession(sessionParams)
sessionManager.getOrCreateSession(sessionParams)
}
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
val retrofit = retrofitFactory.create(okHttpClient.get(), homeServerConnectionConfig.homeServerUri.toString())
val retrofit = retrofitFactory.create(okHttpClient, homeServerConnectionConfig.homeServerUri.toString())
return retrofit.create(AuthAPI::class.java)
}
}

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.auth
import arrow.core.Try
import im.vector.matrix.android.api.auth.data.SessionParams
internal interface SessionParamsStore {
@ -27,9 +26,9 @@ internal interface SessionParamsStore {
fun getAll(): List<SessionParams>
fun save(sessionParams: SessionParams): Try<Unit>
suspend fun save(sessionParams: SessionParams)
fun delete(userId: String): Try<Unit>
suspend fun delete(userId: String)
fun deleteAll(): Try<Unit>
suspend fun deleteAll()
}

View File

@ -16,9 +16,9 @@
package im.vector.matrix.android.internal.auth.db
import arrow.core.Try
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.database.awaitTransaction
import im.vector.matrix.android.internal.di.AuthDatabase
import io.realm.Realm
import io.realm.RealmConfiguration
@ -62,41 +62,29 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
return sessionParams
}
override fun save(sessionParams: SessionParams): Try<Unit> {
return Try {
override suspend fun save(sessionParams: SessionParams) {
awaitTransaction(realmConfiguration) {
val entity = mapper.map(sessionParams)
if (entity != null) {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {
it.insert(entity)
}
realm.close()
it.insert(entity)
}
}
}
override fun delete(userId: String): Try<Unit> {
return Try {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {
it.where(SessionParamsEntity::class.java)
.equalTo(SessionParamsEntityFields.USER_ID, userId)
.findAll()
.deleteAllFromRealm()
}
realm.close()
override suspend fun delete(userId: String) {
awaitTransaction(realmConfiguration) {
it.where(SessionParamsEntity::class.java)
.equalTo(SessionParamsEntityFields.USER_ID, userId)
.findAll()
.deleteAllFromRealm()
}
}
override fun deleteAll(): Try<Unit> {
return Try {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {
it.where(SessionParamsEntity::class.java)
.findAll()
.deleteAllFromRealm()
}
realm.close()
override suspend fun deleteAll() {
awaitTransaction(realmConfiguration) {
it.where(SessionParamsEntity::class.java)
.findAll()
.deleteAllFromRealm()
}
}
}

View File

@ -20,14 +20,19 @@ import io.realm.RealmConfiguration
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import timber.log.Timber
suspend fun awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> Unit) = withContext(Dispatchers.IO) {
suspend fun awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> Unit) = withContext(Dispatchers.Default) {
Realm.getInstance(config).use { bgRealm ->
bgRealm.beginTransaction()
try {
val start = System.currentTimeMillis()
transaction(bgRealm)
if (isActive) {
bgRealm.commitTransaction()
val end = System.currentTimeMillis()
val time = end - start
Timber.v("Execute transaction in $time millis")
}
} finally {
if (bgRealm.isInTransaction) {

View File

@ -36,7 +36,7 @@ internal class RoomSummaryMapper @Inject constructor(
}
val latestEvent = roomSummaryEntity.latestPreviewableEvent?.let {
timelineEventMapper.map(it)
timelineEventMapper.map(it, buildReadReceipts = false)
}
if (latestEvent?.root?.isEncrypted() == true && latestEvent.root.mxDecryptionResult == null) {
// TODO use a global event decryptor? attache to session and that listen to new sessionId?

View File

@ -16,27 +16,26 @@
package im.vector.matrix.android.internal.database.query
import im.vector.matrix.android.internal.database.awaitTransaction
import im.vector.matrix.android.internal.database.model.FilterEntity
import im.vector.matrix.android.internal.session.filter.FilterFactory
import io.realm.Realm
import io.realm.kotlin.createObject
import io.realm.kotlin.where
/**
* Get the current filter, create one if it does not exist
*/
internal fun FilterEntity.Companion.getFilter(realm: Realm): FilterEntity {
internal suspend fun FilterEntity.Companion.getFilter(realm: Realm): FilterEntity {
var filter = realm.where<FilterEntity>().findFirst()
if (filter == null) {
realm.executeTransaction {
realm.createObject<FilterEntity>().apply {
filterBodyJson = FilterFactory.createDefaultFilterBody().toJSONString()
roomEventFilterJson = FilterFactory.createDefaultRoomFilter().toJSONString()
filterId = ""
}
filter = FilterEntity().apply {
filterBodyJson = FilterFactory.createDefaultFilterBody().toJSONString()
roomEventFilterJson = FilterFactory.createDefaultRoomFilter().toJSONString()
filterId = ""
}
awaitTransaction(realm.configuration) {
it.insert(filter)
}
filter = realm.where<FilterEntity>().findFirst()!!
}
return filter
}

View File

@ -24,8 +24,7 @@ import io.realm.kotlin.where
internal fun ReadReceiptEntity.Companion.where(realm: Realm, roomId: String, userId: String): RealmQuery<ReadReceiptEntity> {
return realm.where<ReadReceiptEntity>()
.equalTo(ReadReceiptEntityFields.ROOM_ID, roomId)
.equalTo(ReadReceiptEntityFields.USER_ID, userId)
.equalTo(ReadReceiptEntityFields.PRIMARY_KEY, buildPrimaryKey(roomId, userId))
}
internal fun ReadReceiptEntity.Companion.whereUserId(realm: Realm, userId: String): RealmQuery<ReadReceiptEntity> {
@ -45,8 +44,10 @@ internal fun ReadReceiptEntity.Companion.createUnmanaged(roomId: String, eventId
internal fun ReadReceiptEntity.Companion.getOrCreate(realm: Realm, roomId: String, userId: String): ReadReceiptEntity {
return ReadReceiptEntity.where(realm, roomId, userId).findFirst()
?: realm.createObject(ReadReceiptEntity::class.java, "${roomId}_$userId").apply {
?: realm.createObject(ReadReceiptEntity::class.java, buildPrimaryKey(roomId, userId)).apply {
this.roomId = roomId
this.userId = userId
}
}
private fun buildPrimaryKey(roomId: String, userId: String) = "${roomId}_$userId"

View File

@ -111,7 +111,7 @@ internal fun RealmQuery<TimelineEventEntity>.prev(since: Int? = null, strict: Bo
internal fun RealmList<TimelineEventEntity>.find(eventId: String): TimelineEventEntity? {
return this.where()
.equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, eventId)
.equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
.findFirst()
}

View File

@ -36,7 +36,7 @@ internal object MatrixModule {
@MatrixScope
fun providesMatrixCoroutineDispatchers(): MatrixCoroutineDispatchers {
return MatrixCoroutineDispatchers(io = Dispatchers.IO,
computation = Dispatchers.IO,
computation = Dispatchers.Default,
main = Dispatchers.Main,
crypto = createBackgroundHandler("Crypto_Thread").asCoroutineDispatcher(),
sync = Executors.newSingleThreadExecutor().asCoroutineDispatcher()

View File

@ -17,17 +17,24 @@
package im.vector.matrix.android.internal.network
import com.squareup.moshi.Moshi
import dagger.Lazy
import okhttp3.Call
import okhttp3.OkHttpClient
import okhttp3.Request
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import javax.inject.Inject
class RetrofitFactory @Inject constructor(private val moshi: Moshi) {
fun create(okHttpClient: OkHttpClient, baseUrl: String): Retrofit {
fun create(okHttpClient: Lazy<OkHttpClient>, baseUrl: String): Retrofit {
return Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.callFactory(object : Call.Factory {
override fun newCall(request: Request): Call {
return okHttpClient.get().newCall(request)
}
})
.addConverterFactory(UnitConverterFactory)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()

View File

@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session
import android.content.Context
import com.zhuinden.monarchy.Monarchy
import dagger.Binds
import dagger.Lazy
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoSet
@ -132,7 +133,7 @@ internal abstract class SessionModule {
@JvmStatic
@Provides
@SessionScope
fun providesRetrofit(@Authenticated okHttpClient: OkHttpClient,
fun providesRetrofit(@Authenticated okHttpClient: Lazy<OkHttpClient>,
sessionParams: SessionParams,
retrofitFactory: RetrofitFactory): Retrofit {
return retrofitFactory

View File

@ -16,54 +16,44 @@
package im.vector.matrix.android.internal.session.filter
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.model.FilterEntity
import im.vector.matrix.android.internal.database.model.FilterEntityFields
import im.vector.matrix.android.internal.database.query.getFilter
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.util.awaitTransaction
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.kotlin.where
import javax.inject.Inject
internal class DefaultFilterRepository @Inject constructor(
@SessionDatabase private val realmConfiguration: RealmConfiguration
) : FilterRepository {
internal class DefaultFilterRepository @Inject constructor(private val monarchy: Monarchy) : FilterRepository {
override fun storeFilter(filterBody: FilterBody, roomEventFilter: RoomEventFilter): Boolean {
val result: Boolean
override suspend fun storeFilter(filterBody: FilterBody, roomEventFilter: RoomEventFilter): Boolean {
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
val filter = FilterEntity.getFilter(realm)
val result = if (filter.filterBodyJson != filterBody.toJSONString()) {
// Filter has changed, store it and reset the filter Id
monarchy.awaitTransaction {
// We manage only one filter for now
val filterBodyJson = filterBody.toJSONString()
val roomEventFilterJson = roomEventFilter.toJSONString()
val realm = Realm.getInstance(realmConfiguration)
val filterEntity = FilterEntity.getFilter(it)
val filter = FilterEntity.getFilter(realm)
if (filter.filterBodyJson != filterBody.toJSONString()) {
// Filter has changed, store it and reset the filter Id
realm.executeTransaction {
// We manage only one filter for now
val filterBodyJson = filterBody.toJSONString()
val roomEventFilterJson = roomEventFilter.toJSONString()
val filterEntity = FilterEntity.getFilter(it)
filterEntity.filterBodyJson = filterBodyJson
filterEntity.roomEventFilterJson = roomEventFilterJson
// Reset filterId
filterEntity.filterId = ""
filterEntity.filterBodyJson = filterBodyJson
filterEntity.roomEventFilterJson = roomEventFilterJson
// Reset filterId
filterEntity.filterId = ""
}
true
} else {
filter.filterId.isBlank()
}
result = true
} else {
result = filter.filterId.isBlank()
result
}
realm.close()
return result
}
override fun storeFilterId(filterBody: FilterBody, filterId: String) {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {
override suspend fun storeFilterId(filterBody: FilterBody, filterId: String) {
monarchy.awaitTransaction {
// We manage only one filter for now
val filterBodyJson = filterBody.toJSONString()
@ -73,39 +63,24 @@ internal class DefaultFilterRepository @Inject constructor(
?.findFirst()
?.filterId = filterId
}
realm.close()
}
override fun getFilter(): String {
val result: String
val realm = Realm.getInstance(realmConfiguration)
val filter = FilterEntity.getFilter(realm)
result = if (filter.filterId.isBlank()) {
// Use the Json format
filter.filterBodyJson
} else {
// Use FilterId
filter.filterId
override suspend fun getFilter(): String {
return Realm.getInstance(monarchy.realmConfiguration).use {
val filter = FilterEntity.getFilter(it)
if (filter.filterId.isBlank()) {
// Use the Json format
filter.filterBodyJson
} else {
// Use FilterId
filter.filterId
}
}
realm.close()
return result
}
override fun getRoomFilter(): String {
val realm = Realm.getInstance(realmConfiguration)
val filter = FilterEntity.getFilter(realm)
val result = filter.roomEventFilterJson
realm.close()
return result
override suspend fun getRoomFilter(): String {
return Realm.getInstance(monarchy.realmConfiguration).use {
FilterEntity.getFilter(it).roomEventFilterJson
}
}
}

View File

@ -21,36 +21,13 @@ import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import javax.inject.Inject
internal class DefaultFilterService @Inject constructor(private val filterRepository: FilterRepository,
private val saveFilterTask: SaveFilterTask,
internal class DefaultFilterService @Inject constructor(private val saveFilterTask: SaveFilterTask,
private val taskExecutor: TaskExecutor) : FilterService {
// TODO Pass a list of support events instead
override fun setFilter(filterPreset: FilterService.FilterPreset) {
val filterBody = when (filterPreset) {
FilterService.FilterPreset.RiotFilter -> {
FilterFactory.createRiotFilterBody()
}
FilterService.FilterPreset.NoFilter -> {
FilterFactory.createDefaultFilterBody()
}
}
val roomFilter = when (filterPreset) {
FilterService.FilterPreset.RiotFilter -> {
FilterFactory.createRiotRoomFilter()
}
FilterService.FilterPreset.NoFilter -> {
FilterFactory.createDefaultRoomFilter()
}
}
val updated = filterRepository.storeFilter(filterBody, roomFilter)
if (updated) {
saveFilterTask
.configureWith(SaveFilterTask.Params(filterBody))
.executeBy(taskExecutor)
}
saveFilterTask
.configureWith(SaveFilterTask.Params(filterPreset))
.executeBy(taskExecutor)
}
}

View File

@ -16,18 +16,19 @@
package im.vector.matrix.android.internal.session.filter
import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject
/**
* Save a filter to the server
* Save a filter, in db and if any changes, upload to the server
*/
internal interface SaveFilterTask : Task<SaveFilterTask.Params, Unit> {
data class Params(
val filter: FilterBody
val filterPreset: FilterService.FilterPreset
)
}
@ -37,10 +38,29 @@ internal class DefaultSaveFilterTask @Inject constructor(@UserId private val use
) : SaveFilterTask {
override suspend fun execute(params: SaveFilterTask.Params) {
val filterResponse = executeRequest<FilterResponse> {
// TODO auto retry
apiCall = filterAPI.uploadFilter(userId, params.filter)
val filterBody = when (params.filterPreset) {
FilterService.FilterPreset.RiotFilter -> {
FilterFactory.createRiotFilterBody()
}
FilterService.FilterPreset.NoFilter -> {
FilterFactory.createDefaultFilterBody()
}
}
val roomFilter = when (params.filterPreset) {
FilterService.FilterPreset.RiotFilter -> {
FilterFactory.createRiotRoomFilter()
}
FilterService.FilterPreset.NoFilter -> {
FilterFactory.createDefaultRoomFilter()
}
}
val updated = filterRepository.storeFilter(filterBody, roomFilter)
if (updated) {
val filterResponse = executeRequest<FilterResponse> {
// TODO auto retry
apiCall = filterAPI.uploadFilter(userId, filterBody)
}
filterRepository.storeFilterId(filterBody, filterResponse.filterId)
}
filterRepository.storeFilterId(params.filter, filterResponse.filterId)
}
}

View File

@ -21,20 +21,20 @@ internal interface FilterRepository {
/**
* Return true if the filterBody has changed, or need to be sent to the server
*/
fun storeFilter(filterBody: FilterBody, roomEventFilter: RoomEventFilter): Boolean
suspend fun storeFilter(filterBody: FilterBody, roomEventFilter: RoomEventFilter): Boolean
/**
* Set the filterId of this filter
*/
fun storeFilterId(filterBody: FilterBody, filterId: String)
suspend fun storeFilterId(filterBody: FilterBody, filterId: String)
/**
* Return filter json or filter id
*/
fun getFilter(): String
suspend fun getFilter(): String
/**
* Return the room filter
*/
fun getRoomFilter(): String
suspend fun getRoomFilter(): String
}

View File

@ -21,7 +21,7 @@ import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.RoomAvatarContent
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.mapper.ContentMapper
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.query.prev
@ -41,8 +41,8 @@ internal class RoomAvatarResolver @Inject constructor(private val monarchy: Mona
fun resolve(roomId: String): String? {
var res: String? = null
monarchy.doWithRealm { realm ->
val roomName = EventEntity.where(realm, roomId, EventType.STATE_ROOM_AVATAR).prev()?.asDomain()
res = roomName?.content.toModel<RoomAvatarContent>()?.avatarUrl
val roomName = EventEntity.where(realm, roomId, EventType.STATE_ROOM_AVATAR).prev()
res = ContentMapper.map(roomName?.content).toModel<RoomAvatarContent>()?.avatarUrl
if (!res.isNullOrEmpty()) {
return@doWithRealm
}
@ -60,6 +60,6 @@ internal class RoomAvatarResolver @Inject constructor(private val monarchy: Mona
}
private fun EventEntity?.toRoomMember(): RoomMember? {
return this?.asDomain()?.content?.toModel<RoomMember>()
return ContentMapper.map(this?.content).toModel<RoomMember>()
}
}

View File

@ -21,7 +21,7 @@ import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.mapper.ContentMapper
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
@ -65,8 +65,10 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
roomId: String,
membership: Membership? = null,
roomSummary: RoomSyncSummary? = null,
unreadNotifications: RoomSyncUnreadNotifications? = null) {
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
unreadNotifications: RoomSyncUnreadNotifications? = null,
updateMembers: Boolean = false) {
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId)
if (roomSummary != null) {
if (roomSummary.heroes.isNotEmpty()) {
@ -88,24 +90,27 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
}
val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, filterTypes = PREVIEWABLE_TYPES)
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain()
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
// avoid this call if we are sure there are unread events
|| !isEventRead(monarchy, userId, roomId, latestPreviewableEvent?.eventId)
val otherRoomMembers = RoomMembers(realm, roomId)
.queryRoomMembersEvent()
.notEqualTo(EventEntityFields.STATE_KEY, userId)
.findAll()
.asSequence()
.map { it.stateKey }
// avoid this call if we are sure there are unread events
|| !isEventRead(monarchy, userId, roomId, latestPreviewableEvent?.eventId)
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
roomSummaryEntity.topic = lastTopicEvent?.content.toModel<RoomTopicContent>()?.topic
roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel<RoomTopicContent>()?.topic
roomSummaryEntity.latestPreviewableEvent = latestPreviewableEvent
roomSummaryEntity.otherMemberIds.clear()
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)
if (updateMembers) {
val otherRoomMembers = RoomMembers(realm, roomId)
.queryRoomMembersEvent()
.notEqualTo(EventEntityFields.STATE_KEY, userId)
.findAll()
.asSequence()
.map { it.stateKey }
roomSummaryEntity.otherMemberIds.clear()
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)
}
}
}

View File

@ -74,7 +74,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP
it.updateSenderData()
}
roomEntity.areAllMembersLoaded = true
roomSummaryUpdater.update(realm, roomId)
roomSummaryUpdater.update(realm, roomId, updateMembers = true)
}
}

View File

@ -22,7 +22,7 @@ import im.vector.matrix.android.R
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.*
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.mapper.ContentMapper
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.RoomEntity
@ -56,20 +56,20 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
var name: CharSequence? = null
monarchy.doWithRealm { realm ->
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst()
val roomName = EventEntity.where(realm, roomId, EventType.STATE_ROOM_NAME).prev()?.asDomain()
name = roomName?.content.toModel<RoomNameContent>()?.name
val roomName = EventEntity.where(realm, roomId, EventType.STATE_ROOM_NAME).prev()
name = ContentMapper.map(roomName?.content).toModel<RoomNameContent>()?.name
if (!name.isNullOrEmpty()) {
return@doWithRealm
}
val canonicalAlias = EventEntity.where(realm, roomId, EventType.STATE_CANONICAL_ALIAS).prev()?.asDomain()
name = canonicalAlias?.content.toModel<RoomCanonicalAliasContent>()?.canonicalAlias
val canonicalAlias = EventEntity.where(realm, roomId, EventType.STATE_CANONICAL_ALIAS).prev()
name = ContentMapper.map(canonicalAlias?.content).toModel<RoomCanonicalAliasContent>()?.canonicalAlias
if (!name.isNullOrEmpty()) {
return@doWithRealm
}
val aliases = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).prev()?.asDomain()
name = aliases?.content.toModel<RoomAliasesContent>()?.aliases?.firstOrNull()
val aliases = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).prev()
name = ContentMapper.map(aliases?.content).toModel<RoomAliasesContent>()?.aliases?.firstOrNull()
if (!name.isNullOrEmpty()) {
return@doWithRealm
}
@ -132,6 +132,6 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
}
private fun EventEntity?.toRoomMember(): RoomMember? {
return this?.asDomain()?.content?.toModel<RoomMember>()
return ContentMapper.map(this?.content).toModel<RoomMember>()
}
}

View File

@ -67,6 +67,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
}
suspend fun handle(roomsSyncResponse: RoomsSyncResponse, isInitialSync: Boolean, reporter: DefaultInitialSyncProgressService? = null) {
Timber.v("Execute transaction from $this")
monarchy.awaitTransaction { realm ->
handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, reporter)
handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, reporter)
@ -133,6 +134,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
roomEntity.membership = Membership.JOIN
// State event
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt()
?: Int.MIN_VALUE
@ -146,7 +148,6 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
}
}
}
if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) {
val chunkEntity = handleTimelineEvents(
realm,
@ -157,14 +158,19 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
)
roomEntity.addOrUpdate(chunkEntity)
}
roomSummaryUpdater.update(realm, roomId, Membership.JOIN, roomSync.summary, roomSync.unreadNotifications)
val hasRoomMember = roomSync.state?.events?.firstOrNull {
it.type == EventType.STATE_ROOM_MEMBER
} != null || roomSync.timeline?.events?.firstOrNull {
it.type == EventType.STATE_ROOM_MEMBER
} != null
roomSummaryUpdater.update(realm, roomId, Membership.JOIN, roomSync.summary, roomSync.unreadNotifications, updateMembers = hasRoomMember)
return roomEntity
}
private fun handleInvitedRoom(realm: Realm,
roomId: String,
roomSync:
InvitedRoomSync): RoomEntity {
roomSync: InvitedRoomSync): RoomEntity {
Timber.v("Handle invited sync for room $roomId")
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
roomEntity.membership = Membership.INVITE
@ -172,7 +178,10 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
val chunkEntity = handleTimelineEvents(realm, roomEntity, roomSync.inviteState.events)
roomEntity.addOrUpdate(chunkEntity)
}
roomSummaryUpdater.update(realm, roomId, Membership.INVITE)
val hasRoomMember = roomSync.inviteState?.events?.firstOrNull {
it.type == EventType.STATE_ROOM_MEMBER
} != null
roomSummaryUpdater.update(realm, roomId, Membership.INVITE, updateMembers = hasRoomMember)
return roomEntity
}

View File

@ -16,27 +16,24 @@
package im.vector.matrix.android.internal.session.sync
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.model.SyncEntity
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.util.awaitTransaction
import io.realm.Realm
import io.realm.RealmConfiguration
import javax.inject.Inject
internal class SyncTokenStore @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration) {
internal class SyncTokenStore @Inject constructor(private val monarchy: Monarchy) {
fun getLastToken(): String? {
val realm = Realm.getInstance(realmConfiguration)
val token = realm.where(SyncEntity::class.java).findFirst()?.nextBatch
realm.close()
return token
return Realm.getInstance(monarchy.realmConfiguration).use {
it.where(SyncEntity::class.java).findFirst()?.nextBatch
}
}
fun saveToken(token: String?) {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {
suspend fun saveToken(token: String?) {
monarchy.awaitTransaction {
val sync = SyncEntity(token)
it.insertOrUpdate(sync)
}
realm.close()
}
}

View File

@ -268,7 +268,7 @@ dependencies {
implementation("com.airbnb.android:epoxy:$epoxy_version")
kapt "com.airbnb.android:epoxy-processor:$epoxy_version"
implementation "com.airbnb.android:epoxy-paging:$epoxy_version"
implementation 'com.airbnb.android:mvrx:1.1.0'
implementation 'com.airbnb.android:mvrx:1.3.0'
// Work
implementation "androidx.work:work-runtime-ktx:2.3.0-alpha01"

View File

@ -26,6 +26,7 @@ import im.vector.matrix.rx.rx
import im.vector.riotx.features.home.HomeRoomListDataSource
import im.vector.riotx.features.home.group.ALL_COMMUNITIES_GROUP_ID
import im.vector.riotx.features.home.group.SelectedGroupDataSource
import im.vector.riotx.features.home.room.list.ChronologicalRoomComparator
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
@ -43,7 +44,8 @@ import javax.inject.Singleton
class AppStateHandler @Inject constructor(
private val sessionDataSource: ActiveSessionDataSource,
private val homeRoomListDataSource: HomeRoomListDataSource,
private val selectedGroupDataSource: SelectedGroupDataSource) : LifecycleObserver {
private val selectedGroupDataSource: SelectedGroupDataSource,
private val chronologicalRoomComparator: ChronologicalRoomComparator) : LifecycleObserver {
private val compositeDisposable = CompositeDisposable()
@ -64,31 +66,24 @@ class AppStateHandler @Inject constructor(
.observeOn(AndroidSchedulers.mainThread())
.switchMap {
it.orNull()?.rx()?.liveRoomSummaries()
?: Observable.just(emptyList())
?: Observable.just(emptyList())
}
.throttleLast(300, TimeUnit.MILLISECONDS),
selectedGroupDataSource.observe(),
BiFunction { rooms, selectedGroupOption ->
val selectedGroup = selectedGroupOption.orNull()
val filteredDirectRooms = rooms
.filter { it.isDirect }
.filter {
if (selectedGroup == null || selectedGroup.groupId == ALL_COMMUNITIES_GROUP_ID) {
true
} else {
it.otherMemberIds
.intersect(selectedGroup.userIds)
.isNotEmpty()
}
}
val filteredGroupRooms = rooms
.filter { !it.isDirect }
.filter {
selectedGroup?.groupId == ALL_COMMUNITIES_GROUP_ID
|| selectedGroup?.roomIds?.contains(it.roomId) ?: true
}
filteredDirectRooms + filteredGroupRooms
val filteredRooms = rooms.filter {
if (selectedGroup == null || selectedGroup.groupId == ALL_COMMUNITIES_GROUP_ID) {
true
} else if (it.isDirect) {
it.otherMemberIds
.intersect(selectedGroup.userIds)
.isNotEmpty()
} else {
selectedGroup.roomIds.contains(it.roomId)
}
}
filteredRooms.sortedWith(chronologicalRoomComparator)
}
)
.subscribe {

View File

@ -21,31 +21,31 @@ import androidx.fragment.app.Fragment
import im.vector.riotx.core.platform.VectorBaseActivity
fun VectorBaseActivity.addFragment(frameId: Int, fragment: Fragment) {
supportFragmentManager.inTransaction { add(frameId, fragment) }
supportFragmentManager.commitTransactionNow { add(frameId, fragment) }
}
fun <T : Fragment> VectorBaseActivity.addFragment(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
supportFragmentManager.inTransaction {
supportFragmentManager.commitTransactionNow {
add(frameId, fragmentClass, params.toMvRxBundle(), tag)
}
}
fun VectorBaseActivity.replaceFragment(frameId: Int, fragment: Fragment, tag: String? = null) {
supportFragmentManager.inTransaction { replace(frameId, fragment, tag) }
supportFragmentManager.commitTransactionNow { replace(frameId, fragment, tag) }
}
fun <T : Fragment> VectorBaseActivity.replaceFragment(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
supportFragmentManager.inTransaction {
supportFragmentManager.commitTransactionNow {
replace(frameId, fragmentClass, params.toMvRxBundle(), tag)
}
}
fun VectorBaseActivity.addFragmentToBackstack(frameId: Int, fragment: Fragment, tag: String? = null) {
supportFragmentManager.inTransaction { replace(frameId, fragment).addToBackStack(tag) }
supportFragmentManager.commitTransaction { replace(frameId, fragment).addToBackStack(tag) }
}
fun <T : Fragment> VectorBaseActivity.addFragmentToBackstack(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
supportFragmentManager.inTransaction {
supportFragmentManager.commitTransaction {
replace(frameId, fragmentClass, params.toMvRxBundle(), tag).addToBackStack(tag)
}
}

View File

@ -21,61 +21,61 @@ import androidx.fragment.app.Fragment
import im.vector.riotx.core.platform.VectorBaseFragment
fun VectorBaseFragment.addFragment(frameId: Int, fragment: Fragment) {
parentFragmentManager.inTransaction { add(frameId, fragment) }
parentFragmentManager.commitTransactionNow { add(frameId, fragment) }
}
fun <T : Fragment> VectorBaseFragment.addFragment(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
parentFragmentManager.inTransaction {
parentFragmentManager.commitTransactionNow {
add(frameId, fragmentClass, params.toMvRxBundle(), tag)
}
}
fun VectorBaseFragment.replaceFragment(frameId: Int, fragment: Fragment) {
parentFragmentManager.inTransaction { replace(frameId, fragment) }
parentFragmentManager.commitTransactionNow { replace(frameId, fragment) }
}
fun <T : Fragment> VectorBaseFragment.replaceFragment(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
parentFragmentManager.inTransaction {
parentFragmentManager.commitTransactionNow {
replace(frameId, fragmentClass, params.toMvRxBundle(), tag)
}
}
fun VectorBaseFragment.addFragmentToBackstack(frameId: Int, fragment: Fragment, tag: String? = null) {
parentFragmentManager.inTransaction { replace(frameId, fragment).addToBackStack(tag) }
parentFragmentManager.commitTransaction { replace(frameId, fragment, tag).addToBackStack(tag) }
}
fun <T : Fragment> VectorBaseFragment.addFragmentToBackstack(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
parentFragmentManager.inTransaction {
parentFragmentManager.commitTransaction {
replace(frameId, fragmentClass, params.toMvRxBundle(), tag).addToBackStack(tag)
}
}
fun VectorBaseFragment.addChildFragment(frameId: Int, fragment: Fragment) {
childFragmentManager.inTransaction { add(frameId, fragment) }
fun VectorBaseFragment.addChildFragment(frameId: Int, fragment: Fragment, tag: String? = null) {
childFragmentManager.commitTransactionNow { add(frameId, fragment, tag) }
}
fun <T : Fragment> VectorBaseFragment.addChildFragment(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
childFragmentManager.inTransaction {
childFragmentManager.commitTransactionNow {
add(frameId, fragmentClass, params.toMvRxBundle(), tag)
}
}
fun VectorBaseFragment.replaceChildFragment(frameId: Int, fragment: Fragment) {
childFragmentManager.inTransaction { replace(frameId, fragment) }
fun VectorBaseFragment.replaceChildFragment(frameId: Int, fragment: Fragment, tag: String? = null) {
childFragmentManager.commitTransactionNow { replace(frameId, fragment, tag) }
}
fun <T : Fragment> VectorBaseFragment.replaceChildFragment(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
childFragmentManager.inTransaction {
childFragmentManager.commitTransactionNow {
replace(frameId, fragmentClass, params.toMvRxBundle(), tag)
}
}
fun VectorBaseFragment.addChildFragmentToBackstack(frameId: Int, fragment: Fragment, tag: String? = null) {
childFragmentManager.inTransaction { replace(frameId, fragment).addToBackStack(tag) }
childFragmentManager.commitTransaction { replace(frameId, fragment).addToBackStack(tag) }
}
fun <T : Fragment> VectorBaseFragment.addChildFragmentToBackstack(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
childFragmentManager.inTransaction {
childFragmentManager.commitTransaction {
replace(frameId, fragmentClass, params.toMvRxBundle(), tag).addToBackStack(tag)
}
}

View File

@ -18,6 +18,10 @@ package im.vector.riotx.core.extensions
import androidx.fragment.app.FragmentTransaction
inline fun androidx.fragment.app.FragmentManager.inTransaction(func: FragmentTransaction.() -> FragmentTransaction) {
inline fun androidx.fragment.app.FragmentManager.commitTransactionNow(func: FragmentTransaction.() -> FragmentTransaction) {
beginTransaction().func().commitNow()
}
inline fun androidx.fragment.app.FragmentManager.commitTransaction(func: FragmentTransaction.() -> FragmentTransaction) {
beginTransaction().func().commit()
}

View File

@ -24,6 +24,7 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.annotation.*
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isVisible
@ -34,7 +35,6 @@ import androidx.lifecycle.ViewModelProviders
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.Unbinder
import com.airbnb.mvrx.BaseMvRxActivity
import com.airbnb.mvrx.MvRx
import com.bumptech.glide.util.Util
import com.google.android.material.snackbar.Snackbar
@ -59,7 +59,7 @@ import io.reactivex.disposables.Disposable
import timber.log.Timber
import kotlin.system.measureTimeMillis
abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector {
abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
/* ==========================================================================================
* UI
* ========================================================================================== */

View File

@ -25,24 +25,22 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.MvRxView
import com.airbnb.mvrx.MvRxViewModelStore
import com.airbnb.mvrx.MvRxViewId
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import im.vector.riotx.core.di.DaggerScreenComponent
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.utils.DimensionConverter
import java.util.*
/**
* Add MvRx capabilities to bottomsheetdialog (like BaseMvRxFragment)
*/
abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment(), MvRxView {
override val mvrxViewModelStore by lazy { MvRxViewModelStore(viewModelStore) }
private lateinit var mvrxPersistedViewId: String
private val mvrxViewIdProperty = MvRxViewId()
final override val mvrxViewId: String by mvrxViewIdProperty
private lateinit var screenComponent: ScreenComponent
final override val mvrxViewId: String by lazy { mvrxPersistedViewId }
/* ==========================================================================================
* View model
@ -78,10 +76,7 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment()
protected open fun injectWith(screenComponent: ScreenComponent) = Unit
override fun onCreate(savedInstanceState: Bundle?) {
mvrxViewModelStore.restoreViewModels(this, savedInstanceState)
mvrxPersistedViewId = savedInstanceState?.getString(PERSISTED_VIEW_ID_KEY)
?: this::class.java.simpleName + "_" + UUID.randomUUID().toString()
mvrxViewIdProperty.restoreFrom(savedInstanceState)
super.onCreate(savedInstanceState)
}
@ -98,8 +93,7 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment()
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mvrxViewModelStore.saveViewModels(outState)
outState.putString(PERSISTED_VIEW_ID_KEY, mvrxViewId)
mvrxViewIdProperty.saveTo(outState)
}
override fun onStart() {
@ -121,5 +115,3 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment()
arguments = args?.let { Bundle().apply { putParcelable(MvRx.KEY_ARG, it) } }
}
}
private const val PERSISTED_VIEW_ID_KEY = "mvrx:bottomsheet_persisted_view_id"

View File

@ -114,11 +114,12 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
super.onDestroyView()
mUnBinder?.unbind()
mUnBinder = null
uiDisposables.clear()
}
override fun onDestroy() {
super.onDestroy()
uiDisposables.dispose()
super.onDestroy()
}
override fun injector(): ScreenComponent {
@ -181,7 +182,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
private val uiDisposables = CompositeDisposable()
protected fun Disposable.disposeOnDestroy(): Disposable {
protected fun Disposable.disposeOnDestroyView(): Disposable {
uiDisposables.add(this)
return this
}

View File

@ -23,6 +23,7 @@ import android.widget.ImageView
import android.widget.LinearLayout
import androidx.core.view.isVisible
import im.vector.riotx.R
import im.vector.riotx.core.glide.GlideApp
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
import kotlinx.android.synthetic.main.view_read_receipts.view.*
@ -105,4 +106,11 @@ class ReadReceiptsView @JvmOverloads constructor(
isVisible = false
}
}
fun unbind() {
receiptAvatars.forEach {
GlideApp.with(context.applicationContext).clear(it)
}
isVisible = false
}
}

View File

@ -34,14 +34,14 @@ interface MutableDataSource<T> : DataSource<T> {
*/
open class BehaviorDataSource<T>(private val defaultValue: T? = null) : MutableDataSource<T> {
private val storeRelay = createRelay()
private val behaviorRelay = createRelay()
override fun observe(): Observable<T> {
return storeRelay.hide().observeOn(Schedulers.computation())
return behaviorRelay.hide().observeOn(Schedulers.computation())
}
override fun post(value: T) {
storeRelay.accept(value)
behaviorRelay.accept(value)
}
private fun createRelay(): BehaviorRelay<T> {
@ -58,13 +58,13 @@ open class BehaviorDataSource<T>(private val defaultValue: T? = null) : MutableD
*/
open class PublishDataSource<T> : MutableDataSource<T> {
private val storeRelay = PublishRelay.create<T>()
private val publishRelay = PublishRelay.create<T>()
override fun observe(): Observable<T> {
return storeRelay.hide()
return publishRelay.hide()
}
override fun post(value: T) {
storeRelay.accept(value)
publishRelay.accept(value)
}
}

View File

@ -26,7 +26,7 @@ import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTr
import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationRequest
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
import im.vector.riotx.R
import im.vector.riotx.core.extensions.inTransaction
import im.vector.riotx.core.extensions.commitTransaction
import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.platform.SimpleFragmentActivity
import im.vector.riotx.core.platform.WaitingViewData
@ -102,20 +102,20 @@ class SASVerificationActivity : SimpleFragmentActivity() {
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT,
IncomingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT -> {
supportActionBar?.setTitle(R.string.sas_incoming_request_title)
supportFragmentManager.inTransaction {
supportFragmentManager.commitTransaction {
setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out)
replace(R.id.container, SASVerificationIncomingFragment::class.java, null)
}
}
IncomingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION,
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
supportFragmentManager.inTransaction {
supportFragmentManager.commitTransaction {
setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out)
replace(R.id.container, SASVerificationShortCodeFragment::class.java, null)
}
}
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
supportFragmentManager.inTransaction {
supportFragmentManager.commitTransaction {
setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out)
replace(R.id.container, SASVerificationVerifiedFragment::class.java, null)
}
@ -133,20 +133,20 @@ class SASVerificationActivity : SimpleFragmentActivity() {
OutgoingSasVerificationRequest.UxState.UNKNOWN,
OutgoingSasVerificationRequest.UxState.WAIT_FOR_START,
OutgoingSasVerificationRequest.UxState.WAIT_FOR_KEY_AGREEMENT -> {
supportFragmentManager.inTransaction {
supportFragmentManager.commitTransaction {
setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out)
replace(R.id.container, SASVerificationStartFragment::class.java, null)
}
}
OutgoingSasVerificationRequest.UxState.SHOW_SAS,
OutgoingSasVerificationRequest.UxState.WAIT_FOR_VERIFICATION -> {
supportFragmentManager.inTransaction {
supportFragmentManager.commitTransaction {
setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out)
replace(R.id.container, SASVerificationShortCodeFragment::class.java, null)
}
}
OutgoingSasVerificationRequest.UxState.VERIFIED -> {
supportFragmentManager.inTransaction {
supportFragmentManager.commitTransaction {
setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out)
replace(R.id.container, SASVerificationVerifiedFragment::class.java, null)
}
@ -172,13 +172,13 @@ class SASVerificationActivity : SimpleFragmentActivity() {
finish()
}
SasVerificationViewModel.NAVIGATE_SAS_DISPLAY -> {
supportFragmentManager.inTransaction {
supportFragmentManager.commitTransaction {
setCustomAnimations(R.anim.enter_from_right, R.anim.exit_fade_out)
replace(R.id.container, SASVerificationShortCodeFragment::class.java, null)
}
}
SasVerificationViewModel.NAVIGATE_SUCCESS -> {
supportFragmentManager.inTransaction {
supportFragmentManager.commitTransaction {
setCustomAnimations(R.anim.enter_from_right, R.anim.exit_fade_out)
replace(R.id.container, SASVerificationVerifiedFragment::class.java, null)
}

View File

@ -17,8 +17,7 @@
package im.vector.riotx.features.home
import im.vector.riotx.core.platform.VectorViewModelAction
import im.vector.riotx.features.home.room.list.RoomListFragment
sealed class HomeDetailAction : VectorViewModelAction {
data class SwitchDisplayMode(val displayMode: RoomListFragment.DisplayMode) : HomeDetailAction()
data class SwitchDisplayMode(val displayMode: RoomListDisplayMode) : HomeDetailAction()
}

View File

@ -18,6 +18,7 @@ package im.vector.riotx.features.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.core.view.forEachIndexed
import androidx.lifecycle.Observer
import com.airbnb.mvrx.fragmentViewModel
@ -28,7 +29,7 @@ import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.riotx.R
import im.vector.riotx.core.extensions.addChildFragmentToBackstack
import im.vector.riotx.core.extensions.commitTransactionNow
import im.vector.riotx.core.platform.ToolbarConfigurable
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.ui.views.KeysBackupBanner
@ -59,9 +60,8 @@ class HomeDetailFragment @Inject constructor(
return R.layout.fragment_home_detail
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java)
setupBottomNavigationView()
@ -133,10 +133,10 @@ class HomeDetailFragment @Inject constructor(
private fun setupBottomNavigationView() {
bottomNavigationView.setOnNavigationItemSelectedListener {
val displayMode = when (it.itemId) {
R.id.bottom_action_home -> RoomListFragment.DisplayMode.HOME
R.id.bottom_action_people -> RoomListFragment.DisplayMode.PEOPLE
R.id.bottom_action_rooms -> RoomListFragment.DisplayMode.ROOMS
else -> RoomListFragment.DisplayMode.HOME
R.id.bottom_action_home -> RoomListDisplayMode.HOME
R.id.bottom_action_people -> RoomListDisplayMode.PEOPLE
R.id.bottom_action_rooms -> RoomListDisplayMode.ROOMS
else -> RoomListDisplayMode.HOME
}
viewModel.handle(HomeDetailAction.SwitchDisplayMode(displayMode))
true
@ -152,25 +152,32 @@ class HomeDetailFragment @Inject constructor(
}
}
private fun switchDisplayMode(displayMode: RoomListFragment.DisplayMode) {
private fun switchDisplayMode(displayMode: RoomListDisplayMode) {
groupToolbarTitleView.setText(displayMode.titleRes)
updateSelectedFragment(displayMode)
// Update the navigation view (for when we restore the tabs)
bottomNavigationView.selectedItemId = when (displayMode) {
RoomListFragment.DisplayMode.PEOPLE -> R.id.bottom_action_people
RoomListFragment.DisplayMode.ROOMS -> R.id.bottom_action_rooms
RoomListDisplayMode.PEOPLE -> R.id.bottom_action_people
RoomListDisplayMode.ROOMS -> R.id.bottom_action_rooms
else -> R.id.bottom_action_home
}
}
private fun updateSelectedFragment(displayMode: RoomListFragment.DisplayMode) {
private fun updateSelectedFragment(displayMode: RoomListDisplayMode) {
val fragmentTag = "FRAGMENT_TAG_${displayMode.name}"
val fragment = childFragmentManager.findFragmentByTag(fragmentTag)
if (fragment == null) {
val params = RoomListParams(displayMode)
addChildFragmentToBackstack(R.id.roomListContainer, RoomListFragment::class.java, params, fragmentTag)
} else {
addChildFragmentToBackstack(R.id.roomListContainer, fragment, fragmentTag)
val fragmentToShow = childFragmentManager.findFragmentByTag(fragmentTag)
childFragmentManager.commitTransactionNow {
childFragmentManager.fragments
.filter { it != fragmentToShow }
.forEach {
hide(it)
}
if (fragmentToShow == null) {
val params = RoomListParams(displayMode)
add(R.id.roomListContainer, RoomListFragment::class.java, params.toMvRxBundle(), fragmentTag)
} else {
show(fragmentToShow)
}
}
}

View File

@ -17,14 +17,17 @@
package im.vector.riotx.features.home
import arrow.core.Option
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.sync.SyncState
import im.vector.riotx.features.home.room.list.RoomListFragment
data class HomeDetailViewState(
val groupSummary: Option<GroupSummary> = Option.empty(),
val displayMode: RoomListFragment.DisplayMode = RoomListFragment.DisplayMode.HOME,
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
val displayMode: RoomListDisplayMode = RoomListDisplayMode.HOME,
val notificationCountCatchup: Int = 0,
val notificationHighlightCatchup: Boolean = false,
val notificationCountPeople: Int = 0,

View File

@ -17,6 +17,7 @@
package im.vector.riotx.features.home
import android.os.Bundle
import android.view.View
import im.vector.matrix.android.api.session.Session
import im.vector.riotx.R
import im.vector.riotx.core.extensions.observeK
@ -33,8 +34,8 @@ class HomeDrawerFragment @Inject constructor(
override fun getLayoutResId() = R.layout.fragment_home_drawer
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (savedInstanceState == null) {
replaceChildFragment(R.id.homeDrawerGroupListContainer, GroupListFragment::class.java)
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.home
import androidx.annotation.StringRes
import im.vector.riotx.R
enum class RoomListDisplayMode(@StringRes val titleRes: Int) {
HOME(R.string.bottom_action_home),
PEOPLE(R.string.bottom_action_people_x),
ROOMS(R.string.bottom_action_rooms),
FILTERED(/* Not used */ 0),
SHARE(/* Not used */ 0)
}

View File

@ -18,6 +18,7 @@ package im.vector.riotx.features.home.createdirect
import android.content.Context
import android.os.Bundle
import android.view.View
import android.view.inputmethod.InputMethodManager
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
@ -40,8 +41,8 @@ class CreateDirectRoomDirectoryUsersFragment @Inject constructor(
private lateinit var sharedActionViewModel: CreateDirectRoomSharedActionViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(CreateDirectRoomSharedActionViewModel::class.java)
setupRecyclerView()
setupSearchByMatrixIdView()
@ -61,7 +62,7 @@ class CreateDirectRoomDirectoryUsersFragment @Inject constructor(
.subscribe {
viewModel.handle(CreateDirectRoomAction.SearchDirectoryUsers(it.toString()))
}
.disposeOnDestroy()
.disposeOnDestroyView()
createDirectRoomSearchById.requestFocus()
val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
imm?.showSoftInput(createDirectRoomSearchById, InputMethodManager.SHOW_IMPLICIT)

View File

@ -21,6 +21,7 @@ package im.vector.riotx.features.home.createdirect
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ScrollView
import androidx.core.view.size
import com.airbnb.mvrx.activityViewModel
@ -50,8 +51,8 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor(
private val viewModel: CreateDirectRoomViewModel by activityViewModel()
private lateinit var sharedActionViewModel: CreateDirectRoomSharedActionViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(CreateDirectRoomSharedActionViewModel::class.java)
vectorBaseActivity.setSupportActionBar(createDirectRoomToolbar)
setupRecyclerView()
@ -113,7 +114,7 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor(
}
viewModel.handle(action)
}
.disposeOnDestroy()
.disposeOnDestroyView()
createDirectRoomFilter.setupAsSearch()
createDirectRoomFilter.requestFocus()

View File

@ -17,6 +17,7 @@
package im.vector.riotx.features.home.group
import android.os.Bundle
import android.view.View
import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.fragmentViewModel
@ -40,8 +41,8 @@ class GroupListFragment @Inject constructor(
override fun getLayoutResId() = R.layout.fragment_group_list
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java)
groupController.callback = this
stateView.contentView = groupListEpoxyRecyclerView

View File

@ -42,10 +42,10 @@ sealed class RoomDetailAction : VectorViewModelAction {
object AcceptInvite : RoomDetailAction()
object RejectInvite : RoomDetailAction()
data class EnterEditMode(val eventId: String, val draft: String) : RoomDetailAction()
data class EnterQuoteMode(val eventId: String, val draft: String) : RoomDetailAction()
data class EnterReplyMode(val eventId: String, val draft: String) : RoomDetailAction()
data class ExitSpecialMode(val draft: String) : RoomDetailAction()
data class EnterEditMode(val eventId: String, val text: String) : RoomDetailAction()
data class EnterQuoteMode(val eventId: String, val text: String) : RoomDetailAction()
data class EnterReplyMode(val eventId: String, val text: String) : RoomDetailAction()
data class ExitSpecialMode(val text: String) : RoomDetailAction()
data class ResendMessage(val eventId: String) : RoomDetailAction()
data class RemoveFailedEcho(val eventId: String) : RoomDetailAction()

View File

@ -203,15 +203,14 @@ class RoomDetailFragment @Inject constructor(
private var lockSendButton = false
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
attachmentsHelper = AttachmentsHelper.create(this, this).register()
keyboardStateUtils = KeyboardStateUtils(requireActivity())
setupToolbar(roomToolbar)
setupRecyclerView()
setupComposer()
setupAttachmentButton()
setupInviteView()
setupNotificationView()
setupJumpToReadMarkerView()
@ -229,7 +228,7 @@ class RoomDetailFragment @Inject constructor(
.subscribe {
handleActions(it)
}
.disposeOnDestroy()
.disposeOnDestroyView()
roomDetailViewModel.navigateToEvent.observeEvent(this) {
val scrollPosition = timelineEventController.searchPositionOfEvent(it)
@ -274,7 +273,10 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.requestLiveData.observeEvent(this) {
displayRoomDetailActionResult(it)
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if (savedInstanceState == null) {
when (val sharedData = roomDetailArgs.sharedData) {
is SharedData.Text -> roomDetailViewModel.handle(RoomDetailAction.SendMessage(sharedData.text, false))
@ -284,6 +286,11 @@ class RoomDetailFragment @Inject constructor(
}
}
override fun onDestroyView() {
super.onDestroyView()
recyclerView.adapter = null
}
override fun onDestroy() {
debouncer.cancelAll()
super.onDestroy()
@ -470,6 +477,7 @@ class RoomDetailFragment @Inject constructor(
}
}
recyclerView.adapter = timelineEventController.adapter
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (recyclerView.scrollState == RecyclerView.SCROLL_STATE_IDLE) {
@ -490,6 +498,7 @@ class RoomDetailFragment @Inject constructor(
}
}
})
timelineEventController.callback = this
if (vectorPreferences.swipeToReplyIsEnabled()) {
@ -592,21 +601,29 @@ class RoomDetailFragment @Inject constructor(
})
.build()
composerLayout.sendButton.setOnClickListener {
if (lockSendButton) {
Timber.w("Send button is locked")
return@setOnClickListener
}
val textMessage = composerLayout.composerEditText.text.toString()
if (textMessage.isNotBlank()) {
lockSendButton = true
roomDetailViewModel.handle(RoomDetailAction.SendMessage(textMessage, vectorPreferences.isMarkdownEnabled()))
}
}
composerLayout.composerRelatedMessageCloseButton.setOnClickListener {
roomDetailViewModel.handle(RoomDetailAction.ExitSpecialMode(composerLayout.composerEditText.text.toString()))
}
composerLayout.callback = object : TextComposerView.Callback {
override fun onAddAttachment() {
if (!::attachmentTypeSelector.isInitialized) {
attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@RoomDetailFragment)
}
attachmentTypeSelector.show(composerLayout.attachmentButton, keyboardStateUtils.isKeyboardShowing)
}
override fun onSendMessage(text: String) {
if (lockSendButton) {
Timber.w("Send button is locked")
return
}
if (text.isNotBlank()) {
lockSendButton = true
roomDetailViewModel.handle(RoomDetailAction.SendMessage(text, vectorPreferences.isMarkdownEnabled()))
}
}
override fun onCloseRelatedMessage() {
roomDetailViewModel.handle(RoomDetailAction.ExitSpecialMode(composerLayout.text.toString()))
}
override fun onRichContentSelected(contentUri: Uri): Boolean {
// We need WRITE_EXTERNAL permission
return if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this@RoomDetailFragment, PERMISSION_REQUEST_CODE_INCOMING_URI)) {
@ -629,15 +646,6 @@ class RoomDetailFragment @Inject constructor(
return isHandled
}
private fun setupAttachmentButton() {
composerLayout.attachmentButton.setOnClickListener {
if (!::attachmentTypeSelector.isInitialized) {
attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this)
}
attachmentTypeSelector.show(composerLayout.attachmentButton, keyboardStateUtils.isKeyboardShowing)
}
}
private fun setupInviteView() {
inviteView.callback = this
}
@ -1112,13 +1120,13 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add))
}
is EventSharedAction.Edit -> {
roomDetailViewModel.handle(RoomDetailAction.EnterEditMode(action.eventId, composerLayout.composerEditText.text.toString()))
roomDetailViewModel.handle(RoomDetailAction.EnterEditMode(action.eventId, composerLayout.text.toString()))
}
is EventSharedAction.Quote -> {
roomDetailViewModel.handle(RoomDetailAction.EnterQuoteMode(action.eventId, composerLayout.composerEditText.text.toString()))
roomDetailViewModel.handle(RoomDetailAction.EnterQuoteMode(action.eventId, composerLayout.text.toString()))
}
is EventSharedAction.Reply -> {
roomDetailViewModel.handle(RoomDetailAction.EnterReplyMode(action.eventId, composerLayout.composerEditText.text.toString()))
roomDetailViewModel.handle(RoomDetailAction.EnterReplyMode(action.eventId, composerLayout.text.toString()))
}
is EventSharedAction.CopyPermalink -> {
val permalink = PermalinkFactory.createPermalink(roomDetailArgs.roomId, action.eventId)

View File

@ -521,9 +521,10 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
}
private fun handleEditAction(action: RoomDetailAction.EnterEditMode) {
saveCurrentDraft(action.draft)
saveCurrentDraft(action.text)
room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.EDIT(timelineEvent, action.text)) }
timelineEvent.root.eventId?.let {
room.saveDraft(UserDraft.EDIT(it, timelineEvent.getTextEditableContent() ?: ""))
}
@ -531,16 +532,17 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
}
private fun handleQuoteAction(action: RoomDetailAction.EnterQuoteMode) {
saveCurrentDraft(action.draft)
saveCurrentDraft(action.text)
room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.QUOTE(timelineEvent, action.text)) }
withState { state ->
// Save a new draft and keep the previously entered text, if it was not an edit
timelineEvent.root.eventId?.let {
if (state.sendMode is SendMode.EDIT) {
room.saveDraft(UserDraft.QUOTE(it, ""))
} else {
room.saveDraft(UserDraft.QUOTE(it, action.draft))
room.saveDraft(UserDraft.QUOTE(it, action.text))
}
}
}
@ -548,16 +550,17 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
}
private fun handleReplyAction(action: RoomDetailAction.EnterReplyMode) {
saveCurrentDraft(action.draft)
saveCurrentDraft(action.text)
room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.REPLY(timelineEvent, action.text)) }
withState { state ->
// Save a new draft and keep the previously entered text, if it was not an edit
timelineEvent.root.eventId?.let {
if (state.sendMode is SendMode.EDIT) {
room.saveDraft(UserDraft.REPLY(it, ""))
} else {
room.saveDraft(UserDraft.REPLY(it, action.draft))
room.saveDraft(UserDraft.REPLY(it, action.text))
}
}
}
@ -579,13 +582,14 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
}
private fun handleExitSpecialMode(action: RoomDetailAction.ExitSpecialMode) {
setState { copy(sendMode = SendMode.REGULAR(action.text)) }
withState { state ->
// For edit, just delete the current draft
if (state.sendMode is SendMode.EDIT) {
room.deleteDraft()
} else {
// Save a new draft and keep the previously entered text
room.saveDraft(UserDraft.REGULAR(action.draft))
room.saveDraft(UserDraft.REGULAR(action.text))
}
}
}

View File

@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail.composer
import android.content.Context
import android.net.Uri
import android.text.Editable
import android.util.AttributeSet
import android.view.ViewGroup
import android.widget.ImageButton
@ -31,6 +32,7 @@ import androidx.transition.TransitionManager
import butterknife.BindView
import butterknife.ButterKnife
import im.vector.riotx.R
import kotlinx.android.synthetic.main.merge_composer_layout.view.*
/**
* Encapsulate the timeline composer UX.
@ -39,7 +41,11 @@ import im.vector.riotx.R
class TextComposerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
defStyleAttr: Int = 0) : ConstraintLayout(context, attrs, defStyleAttr) {
interface Callback : ComposerEditText.Callback
interface Callback : ComposerEditText.Callback {
fun onCloseRelatedMessage()
fun onSendMessage(text: String)
fun onAddAttachment()
}
var callback: Callback? = null
@ -62,15 +68,31 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib
private val animationDuration = 100L
val text: Editable?
get() = composerEditText.text
init {
inflate(context, R.layout.merge_composer_layout, this)
ButterKnife.bind(this)
collapse(false)
composerEditText.callback = object : Callback, ComposerEditText.Callback {
composerEditText.callback = object : ComposerEditText.Callback {
override fun onRichContentSelected(contentUri: Uri): Boolean {
return callback?.onRichContentSelected(contentUri) ?: false
}
}
composerRelatedMessageCloseButton.setOnClickListener {
collapse()
callback?.onCloseRelatedMessage()
}
sendButton.setOnClickListener {
val textMessage = text?.toString() ?: ""
callback?.onSendMessage(textMessage)
}
attachmentButton.setOnClickListener {
callback?.onAddAttachment()
}
}
fun collapse(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) {

View File

@ -139,6 +139,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
override fun unbind(holder: H) {
holder.readMarkerView.unbind()
holder.readReceiptsView.unbind()
super.unbind(holder)
}

View File

@ -24,6 +24,7 @@ import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
import im.vector.riotx.core.glide.GlideApp
import im.vector.riotx.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import im.vector.riotx.features.media.ImageContentRenderer
@ -60,6 +61,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
}
override fun unbind(holder: Holder) {
GlideApp.with(holder.view.context.applicationContext).clear(holder.imageView)
contentUploadStateTrackerBinder.unbind(attributes.informationData.eventId)
super.unbind(holder)
}

View File

@ -24,6 +24,7 @@ import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.extensions.replaceFragment
import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.features.home.RoomListDisplayMode
import im.vector.riotx.features.home.room.list.RoomListFragment
import im.vector.riotx.features.home.room.list.RoomListParams
import kotlinx.android.synthetic.main.activity_filtered_rooms.*
@ -47,7 +48,7 @@ class FilteredRoomsActivity : VectorBaseActivity() {
super.onCreate(savedInstanceState)
configureToolbar(filteredRoomsToolbar)
if (isFirstCreation()) {
val params = RoomListParams(RoomListFragment.DisplayMode.FILTERED)
val params = RoomListParams(RoomListDisplayMode.FILTERED)
replaceFragment(R.id.filteredRoomsFragmentContainer, RoomListFragment::class.java, params, FRAGMENT_TAG)
}
filteredRoomsSearchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {

View File

@ -18,21 +18,22 @@ package im.vector.riotx.features.home.room.list
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotx.features.home.RoomListDisplayMode
import io.reactivex.functions.Predicate
class RoomListDisplayModeFilter(private val displayMode: RoomListFragment.DisplayMode) : Predicate<RoomSummary> {
class RoomListDisplayModeFilter(private val displayMode: RoomListDisplayMode) : Predicate<RoomSummary> {
override fun test(roomSummary: RoomSummary): Boolean {
if (roomSummary.membership.isLeft()) {
return false
}
return when (displayMode) {
RoomListFragment.DisplayMode.HOME ->
RoomListDisplayMode.HOME ->
roomSummary.notificationCount > 0 || roomSummary.membership == Membership.INVITE || roomSummary.userDrafts.isNotEmpty()
RoomListFragment.DisplayMode.PEOPLE -> roomSummary.isDirect && roomSummary.membership == Membership.JOIN
RoomListFragment.DisplayMode.ROOMS -> !roomSummary.isDirect && roomSummary.membership == Membership.JOIN
RoomListFragment.DisplayMode.FILTERED -> roomSummary.membership == Membership.JOIN
RoomListFragment.DisplayMode.SHARE -> roomSummary.membership == Membership.JOIN
RoomListDisplayMode.PEOPLE -> roomSummary.isDirect && roomSummary.membership == Membership.JOIN
RoomListDisplayMode.ROOMS -> !roomSummary.isDirect && roomSummary.membership == Membership.JOIN
RoomListDisplayMode.FILTERED -> roomSummary.membership == Membership.JOIN
RoomListDisplayMode.SHARE -> roomSummary.membership == Membership.JOIN
}
}
}

View File

@ -20,7 +20,7 @@ import android.os.Bundle
import android.os.Parcelable
import android.view.Menu
import android.view.MenuItem
import androidx.annotation.StringRes
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
@ -38,8 +38,10 @@ import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.platform.OnBackPressed
import im.vector.riotx.core.platform.StateView
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import im.vector.riotx.features.home.RoomListDisplayMode
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedAction
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
import im.vector.riotx.features.home.room.list.widget.FabMenuView
import im.vector.riotx.features.notifications.NotificationDrawerManager
@ -51,7 +53,7 @@ import javax.inject.Inject
@Parcelize
data class RoomListParams(
val displayMode: RoomListFragment.DisplayMode,
val displayMode: RoomListDisplayMode,
val sharedData: SharedData? = null
) : Parcelable
@ -63,14 +65,6 @@ class RoomListFragment @Inject constructor(
) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, FabMenuView.Listener {
enum class DisplayMode(@StringRes val titleRes: Int) {
HOME(R.string.bottom_action_home),
PEOPLE(R.string.bottom_action_people_x),
ROOMS(R.string.bottom_action_rooms),
FILTERED(/* Not used */ 0),
SHARE(/* Not used */ 0)
}
private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel
private val roomListParams: RoomListParams by args()
private val roomListViewModel: RoomListViewModel by fragmentViewModel()
@ -97,13 +91,13 @@ class RoomListFragment @Inject constructor(
super.onPrepareOptionsMenu(menu)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupCreateRoomButton()
setupRecyclerView()
roomListViewModel.subscribe { renderState(it) }
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
roomListViewModel.subscribe { renderState(it) }
roomListViewModel.viewEvents
.observe()
.observeOn(AndroidSchedulers.mainThread())
@ -113,18 +107,23 @@ class RoomListFragment @Inject constructor(
is RoomListViewEvents.Failure -> showError(it)
}
}
.disposeOnDestroy()
.disposeOnDestroyView()
createChatFabMenu.listener = this
sharedActionViewModel
.observe()
.subscribe { handleQuickActions(it) }
.disposeOnDestroy()
.disposeOnDestroyView()
}
override fun onDestroyView() {
super.onDestroyView()
roomListView.adapter = null
}
private fun openSelectedRoom(event: RoomListViewEvents.SelectRoom) {
if (roomListParams.displayMode == DisplayMode.SHARE) {
if (roomListParams.displayMode == RoomListDisplayMode.SHARE) {
val sharedData = roomListParams.sharedData ?: return
navigator.openRoomForSharing(requireActivity(), event.roomId, sharedData)
} else {
@ -141,9 +140,9 @@ class RoomListFragment @Inject constructor(
private fun setupCreateRoomButton() {
when (roomListParams.displayMode) {
DisplayMode.HOME -> createChatFabMenu.isVisible = true
DisplayMode.PEOPLE -> createChatRoomButton.isVisible = true
DisplayMode.ROOMS -> createGroupRoomButton.isVisible = true
RoomListDisplayMode.HOME -> createChatFabMenu.isVisible = true
RoomListDisplayMode.PEOPLE -> createChatRoomButton.isVisible = true
RoomListDisplayMode.ROOMS -> createGroupRoomButton.isVisible = true
else -> Unit // No button in this mode
}
@ -155,7 +154,7 @@ class RoomListFragment @Inject constructor(
}
// Hide FAB when list is scrolling
roomListEpoxyRecyclerView.addOnScrollListener(
roomListView.addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
createChatFabMenu.removeCallbacks(showFabRunnable)
@ -167,9 +166,9 @@ class RoomListFragment @Inject constructor(
RecyclerView.SCROLL_STATE_DRAGGING,
RecyclerView.SCROLL_STATE_SETTLING -> {
when (roomListParams.displayMode) {
DisplayMode.HOME -> createChatFabMenu.hide()
DisplayMode.PEOPLE -> createChatRoomButton.hide()
DisplayMode.ROOMS -> createGroupRoomButton.hide()
RoomListDisplayMode.HOME -> createChatFabMenu.hide()
RoomListDisplayMode.PEOPLE -> createChatRoomButton.hide()
RoomListDisplayMode.ROOMS -> createGroupRoomButton.hide()
else -> Unit
}
}
@ -180,7 +179,7 @@ class RoomListFragment @Inject constructor(
fun filterRoomsWith(filter: String) {
// Scroll the list to top
roomListEpoxyRecyclerView.scrollToPosition(0)
roomListView.scrollToPosition(0)
roomListViewModel.handle(RoomListAction.FilterWith(filter))
}
@ -196,20 +195,20 @@ class RoomListFragment @Inject constructor(
private fun setupRecyclerView() {
val layoutManager = LinearLayoutManager(context)
val stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
roomListEpoxyRecyclerView.layoutManager = layoutManager
roomListEpoxyRecyclerView.itemAnimator = RoomListAnimator()
roomListView.layoutManager = layoutManager
roomListView.itemAnimator = RoomListAnimator()
roomController.listener = this
roomController.addModelBuildListener { it.dispatchTo(stateRestorer) }
stateView.contentView = roomListEpoxyRecyclerView
roomListEpoxyRecyclerView.setController(roomController)
roomListView.adapter = roomController.adapter
stateView.contentView = roomListView
}
private val showFabRunnable = Runnable {
if (isAdded) {
when (roomListParams.displayMode) {
DisplayMode.HOME -> createChatFabMenu.show()
DisplayMode.PEOPLE -> createChatRoomButton.show()
DisplayMode.ROOMS -> createGroupRoomButton.show()
RoomListDisplayMode.HOME -> createChatFabMenu.show()
RoomListDisplayMode.PEOPLE -> createChatRoomButton.show()
RoomListDisplayMode.ROOMS -> createGroupRoomButton.show()
else -> Unit
}
}
@ -255,9 +254,9 @@ class RoomListFragment @Inject constructor(
// Mark all as read menu
when (roomListParams.displayMode) {
DisplayMode.HOME,
DisplayMode.PEOPLE,
DisplayMode.ROOMS -> {
RoomListDisplayMode.HOME,
RoomListDisplayMode.PEOPLE,
RoomListDisplayMode.ROOMS -> {
val newValue = state.hasUnread
if (hasUnreadRooms != newValue) {
hasUnreadRooms = newValue
@ -285,7 +284,7 @@ class RoomListFragment @Inject constructor(
}
.isNullOrEmpty()
val emptyState = when (roomListParams.displayMode) {
DisplayMode.HOME -> {
RoomListDisplayMode.HOME -> {
if (hasNoRoom) {
StateView.State.Empty(
getString(R.string.room_list_catchup_welcome_title),
@ -299,13 +298,13 @@ class RoomListFragment @Inject constructor(
getString(R.string.room_list_catchup_empty_body))
}
}
DisplayMode.PEOPLE ->
RoomListDisplayMode.PEOPLE ->
StateView.State.Empty(
getString(R.string.room_list_people_empty_title),
ContextCompat.getDrawable(requireContext(), R.drawable.ic_home_bottom_chat),
getString(R.string.room_list_people_empty_body)
)
DisplayMode.ROOMS ->
RoomListDisplayMode.ROOMS ->
StateView.State.Empty(
getString(R.string.room_list_rooms_empty_title),
ContextCompat.getDrawable(requireContext(), R.drawable.ic_home_bottom_group),

View File

@ -33,9 +33,7 @@ import javax.inject.Inject
class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
private val session: Session,
private val roomSummariesSource: DataSource<List<RoomSummary>>,
private val alphabeticalRoomComparator: AlphabeticalRoomComparator,
private val chronologicalRoomComparator: ChronologicalRoomComparator)
private val roomSummariesSource: DataSource<List<RoomSummary>>)
: VectorViewModel<RoomListViewState, RoomListAction>(initialState) {
interface Factory {
@ -96,9 +94,6 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
roomSummariesSource
.observe()
.observeOn(Schedulers.computation())
.map {
it.sortedWith(chronologicalRoomComparator)
}
.execute { asyncRooms ->
copy(asyncRooms = asyncRooms)
}
@ -210,10 +205,11 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
}
private fun buildRoomSummaries(rooms: List<RoomSummary>): RoomSummaries {
// Set up init size on directChats and groupRooms as they are the biggest ones
val invites = ArrayList<RoomSummary>()
val favourites = ArrayList<RoomSummary>()
val directChats = ArrayList<RoomSummary>()
val groupRooms = ArrayList<RoomSummary>()
val directChats = ArrayList<RoomSummary>(rooms.size)
val groupRooms = ArrayList<RoomSummary>(rooms.size)
val lowPriorities = ArrayList<RoomSummary>()
val serverNotices = ArrayList<RoomSummary>()
@ -231,21 +227,13 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
}
}
val roomComparator = when (displayMode) {
RoomListFragment.DisplayMode.HOME -> chronologicalRoomComparator
RoomListFragment.DisplayMode.PEOPLE -> chronologicalRoomComparator
RoomListFragment.DisplayMode.ROOMS -> chronologicalRoomComparator
RoomListFragment.DisplayMode.FILTERED -> chronologicalRoomComparator
RoomListFragment.DisplayMode.SHARE -> chronologicalRoomComparator
}
return RoomSummaries().apply {
put(RoomCategory.INVITE, invites.sortedWith(roomComparator))
put(RoomCategory.FAVOURITE, favourites.sortedWith(roomComparator))
put(RoomCategory.DIRECT, directChats.sortedWith(roomComparator))
put(RoomCategory.GROUP, groupRooms.sortedWith(roomComparator))
put(RoomCategory.LOW_PRIORITY, lowPriorities.sortedWith(roomComparator))
put(RoomCategory.SERVER_NOTICE, serverNotices.sortedWith(roomComparator))
put(RoomCategory.INVITE, invites)
put(RoomCategory.FAVOURITE, favourites)
put(RoomCategory.DIRECT, directChats)
put(RoomCategory.GROUP, groupRooms)
put(RoomCategory.LOW_PRIORITY, lowPriorities)
put(RoomCategory.SERVER_NOTICE, serverNotices)
}
}
}

View File

@ -18,22 +18,21 @@ package im.vector.riotx.features.home.room.list
import im.vector.matrix.android.api.session.Session
import im.vector.riotx.features.home.HomeRoomListDataSource
import im.vector.riotx.features.home.RoomListDisplayMode
import im.vector.riotx.features.share.ShareRoomListDataSource
import javax.inject.Inject
import javax.inject.Provider
class RoomListViewModelFactory @Inject constructor(private val session: Provider<Session>,
private val homeRoomListDataSource: Provider<HomeRoomListDataSource>,
private val shareRoomListDataSource: Provider<ShareRoomListDataSource>,
private val alphabeticalRoomComparator: Provider<AlphabeticalRoomComparator>,
private val chronologicalRoomComparator: Provider<ChronologicalRoomComparator>) : RoomListViewModel.Factory {
private val shareRoomListDataSource: Provider<ShareRoomListDataSource>)
: RoomListViewModel.Factory {
override fun create(initialState: RoomListViewState): RoomListViewModel {
return RoomListViewModel(
initialState,
session.get(),
if (initialState.displayMode == RoomListFragment.DisplayMode.SHARE) shareRoomListDataSource.get() else homeRoomListDataSource.get(),
alphabeticalRoomComparator.get(),
chronologicalRoomComparator.get())
if (initialState.displayMode == RoomListDisplayMode.SHARE) shareRoomListDataSource.get() else homeRoomListDataSource.get()
)
}
}

View File

@ -23,9 +23,10 @@ import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotx.R
import im.vector.riotx.features.home.RoomListDisplayMode
data class RoomListViewState(
val displayMode: RoomListFragment.DisplayMode,
val displayMode: RoomListDisplayMode,
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
val roomFilter: String = "",
val asyncFilteredRooms: Async<RoomSummaries> = Uninitialized,

View File

@ -24,6 +24,7 @@ import im.vector.riotx.R
import im.vector.riotx.core.epoxy.helpFooterItem
import im.vector.riotx.core.epoxy.noResultItem
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.RoomListDisplayMode
import im.vector.riotx.core.resources.UserPreferencesProvider
import im.vector.riotx.features.home.room.filtered.FilteredRoomFooterItem
import im.vector.riotx.features.home.room.filtered.filteredRoomFooterItem
@ -58,8 +59,8 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
override fun buildModels() {
val nonNullViewState = viewState ?: return
when (nonNullViewState.displayMode) {
RoomListFragment.DisplayMode.FILTERED,
RoomListFragment.DisplayMode.SHARE -> {
RoomListDisplayMode.FILTERED,
RoomListDisplayMode.SHARE -> {
buildFilteredRooms(nonNullViewState)
}
else -> {
@ -106,7 +107,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
viewState.rejectingErrorRoomsIds)
when {
viewState.displayMode == RoomListFragment.DisplayMode.FILTERED -> addFilterFooter(viewState)
viewState.displayMode == RoomListDisplayMode.FILTERED -> addFilterFooter(viewState)
filteredSummaries.isEmpty() -> addEmptyFooter()
}
}

View File

@ -62,7 +62,7 @@ class LoginFragment @Inject constructor() : VectorBaseFragment() {
viewModel.handle(LoginAction.UpdateHomeServer(homeServerField.text.toString()))
}
}
.disposeOnDestroy()
.disposeOnDestroyView()
homeServerField.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
@ -107,7 +107,7 @@ class LoginFragment @Inject constructor() : VectorBaseFragment() {
}
)
.subscribeBy { authenticateButton.isEnabled = it }
.disposeOnDestroy()
.disposeOnDestroyView()
authenticateButton.setOnClickListener { authenticate() }
authenticateButtonSso.setOnClickListener { openSso() }

View File

@ -116,7 +116,6 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
private fun onSessionCreated(session: Session) {
activeSessionHolder.setActiveSession(session)
session.configureAndStart(pushRuleTriggerListener, sessionListener)
setState {
copy(
asyncLoginAction = Success(Unit)
@ -131,9 +130,15 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
// Should not happen
Timber.w("homeServerConnectionConfig is null")
} else {
val session = authenticator.createSessionFromSso(action.credentials, homeServerConnectionConfigFinal)
authenticator.createSessionFromSso(action.credentials, homeServerConnectionConfigFinal, object : MatrixCallback<Session> {
override fun onSuccess(data: Session) {
onSessionCreated(data)
}
onSessionCreated(session)
override fun onFailure(failure: Throwable) = setState {
copy(asyncLoginAction = Fail(failure))
}
})
}
}
@ -149,7 +154,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
// Do not retry if we already have flows for this config -> causes infinite focus loop
if (newConfig?.homeServerUri?.toString() == homeServerConnectionConfig?.homeServerUri?.toString()
&& state.asyncHomeServerLoginFlowRequest is Success) return@withState
&& state.asyncHomeServerLoginFlowRequest is Success) return@withState
currentTask?.cancel()
homeServerConnectionConfig = newConfig

View File

@ -16,6 +16,7 @@
package im.vector.riotx.features.reactions
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseFragment
@ -27,8 +28,8 @@ class EmojiChooserFragment @Inject constructor() : VectorBaseFragment() {
private lateinit var viewModel: EmojiChooserViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = activityViewModelProvider.get(EmojiChooserViewModel::class.java)
viewModel.initWithContext(context!!)
(view as? RecyclerView)?.let {

View File

@ -16,6 +16,7 @@
package im.vector.riotx.features.reactions
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -37,9 +38,8 @@ class EmojiSearchResultFragment @Inject constructor(
var sharedViewModel: EmojiChooserViewModel? = null
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedViewModel = activityViewModelProvider.get(EmojiChooserViewModel::class.java)
epoxyController.listener = object : ReactionClickListener {

View File

@ -67,7 +67,7 @@ class PublicRoomsFragment @Inject constructor(
.subscribeBy {
viewModel.handle(RoomDirectoryAction.FilterWith(it.toString()))
}
.disposeOnDestroy()
.disposeOnDestroyView()
publicRoomsCreateNewRoom.setOnClickListener {
sharedActionViewModel.post(RoomDirectorySharedAction.CreateRoom)

View File

@ -18,6 +18,7 @@ package im.vector.riotx.features.roomdirectory.createroom
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.activityViewModel
@ -39,8 +40,8 @@ class CreateRoomFragment @Inject constructor(private val createRoomController: C
override fun getMenuRes() = R.menu.vector_room_creation
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
vectorBaseActivity.setSupportActionBar(createRoomToolbar)
sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
setupRecyclerView()

View File

@ -55,6 +55,9 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie
it.setDisplayShowHomeEnabled(true)
it.setDisplayHomeAsUpEnabled(true)
}
sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
setupRecyclerView()
}
override fun getMenuRes() = R.menu.menu_directory_server_picker
@ -69,12 +72,6 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie
return super.onOptionsItemSelected(item)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
setupRecyclerView()
}
private fun setupRecyclerView() {
val layoutManager = LinearLayoutManager(context)

View File

@ -45,16 +45,11 @@ class RoomPreviewNoPreviewFragment @Inject constructor(
private val roomPreviewViewModel: RoomPreviewViewModel by fragmentViewModel()
private val roomPreviewData: RoomPreviewData by args()
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setupToolbar(roomPreviewNoPreviewToolbar)
}
override fun getLayoutResId() = R.layout.fragment_room_preview_no_preview
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupToolbar(roomPreviewNoPreviewToolbar)
// Toolbar
avatarRenderer.render(roomPreviewData.avatarUrl, roomPreviewData.roomId, roomPreviewData.roomName, roomPreviewNoPreviewToolbarAvatar)
roomPreviewNoPreviewToolbarTitle.text = roomPreviewData.roomName

View File

@ -31,6 +31,7 @@ import im.vector.riotx.core.extensions.replaceFragment
import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.features.attachments.AttachmentsHelper
import im.vector.riotx.features.home.LoadingFragment
import im.vector.riotx.features.home.RoomListDisplayMode
import im.vector.riotx.features.home.room.list.RoomListFragment
import im.vector.riotx.features.home.room.list.RoomListParams
import im.vector.riotx.features.login.LoginActivity
@ -97,7 +98,7 @@ class IncomingShareActivity :
}
override fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>) {
val roomListParams = RoomListParams(RoomListFragment.DisplayMode.SHARE, sharedData = SharedData.Attachments(attachments))
val roomListParams = RoomListParams(RoomListDisplayMode.SHARE, sharedData = SharedData.Attachments(attachments))
replaceFragment(R.id.shareRoomListFragmentContainer, RoomListFragment::class.java, roomListParams)
}
@ -116,7 +117,7 @@ class IncomingShareActivity :
return if (sharedText.isNullOrEmpty()) {
false
} else {
val roomListParams = RoomListParams(RoomListFragment.DisplayMode.SHARE, sharedData = SharedData.Text(sharedText))
val roomListParams = RoomListParams(RoomListDisplayMode.SHARE, sharedData = SharedData.Text(sharedText))
replaceFragment(R.id.shareRoomListFragmentContainer, RoomListFragment::class.java, roomListParams)
true
}

View File

@ -18,7 +18,7 @@ package im.vector.riotx.features.ui
import android.content.SharedPreferences
import androidx.core.content.edit
import im.vector.riotx.features.home.room.list.RoomListFragment
import im.vector.riotx.features.home.RoomListDisplayMode
import javax.inject.Inject
/**
@ -26,21 +26,21 @@ import javax.inject.Inject
*/
class SharedPreferencesUiStateRepository @Inject constructor(private val sharedPreferences: SharedPreferences) : UiStateRepository {
override fun getDisplayMode(): RoomListFragment.DisplayMode {
override fun getDisplayMode(): RoomListDisplayMode {
return when (sharedPreferences.getInt(KEY_DISPLAY_MODE, VALUE_DISPLAY_MODE_CATCHUP)) {
VALUE_DISPLAY_MODE_PEOPLE -> RoomListFragment.DisplayMode.PEOPLE
VALUE_DISPLAY_MODE_ROOMS -> RoomListFragment.DisplayMode.ROOMS
else -> RoomListFragment.DisplayMode.HOME
VALUE_DISPLAY_MODE_PEOPLE -> RoomListDisplayMode.PEOPLE
VALUE_DISPLAY_MODE_ROOMS -> RoomListDisplayMode.ROOMS
else -> RoomListDisplayMode.HOME
}
}
override fun storeDisplayMode(displayMode: RoomListFragment.DisplayMode) {
override fun storeDisplayMode(displayMode: RoomListDisplayMode) {
sharedPreferences.edit {
putInt(KEY_DISPLAY_MODE,
when (displayMode) {
RoomListFragment.DisplayMode.PEOPLE -> VALUE_DISPLAY_MODE_PEOPLE
RoomListFragment.DisplayMode.ROOMS -> VALUE_DISPLAY_MODE_ROOMS
else -> VALUE_DISPLAY_MODE_CATCHUP
RoomListDisplayMode.PEOPLE -> VALUE_DISPLAY_MODE_PEOPLE
RoomListDisplayMode.ROOMS -> VALUE_DISPLAY_MODE_ROOMS
else -> VALUE_DISPLAY_MODE_CATCHUP
})
}
}

View File

@ -16,14 +16,14 @@
package im.vector.riotx.features.ui
import im.vector.riotx.features.home.room.list.RoomListFragment
import im.vector.riotx.features.home.RoomListDisplayMode
/**
* This interface is used to persist UI state across application restart
*/
interface UiStateRepository {
fun getDisplayMode(): RoomListFragment.DisplayMode
fun getDisplayMode(): RoomListDisplayMode
fun storeDisplayMode(displayMode: RoomListFragment.DisplayMode)
fun storeDisplayMode(displayMode: RoomListDisplayMode)
}

View File

@ -90,10 +90,8 @@
<ImageButton
android:id="@+id/composer_related_message_close"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_close_round"
android:tint="@color/riotx_notice"

View File

@ -7,8 +7,8 @@
android:layout_height="match_parent"
android:background="?riotx_header_panel_background">
<com.airbnb.epoxy.EpoxyRecyclerView
android:id="@+id/roomListEpoxyRecyclerView"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/roomListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="always" />
@ -30,7 +30,7 @@
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:accessibilityTraversalBefore="@+id/roomListEpoxyRecyclerView"
android:accessibilityTraversalBefore="@+id/roomListView"
android:contentDescription="@string/a11y_create_direct_message"
android:scaleType="center"
android:src="@drawable/ic_fab_add_chat"
@ -47,7 +47,7 @@
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:accessibilityTraversalBefore="@+id/roomListEpoxyRecyclerView"
android:accessibilityTraversalBefore="@+id/roomListView"
android:contentDescription="@string/a11y_create_room"
android:src="@drawable/ic_fab_add_room"
android:visibility="gone"

View File

@ -24,7 +24,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:accessibilityTraversalBefore="@+id/roomListEpoxyRecyclerView"
android:accessibilityTraversalBefore="@+id/roomListView"
android:contentDescription="@string/a11y_create_room"
android:src="@drawable/ic_fab_add_room"
app:backgroundTint="#FFFFFF"