Merge branch 'feature/invites' into develop

This commit is contained in:
ganfra 2019-05-17 17:38:09 +02:00
commit 2780ca30a8
56 changed files with 665 additions and 219 deletions

View File

@ -22,7 +22,6 @@ import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.MyMembership
import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.message.MessageType
@ -78,7 +77,7 @@ object RoomDataHelper {
fun fakeInitialSync(monarchy: Monarchy, roomId: String) { fun fakeInitialSync(monarchy: Monarchy, roomId: String) {
monarchy.runTransactionSync { realm -> monarchy.runTransactionSync { realm ->
val roomEntity = realm.createObject<RoomEntity>(roomId) val roomEntity = realm.createObject<RoomEntity>(roomId)
roomEntity.membership = MyMembership.JOINED roomEntity.membership = Membership.JOIN
val eventList = createFakeListOfEvents(10) val eventList = createFakeListOfEvents(10)
val chunkEntity = realm.createObject<ChunkEntity>().apply { val chunkEntity = realm.createObject<ChunkEntity>().apply {
nextToken = null nextToken = null

View File

@ -21,7 +21,7 @@ import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.session.room.EventRelationExtractor import im.vector.matrix.android.internal.session.room.EventRelationExtractor
import im.vector.matrix.android.internal.session.room.members.SenderRoomMemberExtractor import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimeline import im.vector.matrix.android.internal.session.room.timeline.DefaultTimeline
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor

View File

@ -17,7 +17,7 @@
package im.vector.matrix.android.api.session.room package im.vector.matrix.android.api.session.room


import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.session.room.members.RoomMembersService import im.vector.matrix.android.api.session.room.members.MembershipService
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.read.ReadService import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.api.session.room.send.SendService import im.vector.matrix.android.api.session.room.send.SendService
@ -27,7 +27,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineService
/** /**
* This interface defines methods to interact within a room. * This interface defines methods to interact within a room.
*/ */
interface Room : TimelineService, SendService, ReadService, RoomMembersService, StateService { interface Room : TimelineService, SendService, ReadService, MembershipService, StateService {


/** /**
* The roomId of this room * The roomId of this room

View File

@ -24,9 +24,9 @@ import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable


/** /**
* This interface defines methods to retrieve room members of a room. It's implemented at the room level. * This interface defines methods to handling membership. It's implemented at the room level.
*/ */
interface RoomMembersService { interface MembershipService {


/** /**
* This methods load all room members if it was done yet. * This methods load all room members if it was done yet.
@ -54,4 +54,15 @@ interface RoomMembersService {
*/ */
fun invite(userId: String, callback: MatrixCallback<Unit>) fun invite(userId: String, callback: MatrixCallback<Unit>)


/**
* Join the room
*/
fun join(callback: MatrixCallback<Unit>)

/**
* Leave the room.
*
*/
fun leave(callback: MatrixCallback<Unit>)

} }

View File

@ -19,10 +19,12 @@ package im.vector.matrix.android.api.session.room.model
import com.squareup.moshi.Json import com.squareup.moshi.Json


/** /**
* Represents the membership of a user on a room. Linked to a [RoomMember] * Represents the membership of a user on a room
*/ */
enum class Membership(val value: String) { enum class Membership(val value: String) {


NONE("none"),

@Json(name = "invite") @Json(name = "invite")
INVITE("invite"), INVITE("invite"),


@ -38,4 +40,8 @@ enum class Membership(val value: String) {
@Json(name = "ban") @Json(name = "ban")
BAN("ban"); BAN("ban");


fun isLeft(): Boolean {
return this == KNOCK || this == LEAVE || this == BAN
}

} }

View File

@ -1,27 +0,0 @@
/*
* 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.matrix.android.api.session.room.model

/**
* Represents the membership of the current auth user on a room.
*/
enum class MyMembership {
JOINED,
LEFT,
INVITED,
NONE
}

View File

@ -31,7 +31,8 @@ data class RoomSummary(
val isDirect: Boolean = false, val isDirect: Boolean = false,
val lastMessage: Event? = null, val lastMessage: Event? = null,
val otherMemberIds: List<String> = emptyList(), val otherMemberIds: List<String> = emptyList(),
var notificationCount: Int = 0, val notificationCount: Int = 0,
var highlightCount: Int = 0, val highlightCount: Int = 0,
var tags: List<RoomTag> = emptyList() val tags: List<RoomTag> = emptyList(),
val membership: Membership = Membership.NONE
) )

View File

@ -86,10 +86,9 @@ internal fun ChunkEntity.add(roomId: String,
isUnlinked: Boolean = false) { isUnlinked: Boolean = false) {


assertIsManaged() assertIsManaged()
if (event.eventId.isNullOrEmpty() || this.events.fastContains(event.eventId)) { if (event.eventId != null && events.fastContains(event.eventId)) {
return return
} }

var currentDisplayIndex = lastDisplayIndex(direction, 0) var currentDisplayIndex = lastDisplayIndex(direction, 0)
if (direction == PaginationDirection.FORWARDS) { if (direction == PaginationDirection.FORWARDS) {
currentDisplayIndex += 1 currentDisplayIndex += 1

View File

@ -20,7 +20,7 @@ import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.UnsignedData import im.vector.matrix.android.api.session.events.model.UnsignedData
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider

import java.util.*


internal object EventMapper { internal object EventMapper {


@ -29,7 +29,7 @@ internal object EventMapper {
val uds = if (event.unsignedData == null) null val uds = if (event.unsignedData == null) null
else MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(event.unsignedData) else MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(event.unsignedData)
val eventEntity = EventEntity() val eventEntity = EventEntity()
eventEntity.eventId = event.eventId ?: "" eventEntity.eventId = event.eventId ?: UUID.randomUUID().toString()
eventEntity.roomId = event.roomId ?: roomId eventEntity.roomId = event.roomId ?: roomId
eventEntity.content = ContentMapper.map(event.content) eventEntity.content = ContentMapper.map(event.content)
val resolvedPrevContent = event.prevContent ?: event.unsignedData?.prevContent val resolvedPrevContent = event.prevContent ?: event.unsignedData?.prevContent

View File

@ -37,7 +37,8 @@ internal object RoomSummaryMapper {
otherMemberIds = roomSummaryEntity.otherMemberIds.toList(), otherMemberIds = roomSummaryEntity.otherMemberIds.toList(),
highlightCount = roomSummaryEntity.highlightCount, highlightCount = roomSummaryEntity.highlightCount,
notificationCount = roomSummaryEntity.notificationCount, notificationCount = roomSummaryEntity.notificationCount,
tags = tags tags = tags,
membership = roomSummaryEntity.membership
) )
} }
} }

View File

@ -16,7 +16,7 @@


package im.vector.matrix.android.internal.database.model package im.vector.matrix.android.internal.database.model


import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.Membership
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.Ignore import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
@ -26,10 +26,10 @@ internal open class GroupEntity(@PrimaryKey var groupId: String = ""


) : RealmObject() { ) : RealmObject() {


private var membershipStr: String = MyMembership.NONE.name private var membershipStr: String = Membership.NONE.name


@delegate:Ignore @delegate:Ignore
var membership: MyMembership by Delegates.observable(MyMembership.valueOf(membershipStr)) { _, _, newValue -> var membership: Membership by Delegates.observable(Membership.valueOf(membershipStr)) { _, _, newValue ->
membershipStr = newValue.name membershipStr = newValue.name
} }



View File

@ -16,7 +16,7 @@


package im.vector.matrix.android.internal.database.model package im.vector.matrix.android.internal.database.model


import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.Membership
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.Ignore import io.realm.annotations.Ignore
@ -30,10 +30,10 @@ internal open class RoomEntity(@PrimaryKey var roomId: String = "",
var areAllMembersLoaded: Boolean = false var areAllMembersLoaded: Boolean = false
) : RealmObject() { ) : RealmObject() {


private var membershipStr: String = MyMembership.NONE.name private var membershipStr: String = Membership.NONE.name


@delegate:Ignore @delegate:Ignore
var membership: MyMembership by Delegates.observable(MyMembership.valueOf(membershipStr)) { _, _, newValue -> var membership: Membership by Delegates.observable(Membership.valueOf(membershipStr)) { _, _, newValue ->
membershipStr = newValue.name membershipStr = newValue.name
} }



View File

@ -16,9 +16,12 @@


package im.vector.matrix.android.internal.database.model package im.vector.matrix.android.internal.database.model


import im.vector.matrix.android.api.session.room.model.Membership
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import kotlin.properties.Delegates


internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "", internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
var displayName: String? = "", var displayName: String? = "",
@ -35,6 +38,13 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
var tags: RealmList<RoomTagEntity> = RealmList() var tags: RealmList<RoomTagEntity> = RealmList()
) : RealmObject() { ) : RealmObject() {


private var membershipStr: String = Membership.NONE.name

@delegate:Ignore
var membership: Membership by Delegates.observable(Membership.valueOf(membershipStr)) { _, _, newValue ->
membershipStr = newValue.name
}

companion object companion object


} }

View File

@ -16,7 +16,7 @@


package im.vector.matrix.android.internal.database.query package im.vector.matrix.android.internal.database.query


import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.internal.database.model.GroupEntity import im.vector.matrix.android.internal.database.model.GroupEntity
import im.vector.matrix.android.internal.database.model.GroupEntityFields import im.vector.matrix.android.internal.database.model.GroupEntityFields
import io.realm.Realm import io.realm.Realm
@ -27,7 +27,7 @@ internal fun GroupEntity.Companion.where(realm: Realm, roomId: String): RealmQue
return realm.where<GroupEntity>().equalTo(GroupEntityFields.GROUP_ID, roomId) return realm.where<GroupEntity>().equalTo(GroupEntityFields.GROUP_ID, roomId)
} }


internal fun GroupEntity.Companion.where(realm: Realm, membership: MyMembership? = null): RealmQuery<GroupEntity> { internal fun GroupEntity.Companion.where(realm: Realm, membership: Membership? = null): RealmQuery<GroupEntity> {
val query = realm.where<GroupEntity>() val query = realm.where<GroupEntity>()
if (membership != null) { if (membership != null) {
query.equalTo(GroupEntityFields.MEMBERSHIP_STR, membership.name) query.equalTo(GroupEntityFields.MEMBERSHIP_STR, membership.name)

View File

@ -16,7 +16,7 @@


package im.vector.matrix.android.internal.database.query package im.vector.matrix.android.internal.database.query


import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.RoomEntityFields import im.vector.matrix.android.internal.database.model.RoomEntityFields
@ -28,7 +28,7 @@ internal fun RoomEntity.Companion.where(realm: Realm, roomId: String): RealmQuer
return realm.where<RoomEntity>().equalTo(RoomEntityFields.ROOM_ID, roomId) return realm.where<RoomEntity>().equalTo(RoomEntityFields.ROOM_ID, roomId)
} }


internal fun RoomEntity.Companion.where(realm: Realm, membership: MyMembership? = null): RealmQuery<RoomEntity> { internal fun RoomEntity.Companion.where(realm: Realm, membership: Membership? = null): RealmQuery<RoomEntity> {
val query = realm.where<RoomEntity>() val query = realm.where<RoomEntity>()
if (membership != null) { if (membership != null) {
query.equalTo(RoomEntityFields.MEMBERSHIP_STR, membership.name) query.equalTo(RoomEntityFields.MEMBERSHIP_STR, membership.name)

View File

@ -37,8 +37,8 @@ import im.vector.matrix.android.internal.session.room.DefaultRoomService
import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater
import im.vector.matrix.android.internal.session.room.RoomAvatarResolver import im.vector.matrix.android.internal.session.room.RoomAvatarResolver
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver
import im.vector.matrix.android.internal.session.room.members.RoomMemberDisplayNameResolver import im.vector.matrix.android.internal.session.room.membership.RoomMemberDisplayNameResolver
import im.vector.matrix.android.internal.session.room.prune.EventsPruner import im.vector.matrix.android.internal.session.room.prune.EventsPruner
import im.vector.matrix.android.internal.session.signout.DefaultSignOutService import im.vector.matrix.android.internal.session.signout.DefaultSignOutService
import im.vector.matrix.android.internal.session.user.DefaultUserService import im.vector.matrix.android.internal.session.user.DefaultUserService

View File

@ -20,7 +20,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.members.RoomMembersService import im.vector.matrix.android.api.session.room.members.MembershipService
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.read.ReadService import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.api.session.room.send.SendService import im.vector.matrix.android.api.session.room.send.SendService
@ -39,13 +39,13 @@ internal class DefaultRoom(
private val sendService: SendService, private val sendService: SendService,
private val stateService: StateService, private val stateService: StateService,
private val readService: ReadService, private val readService: ReadService,
private val roomMembersService: RoomMembersService private val roomMembersService: MembershipService
) : Room, ) : Room,
TimelineService by timelineService, TimelineService by timelineService,
SendService by sendService, SendService by sendService,
StateService by stateService, StateService by stateService,
ReadService by readService, ReadService by readService,
RoomMembersService by roomMembersService { MembershipService by roomMembersService {


override val roomSummary: LiveData<RoomSummary> by lazy { override val roomSummary: LiveData<RoomSummary> by lazy {
val liveRealmData = RealmLiveData<RoomSummaryEntity>(monarchy.realmConfiguration) { realm -> val liveRealmData = RealmLiveData<RoomSummaryEntity>(monarchy.realmConfiguration) { realm ->

View File

@ -18,17 +18,21 @@ package im.vector.matrix.android.internal.session.room


import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.annotation.ReactionContent
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse
import im.vector.matrix.android.internal.network.NetworkConstants import im.vector.matrix.android.internal.network.NetworkConstants
import im.vector.matrix.android.internal.session.room.invite.InviteBody import im.vector.matrix.android.internal.session.room.membership.RoomMembersResponse
import im.vector.matrix.android.internal.session.room.members.RoomMembersResponse import im.vector.matrix.android.internal.session.room.membership.joining.InviteBody
import im.vector.matrix.android.internal.session.room.send.SendResponse import im.vector.matrix.android.internal.session.room.send.SendResponse
import im.vector.matrix.android.internal.session.room.timeline.EventContextResponse import im.vector.matrix.android.internal.session.room.timeline.EventContextResponse
import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse
import retrofit2.Call import retrofit2.Call
import retrofit2.http.* import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
import retrofit2.http.Query


internal interface RoomAPI { internal interface RoomAPI {


@ -173,4 +177,24 @@ internal interface RoomAPI {
@Path("eventType") eventType: String, @Path("eventType") eventType: String,
@Body content: Content? @Body content: Content?
): Call<SendResponse> ): Call<SendResponse>

/**
* Join the given room.
*
* @param roomId the room id
* @param params the request body
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/join")
fun join(@Path("roomId") roomId: String,
@Body params: Map<String, String>): Call<Unit>

/**
* Leave the given room.
*
* @param roomId the room id
* @param params the request body
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/leave")
fun leave(@Path("roomId") roomId: String,
@Body params: Map<String, String>): Call<Unit>
} }

View File

@ -20,14 +20,14 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.events.model.EventType 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.events.model.toModel
import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomAvatarContent import im.vector.matrix.android.api.session.room.model.RoomAvatarContent
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.room.members.RoomMembers import im.vector.matrix.android.internal.session.room.membership.RoomMembers


internal class RoomAvatarResolver(private val monarchy: Monarchy, internal class RoomAvatarResolver(private val monarchy: Monarchy,
private val credentials: Credentials) { private val credentials: Credentials) {
@ -48,7 +48,7 @@ internal class RoomAvatarResolver(private val monarchy: Monarchy,
} }
val roomMembers = RoomMembers(realm, roomId) val roomMembers = RoomMembers(realm, roomId)
val members = roomMembers.getLoaded() val members = roomMembers.getLoaded()
if (roomEntity?.membership == MyMembership.INVITED) { if (roomEntity?.membership == Membership.INVITE) {
if (members.size == 1) { if (members.size == 1) {
res = members.entries.first().value.avatarUrl res = members.entries.first().value.avatarUrl
} else if (members.size > 1) { } else if (members.size > 1) {
@ -57,9 +57,9 @@ internal class RoomAvatarResolver(private val monarchy: Monarchy,
} }
} else { } else {
// detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat) // detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat)
if (roomMembers.getNumberOfJoinedMembers() == 1 && members.isNotEmpty()) { if (members.size == 1) {
res = members.entries.first().value.avatarUrl res = members.entries.first().value.avatarUrl
} else if (roomMembers.getNumberOfMembers() == 2 && members.size > 1) { } else if (members.size == 2) {
val firstOtherMember = members.filterKeys { it != credentials.userId }.values.firstOrNull() val firstOtherMember = members.filterKeys { it != credentials.userId }.values.firstOrNull()
res = firstOtherMember?.avatarUrl res = firstOtherMember?.avatarUrl
} }

View File

@ -18,10 +18,12 @@ package im.vector.matrix.android.internal.session.room


import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.internal.session.room.invite.InviteTask import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
import im.vector.matrix.android.internal.session.room.members.DefaultRoomMembersService import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor
import im.vector.matrix.android.internal.session.room.members.SenderRoomMemberExtractor import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask
import im.vector.matrix.android.internal.session.room.read.DefaultReadService import im.vector.matrix.android.internal.session.room.read.DefaultReadService
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
import im.vector.matrix.android.internal.session.room.send.DefaultSendService import im.vector.matrix.android.internal.session.room.send.DefaultSendService
@ -34,24 +36,26 @@ import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor


internal class RoomFactory(private val loadRoomMembersTask: LoadRoomMembersTask, internal class RoomFactory(private val monarchy: Monarchy,
private val eventFactory: LocalEchoEventFactory,
private val taskExecutor: TaskExecutor,
private val loadRoomMembersTask: LoadRoomMembersTask,
private val inviteTask: InviteTask, private val inviteTask: InviteTask,
private val sendStateTask: SendStateTask, private val sendStateTask: SendStateTask,
private val monarchy: Monarchy,
private val paginationTask: PaginationTask, private val paginationTask: PaginationTask,
private val contextOfEventTask: GetContextOfEventTask, private val contextOfEventTask: GetContextOfEventTask,
private val setReadMarkersTask: SetReadMarkersTask, private val setReadMarkersTask: SetReadMarkersTask,
private val eventFactory: LocalEchoEventFactory, private val joinRoomTask: JoinRoomTask,
private val taskExecutor: TaskExecutor) { private val leaveRoomTask: LeaveRoomTask) {


fun instantiate(roomId: String): Room { fun instantiate(roomId: String): Room {
val roomMemberExtractor = SenderRoomMemberExtractor(roomId) val roomMemberExtractor = SenderRoomMemberExtractor(roomId)
val timelineEventFactory = TimelineEventFactory(roomMemberExtractor, EventRelationExtractor()) val timelineEventFactory = TimelineEventFactory(roomMemberExtractor, EventRelationExtractor())
val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, contextOfEventTask, timelineEventFactory, paginationTask) val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineEventFactory, contextOfEventTask, paginationTask)
val sendService = DefaultSendService(roomId, eventFactory, monarchy) val sendService = DefaultSendService(roomId, eventFactory, monarchy)
val stateService = DefaultStateService(roomId, sendStateTask, taskExecutor) val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask)
val roomMembersService = DefaultRoomMembersService(roomId, monarchy, loadRoomMembersTask, inviteTask, taskExecutor) val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask)
val readService = DefaultReadService(roomId, monarchy, setReadMarkersTask, taskExecutor) val readService = DefaultReadService(roomId, monarchy, taskExecutor, setReadMarkersTask)


return DefaultRoom( return DefaultRoom(
roomId, roomId,

View File

@ -19,10 +19,14 @@ package im.vector.matrix.android.internal.session.room
import im.vector.matrix.android.internal.session.DefaultSession import im.vector.matrix.android.internal.session.DefaultSession
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
import im.vector.matrix.android.internal.session.room.create.DefaultCreateRoomTask import im.vector.matrix.android.internal.session.room.create.DefaultCreateRoomTask
import im.vector.matrix.android.internal.session.room.invite.DefaultInviteTask import im.vector.matrix.android.internal.session.room.membership.DefaultLoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.invite.InviteTask import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.members.DefaultLoadRoomMembersTask import im.vector.matrix.android.internal.session.room.membership.joining.DefaultInviteTask
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask import im.vector.matrix.android.internal.session.room.membership.joining.DefaultJoinRoomTask
import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
import im.vector.matrix.android.internal.session.room.membership.leaving.DefaultLeaveRoomTask
import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask
import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
@ -71,7 +75,7 @@ class RoomModule {
} }


scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
RoomFactory(get(), get(), get(), get(), get(), get(), get(), get(), get()) RoomFactory(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get())
} }


scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
@ -82,6 +86,14 @@ class RoomModule {
DefaultInviteTask(get()) as InviteTask DefaultInviteTask(get()) as InviteTask
} }


scope(DefaultSession.SCOPE) {
DefaultJoinRoomTask(get()) as JoinRoomTask
}

scope(DefaultSession.SCOPE) {
DefaultLeaveRoomTask(get()) as LeaveRoomTask
}

scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
DefaultSendStateTask(get()) as SendStateTask DefaultSendStateTask(get()) as SendStateTask
} }

View File

@ -21,6 +21,7 @@ package im.vector.matrix.android.internal.session.room
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.events.model.EventType 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.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.api.session.room.model.RoomTopicContent
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
@ -28,8 +29,8 @@ import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.query.latestEvent import im.vector.matrix.android.internal.database.query.latestEvent
import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver
import im.vector.matrix.android.internal.session.room.members.RoomMembers import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary
import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications
import io.realm.Realm import io.realm.Realm
@ -41,11 +42,12 @@ internal class RoomSummaryUpdater(private val credentials: Credentials,


fun update(realm: Realm, fun update(realm: Realm,
roomId: String, roomId: String,
membership: Membership? = null,
roomSummary: RoomSyncSummary? = null, roomSummary: RoomSyncSummary? = null,
unreadNotifications: RoomSyncUnreadNotifications? = null) { unreadNotifications: RoomSyncUnreadNotifications? = null) {


val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId) ?: realm.createObject(roomId)


if (roomSummary != null) { if (roomSummary != null) {
if (roomSummary.heroes.isNotEmpty()) { if (roomSummary.heroes.isNotEmpty()) {
@ -65,8 +67,11 @@ internal class RoomSummaryUpdater(private val credentials: Credentials,
if (unreadNotifications?.notificationCount != null) { if (unreadNotifications?.notificationCount != null) {
roomSummaryEntity.notificationCount = unreadNotifications.notificationCount roomSummaryEntity.notificationCount = unreadNotifications.notificationCount
} }
if (membership != null) {
roomSummaryEntity.membership = membership
}


val lastEvent = EventEntity.latestEvent(realm, roomId, includedTypes = listOf(EventType.MESSAGE)) val lastEvent = EventEntity.latestEvent(realm, roomId)
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain() val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain()
val otherRoomMembers = RoomMembers(realm, roomId).getLoaded().filterKeys { it != credentials.userId } val otherRoomMembers = RoomMembers(realm, roomId).getLoaded().filterKeys { it != credentials.userId }
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString() roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()

View File

@ -16,28 +16,32 @@
* *
*/ */


package im.vector.matrix.android.internal.session.room.members package im.vector.matrix.android.internal.session.room.membership


import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.members.RoomMembersService import im.vector.matrix.android.api.session.room.members.MembershipService
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.session.room.invite.InviteTask import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.fetchCopied import im.vector.matrix.android.internal.util.fetchCopied


internal class DefaultRoomMembersService(private val roomId: String, internal class DefaultMembershipService(private val roomId: String,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val loadRoomMembersTask: LoadRoomMembersTask, private val taskExecutor: TaskExecutor,
private val inviteTask: InviteTask, private val loadRoomMembersTask: LoadRoomMembersTask,
private val taskExecutor: TaskExecutor private val inviteTask: InviteTask,
) : RoomMembersService { private val joinTask: JoinRoomTask,
private val leaveRoomTask: LeaveRoomTask
) : MembershipService {


override fun loadRoomMembersIfNeeded(): Cancelable { override fun loadRoomMembersIfNeeded(): Cancelable {
val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE) val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE)
@ -68,4 +72,18 @@ internal class DefaultRoomMembersService(private val roomId: String,
.dispatchTo(callback) .dispatchTo(callback)
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }

override fun join(callback: MatrixCallback<Unit>) {
val params = JoinRoomTask.Params(roomId)
joinTask.configureWith(params)
.dispatchTo(callback)
.executeBy(taskExecutor)
}

override fun leave(callback: MatrixCallback<Unit>) {
val params = LeaveRoomTask.Params(roomId)
leaveRoomTask.configureWith(params)
.dispatchTo(callback)
.executeBy(taskExecutor)
}
} }

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */


package im.vector.matrix.android.internal.session.room.members package im.vector.matrix.android.internal.session.room.membership


import arrow.core.Try import arrow.core.Try
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */


package im.vector.matrix.android.internal.session.room.members package im.vector.matrix.android.internal.session.room.membership


import android.content.Context import android.content.Context
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
@ -22,7 +22,7 @@ import im.vector.matrix.android.R
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.events.model.EventType 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.events.model.toModel
import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomAliasesContent import im.vector.matrix.android.api.session.room.model.RoomAliasesContent
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
import im.vector.matrix.android.api.session.room.model.RoomNameContent import im.vector.matrix.android.api.session.room.model.RoomNameContent
@ -77,24 +77,17 @@ internal class RoomDisplayNameResolver(private val context: Context,
} }


val roomMembers = RoomMembers(realm, roomId) val roomMembers = RoomMembers(realm, roomId)
val otherRoomMembers = roomMembers.getLoaded() val loadedMembers = roomMembers.getLoaded()
.filterKeys { it != credentials.userId } val otherRoomMembers = loadedMembers.filterKeys { it != credentials.userId }

if (roomEntity?.membership == Membership.INVITE) {
if (roomEntity?.membership == MyMembership.INVITED) { val inviteMeEvent = roomMembers.queryRoomMemberEvent(credentials.userId).findFirst()
//TODO handle invited val inviterId = inviteMeEvent?.sender
/* name = if (inviterId != null && otherRoomMembers.containsKey(inviterId)) {
if (currentUser != null roomMemberDisplayNameResolver.resolve(inviterId, otherRoomMembers)
&& !othersActiveMembers.isEmpty()
&& !TextUtils.isEmpty(currentUser!!.mSender)) {
// extract who invited us to the room
name = context.getString(R.string.room_displayname_invite_from, roomState.resolve(currentUser!!.mSender))
} else { } else {
name = context.getString(R.string.room_displayname_room_invite) context.getString(R.string.room_displayname_room_invite)
} }
*/
name = context.getString(R.string.room_displayname_room_invite)
} else { } else {

val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
val memberIds = if (roomSummary?.heroes?.isNotEmpty() == true) { val memberIds = if (roomSummary?.heroes?.isNotEmpty() == true) {
roomSummary.heroes roomSummary.heroes

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */


package im.vector.matrix.android.internal.session.room.members package im.vector.matrix.android.internal.session.room.membership


import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomMember


View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */


package im.vector.matrix.android.internal.session.room.members package im.vector.matrix.android.internal.session.room.membership


import im.vector.matrix.android.api.session.events.model.EventType 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.events.model.toModel

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */


package im.vector.matrix.android.internal.session.room.members package im.vector.matrix.android.internal.session.room.membership


import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */


package im.vector.matrix.android.internal.session.room.members package im.vector.matrix.android.internal.session.room.membership


import im.vector.matrix.android.api.session.events.model.EventType 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.events.model.toModel

View File

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */


package im.vector.matrix.android.internal.session.room.invite package im.vector.matrix.android.internal.session.room.membership.joining


import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */


package im.vector.matrix.android.internal.session.room.invite package im.vector.matrix.android.internal.session.room.membership.joining


import arrow.core.Try import arrow.core.Try
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest

View File

@ -0,0 +1,38 @@
/*
* 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.matrix.android.internal.session.room.membership.joining

import arrow.core.Try
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.task.Task

internal interface JoinRoomTask : Task<JoinRoomTask.Params, Unit> {
data class Params(
val roomId: String
)
}

internal class DefaultJoinRoomTask(private val roomAPI: RoomAPI) : JoinRoomTask {

override fun execute(params: JoinRoomTask.Params): Try<Unit> {
return executeRequest {
apiCall = roomAPI.join(params.roomId, HashMap())
}
}

}

View File

@ -0,0 +1,38 @@
/*
* 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.matrix.android.internal.session.room.membership.leaving

import arrow.core.Try
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.task.Task

internal interface LeaveRoomTask : Task<LeaveRoomTask.Params, Unit> {
data class Params(
val roomId: String
)
}

internal class DefaultLeaveRoomTask(private val roomAPI: RoomAPI) : LeaveRoomTask {

override fun execute(params: LeaveRoomTask.Params): Try<Unit> {
return executeRequest {
apiCall = roomAPI.leave(params.roomId, HashMap())
}
}

}

View File

@ -27,8 +27,8 @@ import im.vector.matrix.android.internal.util.fetchCopied


internal class DefaultReadService(private val roomId: String, internal class DefaultReadService(private val roomId: String,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val setReadMarkersTask: SetReadMarkersTask, private val taskExecutor: TaskExecutor,
private val taskExecutor: TaskExecutor) : ReadService { private val setReadMarkersTask: SetReadMarkersTask) : ReadService {


override fun markAllAsRead(callback: MatrixCallback<Unit>) { override fun markAllAsRead(callback: MatrixCallback<Unit>) {
val latestEvent = getLatestEvent() val latestEvent = getLatestEvent()

View File

@ -23,8 +23,8 @@ import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith


internal class DefaultStateService(private val roomId: String, internal class DefaultStateService(private val roomId: String,
private val sendStateTask: SendStateTask, private val taskExecutor: TaskExecutor,
private val taskExecutor: TaskExecutor) : StateService { private val sendStateTask: SendStateTask) : StateService {


override fun updateTopic(topic: String, callback: MatrixCallback<Unit>) { override fun updateTopic(topic: String, callback: MatrixCallback<Unit>) {
val params = SendStateTask.Params(roomId, val params = SendStateTask.Params(roomId,

View File

@ -28,8 +28,8 @@ import im.vector.matrix.android.internal.util.fetchCopyMap
internal class DefaultTimelineService(private val roomId: String, internal class DefaultTimelineService(private val roomId: String,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val contextOfEventTask: GetContextOfEventTask,
private val timelineEventFactory: TimelineEventFactory, private val timelineEventFactory: TimelineEventFactory,
private val contextOfEventTask: GetContextOfEventTask,
private val paginationTask: PaginationTask private val paginationTask: PaginationTask
) : TimelineService { ) : TimelineService {



View File

@ -20,7 +20,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.session.room.EventRelationExtractor import im.vector.matrix.android.internal.session.room.EventRelationExtractor
import im.vector.matrix.android.internal.session.room.members.SenderRoomMemberExtractor import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor
import io.realm.Realm import io.realm.Realm


internal class TimelineEventFactory( internal class TimelineEventFactory(

View File

@ -17,7 +17,7 @@
package im.vector.matrix.android.internal.session.sync package im.vector.matrix.android.internal.session.sync


import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.internal.database.model.GroupEntity import im.vector.matrix.android.internal.database.model.GroupEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.sync.model.GroupsSyncResponse import im.vector.matrix.android.internal.session.sync.model.GroupsSyncResponse
@ -56,7 +56,7 @@ internal class GroupSyncHandler(private val monarchy: Monarchy) {
groupId: String): GroupEntity { groupId: String): GroupEntity {


val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId) val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId)
groupEntity.membership = MyMembership.JOINED groupEntity.membership = Membership.JOIN
return groupEntity return groupEntity
} }


@ -64,7 +64,7 @@ internal class GroupSyncHandler(private val monarchy: Monarchy) {
groupId: String): GroupEntity { groupId: String): GroupEntity {


val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId) val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId)
groupEntity.membership = MyMembership.INVITED groupEntity.membership = Membership.INVITE
return groupEntity return groupEntity


} }
@ -74,7 +74,7 @@ internal class GroupSyncHandler(private val monarchy: Monarchy) {
groupId: String): GroupEntity { groupId: String): GroupEntity {


val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId) val groupEntity = GroupEntity.where(realm, groupId).findFirst() ?: GroupEntity(groupId)
groupEntity.membership = MyMembership.LEFT groupEntity.membership = Membership.LEAVE
return groupEntity return groupEntity
} }



View File

@ -20,7 +20,7 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType 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.events.model.toModel
import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent
import im.vector.matrix.android.internal.database.helper.addAll import im.vector.matrix.android.internal.database.helper.addAll
import im.vector.matrix.android.internal.database.helper.addOrUpdate import im.vector.matrix.android.internal.database.helper.addOrUpdate
@ -69,7 +69,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
val rooms = when (handlingStrategy) { val rooms = when (handlingStrategy) {
is HandlingStrategy.JOINED -> handlingStrategy.data.map { handleJoinedRoom(realm, it.key, it.value) } is HandlingStrategy.JOINED -> handlingStrategy.data.map { handleJoinedRoom(realm, it.key, it.value) }
is HandlingStrategy.INVITED -> handlingStrategy.data.map { handleInvitedRoom(realm, it.key, it.value) } is HandlingStrategy.INVITED -> handlingStrategy.data.map { handleInvitedRoom(realm, it.key, it.value) }
is HandlingStrategy.LEFT -> handlingStrategy.data.map { handleLeftRoom(it.key, it.value) } is HandlingStrategy.LEFT -> handlingStrategy.data.map { handleLeftRoom(realm, it.key, it.value) }
} }
realm.insertOrUpdate(rooms) realm.insertOrUpdate(rooms)
} }
@ -83,11 +83,10 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
val roomEntity = RoomEntity.where(realm, roomId).findFirst() val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId) ?: realm.createObject(roomId)


if (roomEntity.membership == MyMembership.INVITED) { if (roomEntity.membership == Membership.INVITE) {
roomEntity.chunks.deleteAllFromRealm() roomEntity.chunks.deleteAllFromRealm()
} }

roomEntity.membership = Membership.JOIN
roomEntity.membership = MyMembership.JOINED


val lastChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) val lastChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
val isInitialSync = lastChunk == null val isInitialSync = lastChunk == null
@ -121,8 +120,8 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
} }
} }
} }
roomSummaryUpdater.update(realm, roomId, roomSync.summary, roomSync.unreadNotifications) eventRelationsAggregationUpdater.update(realm, roomId, roomSync.timeline?.events)
eventRelationsAggregationUpdater.update(realm,roomId,roomSync.timeline?.events) roomSummaryUpdater.update(realm, roomId, Membership.JOIN, roomSync.summary, roomSync.unreadNotifications)


if (roomSync.ephemeral != null && roomSync.ephemeral.events.isNotEmpty()) { if (roomSync.ephemeral != null && roomSync.ephemeral.events.isNotEmpty()) {
handleEphemeral(realm, roomId, roomSync.ephemeral) handleEphemeral(realm, roomId, roomSync.ephemeral)
@ -131,7 +130,6 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
if (roomSync.accountData != null && roomSync.accountData.events.isNullOrEmpty().not()) { if (roomSync.accountData != null && roomSync.accountData.events.isNullOrEmpty().not()) {
handleRoomAccountDataEvents(realm, roomId, roomSync.accountData) handleRoomAccountDataEvents(realm, roomId, roomSync.accountData)
} }

return roomEntity return roomEntity
} }


@ -139,26 +137,28 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
roomId: String, roomId: String,
roomSync: roomSync:
InvitedRoomSync): RoomEntity { InvitedRoomSync): RoomEntity {

Timber.v("Handle invited sync for room $roomId") Timber.v("Handle invited sync for room $roomId")

val roomEntity = RoomEntity.where(realm, roomId).findFirst()
val roomEntity = RoomEntity() ?: realm.createObject(roomId)
roomEntity.roomId = roomId roomEntity.membership = Membership.INVITE
roomEntity.membership = MyMembership.INVITED
if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) { if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
val chunkEntity = handleTimelineEvents(realm, roomId, roomSync.inviteState.events) val chunkEntity = handleTimelineEvents(realm, roomId, roomSync.inviteState.events)
roomEntity.addOrUpdate(chunkEntity) roomEntity.addOrUpdate(chunkEntity)
} }
roomSummaryUpdater.update(realm, roomId, Membership.INVITE)
return roomEntity return roomEntity
} }


// TODO : handle it private fun handleLeftRoom(realm: Realm,
private fun handleLeftRoom(roomId: String, roomId: String,
roomSync: RoomSync): RoomEntity { roomSync: RoomSync): RoomEntity {
return RoomEntity().apply { val roomEntity = RoomEntity.where(realm, roomId).findFirst()
this.roomId = roomId ?: realm.createObject(roomId)
this.membership = MyMembership.LEFT
} roomEntity.membership = Membership.LEAVE
roomEntity.chunks.deleteAllFromRealm()
roomSummaryUpdater.update(realm, roomId, Membership.LEAVE, roomSync.summary, roomSync.unreadNotifications)
return roomEntity
} }


private fun handleTimelineEvents(realm: Realm, private fun handleTimelineEvents(realm: Realm,

View File

@ -23,7 +23,7 @@ import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.UserEntity import im.vector.matrix.android.internal.database.model.UserEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.room.members.RoomMembers import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync import im.vector.matrix.android.internal.util.tryTransactionSync



View File

@ -27,9 +27,8 @@ sealed class RoomDetailActions {
object IsDisplayed : RoomDetailActions() object IsDisplayed : RoomDetailActions()
data class EventDisplayed(val event: TimelineEvent) : RoomDetailActions() data class EventDisplayed(val event: TimelineEvent) : RoomDetailActions()
data class LoadMore(val direction: Timeline.Direction) : RoomDetailActions() data class LoadMore(val direction: Timeline.Direction) : RoomDetailActions()

data class SendReaction(val reaction: String, val targetEventId: String) : RoomDetailActions() data class SendReaction(val reaction: String, val targetEventId: String) : RoomDetailActions()

object AcceptInvite : RoomDetailActions()

object RejectInvite : RoomDetailActions()


} }

View File

@ -53,7 +53,12 @@ import com.otaliastudios.autocomplete.Autocomplete
import com.otaliastudios.autocomplete.AutocompleteCallback import com.otaliastudios.autocomplete.AutocompleteCallback
import com.otaliastudios.autocomplete.CharPolicy import com.otaliastudios.autocomplete.CharPolicy
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotredesign.R import im.vector.riotredesign.R
@ -64,7 +69,15 @@ import im.vector.riotredesign.core.extensions.observeEvent
import im.vector.riotredesign.core.glide.GlideApp import im.vector.riotredesign.core.glide.GlideApp
import im.vector.riotredesign.core.platform.ToolbarConfigurable import im.vector.riotredesign.core.platform.ToolbarConfigurable
import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.core.utils.* import im.vector.riotredesign.core.utils.LiveEvent
import im.vector.riotredesign.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.riotredesign.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
import im.vector.riotredesign.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA
import im.vector.riotredesign.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA
import im.vector.riotredesign.core.utils.checkPermissions
import im.vector.riotredesign.core.utils.copyToClipboard
import im.vector.riotredesign.core.utils.openCamera
import im.vector.riotredesign.core.utils.shareMedia
import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandPresenter import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandPresenter
import im.vector.riotredesign.features.autocomplete.command.CommandAutocompletePolicy import im.vector.riotredesign.features.autocomplete.command.CommandAutocompletePolicy
import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserPresenter import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserPresenter
@ -82,6 +95,7 @@ import im.vector.riotredesign.features.home.room.detail.timeline.action.MessageM
import im.vector.riotredesign.features.home.room.detail.timeline.helper.EndlessRecyclerViewScrollListener import im.vector.riotredesign.features.home.room.detail.timeline.helper.EndlessRecyclerViewScrollListener
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotredesign.features.html.PillImageSpan import im.vector.riotredesign.features.html.PillImageSpan
import im.vector.riotredesign.features.invite.VectorInviteView
import im.vector.riotredesign.features.media.ImageContentRenderer import im.vector.riotredesign.features.media.ImageContentRenderer
import im.vector.riotredesign.features.media.ImageMediaViewerActivity import im.vector.riotredesign.features.media.ImageMediaViewerActivity
import im.vector.riotredesign.features.media.VideoContentRenderer import im.vector.riotredesign.features.media.VideoContentRenderer
@ -112,7 +126,8 @@ private const val REACTION_SELECT_REQUEST_CODE = 2
class RoomDetailFragment : class RoomDetailFragment :
VectorBaseFragment(), VectorBaseFragment(),
TimelineEventController.Callback, TimelineEventController.Callback,
AutocompleteUserPresenter.Callback { AutocompleteUserPresenter.Callback,
VectorInviteView.Callback {


companion object { companion object {


@ -169,6 +184,7 @@ class RoomDetailFragment :
setupToolbar() setupToolbar()
setupComposer() setupComposer()
setupAttachmentButton() setupAttachmentButton()
setupInviteView()
roomDetailViewModel.subscribe { renderState(it) } roomDetailViewModel.subscribe { renderState(it) }
textComposerViewModel.subscribe { renderTextComposerState(it) } textComposerViewModel.subscribe { renderTextComposerState(it) }
roomDetailViewModel.sendMessageResultLiveData.observeEvent(this) { renderSendMessageResult(it) } roomDetailViewModel.sendMessageResultLiveData.observeEvent(this) { renderSendMessageResult(it) }
@ -183,11 +199,13 @@ class RoomDetailFragment :
if (resultCode == RESULT_OK && data != null) { if (resultCode == RESULT_OK && data != null) {
when (requestCode) { when (requestCode) {
REQUEST_FILES_REQUEST_CODE, TAKE_IMAGE_REQUEST_CODE -> handleMediaIntent(data) REQUEST_FILES_REQUEST_CODE, TAKE_IMAGE_REQUEST_CODE -> handleMediaIntent(data)
REACTION_SELECT_REQUEST_CODE -> { REACTION_SELECT_REQUEST_CODE -> {
val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID) ?: return val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID)
val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT) ?: return ?: return
val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT)
?: return
//TODO check if already reacted with that? //TODO check if already reacted with that?
roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction,eventId)) roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, eventId))
} }
} }
} }
@ -342,27 +360,31 @@ class RoomDetailFragment :
} }
} }


private fun setupInviteView() {
inviteView.callback = this
}

private fun onSendChoiceClicked(dialogListItem: DialogListItem) { private fun onSendChoiceClicked(dialogListItem: DialogListItem) {
Timber.v("On send choice clicked: $dialogListItem") Timber.v("On send choice clicked: $dialogListItem")
when (dialogListItem) { when (dialogListItem) {
is DialogListItem.SendFile -> { is DialogListItem.SendFile -> {
// launchFileIntent // launchFileIntent
} }
is DialogListItem.SendVoice -> { is DialogListItem.SendVoice -> {
//launchAudioRecorderIntent() //launchAudioRecorderIntent()
} }
is DialogListItem.SendSticker -> { is DialogListItem.SendSticker -> {
//startStickerPickerActivity() //startStickerPickerActivity()
} }
is DialogListItem.TakePhotoVideo -> is DialogListItem.TakePhotoVideo ->
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) { if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
// launchCamera() // launchCamera()
} }
is DialogListItem.TakePhoto -> is DialogListItem.TakePhoto ->
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA)) { if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA)) {
openCamera(requireActivity(), CAMERA_VALUE_TITLE, TAKE_IMAGE_REQUEST_CODE) openCamera(requireActivity(), CAMERA_VALUE_TITLE, TAKE_IMAGE_REQUEST_CODE)
} }
is DialogListItem.TakeVideo -> is DialogListItem.TakeVideo ->
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA)) { if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA)) {
// launchNativeVideoRecorder() // launchNativeVideoRecorder()
} }
@ -376,7 +398,17 @@ class RoomDetailFragment :


private fun renderState(state: RoomDetailViewState) { private fun renderState(state: RoomDetailViewState) {
renderRoomSummary(state) renderRoomSummary(state)
timelineEventController.setTimeline(state.timeline) val summary = state.asyncRoomSummary()
val inviter = state.inviter()
if (summary?.membership == Membership.JOIN) {
timelineEventController.setTimeline(state.timeline)
inviteView.visibility = View.GONE
} else if (summary?.membership == Membership.INVITE && inviter != null) {
inviteView.visibility = View.VISIBLE
inviteView.render(inviter, VectorInviteView.Mode.LARGE)
} else {
//TODO : close the screen
}
} }


private fun renderRoomSummary(state: RoomDetailViewState) { private fun renderRoomSummary(state: RoomDetailViewState) {
@ -399,20 +431,20 @@ class RoomDetailFragment :
private fun renderSendMessageResult(sendMessageResult: SendMessageResult) { private fun renderSendMessageResult(sendMessageResult: SendMessageResult) {
when (sendMessageResult) { when (sendMessageResult) {
is SendMessageResult.MessageSent, is SendMessageResult.MessageSent,
is SendMessageResult.SlashCommandHandled -> { is SendMessageResult.SlashCommandHandled -> {
// Clear composer // Clear composer
composerEditText.text = null composerEditText.text = null
} }
is SendMessageResult.SlashCommandError -> { is SendMessageResult.SlashCommandError -> {
displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command)) displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command))
} }
is SendMessageResult.SlashCommandUnknown -> { is SendMessageResult.SlashCommandUnknown -> {
displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command)) displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command))
} }
is SendMessageResult.SlashCommandResultOk -> { is SendMessageResult.SlashCommandResultOk -> {
// Ignore // Ignore
} }
is SendMessageResult.SlashCommandResultError -> { is SendMessageResult.SlashCommandResultError -> {
displayCommandError(sendMessageResult.throwable.localizedMessage) displayCommandError(sendMessageResult.throwable.localizedMessage)
} }
is SendMessageResult.SlashCommandNotImplemented -> { is SendMessageResult.SlashCommandNotImplemented -> {
@ -487,7 +519,7 @@ class RoomDetailFragment :
override fun onClickOnReactionPill(informationData: MessageInformationData, reaction: String, on: Boolean) { override fun onClickOnReactionPill(informationData: MessageInformationData, reaction: String, on: Boolean) {
if (on) { if (on) {
//we should test the current real state of reaction on this event //we should test the current real state of reaction on this event
roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction,informationData.eventId)) roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, informationData.eventId))
} else { } else {
//TODO it's an undo :/ //TODO it's an undo :/
} }
@ -503,18 +535,18 @@ class RoomDetailFragment :
it?.getContentIfNotHandled()?.let { actionData -> it?.getContentIfNotHandled()?.let { actionData ->


when (actionData.actionId) { when (actionData.actionId) {
MessageMenuViewModel.ACTION_ADD_REACTION -> { MessageMenuViewModel.ACTION_ADD_REACTION -> {
val eventId = actionData.data?.toString() ?: return val eventId = actionData.data?.toString() ?: return
startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), eventId), REACTION_SELECT_REQUEST_CODE) startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), eventId), REACTION_SELECT_REQUEST_CODE)
} }
MessageMenuViewModel.ACTION_COPY -> { MessageMenuViewModel.ACTION_COPY -> {
//I need info about the current selected message :/ //I need info about the current selected message :/
copyToClipboard(requireContext(), actionData.data?.toString() ?: "", false) copyToClipboard(requireContext(), actionData.data?.toString() ?: "", false)
val snack = Snackbar.make(view!!, requireContext().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT) val snack = Snackbar.make(view!!, requireContext().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
snack.view.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.notification_accent_color)) snack.view.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.notification_accent_color))
snack.show() snack.show()
} }
MessageMenuViewModel.ACTION_SHARE -> { MessageMenuViewModel.ACTION_SHARE -> {
//TODO current data communication is too limited //TODO current data communication is too limited
//Need to now the media type //Need to now the media type
actionData.data?.toString()?.let { actionData.data?.toString()?.let {
@ -557,12 +589,12 @@ class RoomDetailFragment :
.setPositiveButton(R.string.ok) { dialog, id -> dialog.cancel() } .setPositiveButton(R.string.ok) { dialog, id -> dialog.cancel() }
.show() .show()
} }
MessageMenuViewModel.ACTION_QUICK_REACT -> { MessageMenuViewModel.ACTION_QUICK_REACT -> {
(actionData.data as? Pair<String, String>)?.let { pairData -> (actionData.data as? Pair<String, String>)?.let { pairData ->
roomDetailViewModel.process(RoomDetailActions.SendReaction(pairData.second, pairData.first)) roomDetailViewModel.process(RoomDetailActions.SendReaction(pairData.second, pairData.first))
} }
} }
else -> { else -> {
Toast.makeText(context, "Action ${actionData.actionId} not implemented", Toast.LENGTH_LONG).show() Toast.makeText(context, "Action ${actionData.actionId} not implemented", Toast.LENGTH_LONG).show()
} }
} }
@ -615,4 +647,13 @@ class RoomDetailFragment :
} }
} }


// VectorInviteView.Callback

override fun onAcceptInvite() {
roomDetailViewModel.process(RoomDetailActions.AcceptInvite)
}

override fun onRejectInvite() {
roomDetailViewModel.process(RoomDetailActions.RejectInvite)
}
} }

View File

@ -19,11 +19,13 @@ package im.vector.riotredesign.features.home.room.detail
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.jakewharton.rxrelay2.BehaviorRelay import com.jakewharton.rxrelay2.BehaviorRelay
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.platform.VectorViewModel
@ -62,6 +64,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
init { init {
observeRoomSummary() observeRoomSummary()
observeEventDisplayedActions() observeEventDisplayedActions()
observeInvitationState()
room.loadRoomMembersIfNeeded() room.loadRoomMembersIfNeeded()
timeline.start() timeline.start()
setState { copy(timeline = this@RoomDetailViewModel.timeline) } setState { copy(timeline = this@RoomDetailViewModel.timeline) }
@ -69,12 +72,14 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,


fun process(action: RoomDetailActions) { fun process(action: RoomDetailActions) {
when (action) { when (action) {
is RoomDetailActions.SendMessage -> handleSendMessage(action) is RoomDetailActions.SendMessage -> handleSendMessage(action)
is RoomDetailActions.IsDisplayed -> handleIsDisplayed() is RoomDetailActions.IsDisplayed -> handleIsDisplayed()
is RoomDetailActions.SendMedia -> handleSendMedia(action) is RoomDetailActions.SendMedia -> handleSendMedia(action)
is RoomDetailActions.EventDisplayed -> handleEventDisplayed(action) is RoomDetailActions.EventDisplayed -> handleEventDisplayed(action)
is RoomDetailActions.LoadMore -> handleLoadMore(action) is RoomDetailActions.LoadMore -> handleLoadMore(action)
is RoomDetailActions.SendReaction -> handleSendReaction(action) is RoomDetailActions.SendReaction -> handleSendReaction(action)
is RoomDetailActions.AcceptInvite -> handleAcceptInvite()
is RoomDetailActions.RejectInvite -> handleRejectInvite()
} }
} }


@ -89,63 +94,63 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
val slashCommandResult = CommandParser.parseSplashCommand(action.text) val slashCommandResult = CommandParser.parseSplashCommand(action.text)


when (slashCommandResult) { when (slashCommandResult) {
is ParsedCommand.ErrorNotACommand -> { is ParsedCommand.ErrorNotACommand -> {
// Send the text message to the room // Send the text message to the room
room.sendTextMessage(action.text) room.sendTextMessage(action.text)
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent)) _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
} }
is ParsedCommand.ErrorSyntax -> { is ParsedCommand.ErrorSyntax -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandError(slashCommandResult.command))) _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandError(slashCommandResult.command)))
} }
is ParsedCommand.ErrorEmptySlashCommand -> { is ParsedCommand.ErrorEmptySlashCommand -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown("/"))) _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown("/")))
} }
is ParsedCommand.ErrorUnknownSlashCommand -> { is ParsedCommand.ErrorUnknownSlashCommand -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown(slashCommandResult.slashCommand))) _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown(slashCommandResult.slashCommand)))
} }
is ParsedCommand.Invite -> { is ParsedCommand.Invite -> {
handleInviteSlashCommand(slashCommandResult) handleInviteSlashCommand(slashCommandResult)
} }
is ParsedCommand.SetUserPowerLevel -> { is ParsedCommand.SetUserPowerLevel -> {
// TODO // TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
} }
is ParsedCommand.ClearScalarToken -> { is ParsedCommand.ClearScalarToken -> {
// TODO // TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
} }
is ParsedCommand.SetMarkdown -> { is ParsedCommand.SetMarkdown -> {
// TODO // TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
} }
is ParsedCommand.UnbanUser -> { is ParsedCommand.UnbanUser -> {
// TODO // TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
} }
is ParsedCommand.BanUser -> { is ParsedCommand.BanUser -> {
// TODO // TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
} }
is ParsedCommand.KickUser -> { is ParsedCommand.KickUser -> {
// TODO // TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
} }
is ParsedCommand.JoinRoom -> { is ParsedCommand.JoinRoom -> {
// TODO // TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
} }
is ParsedCommand.PartRoom -> { is ParsedCommand.PartRoom -> {
// TODO // TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
} }
is ParsedCommand.SendEmote -> { is ParsedCommand.SendEmote -> {
room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE) room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE)
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled)) _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled))
} }
is ParsedCommand.ChangeTopic -> { is ParsedCommand.ChangeTopic -> {
handleChangeTopicSlashCommand(slashCommandResult) handleChangeTopicSlashCommand(slashCommandResult)
} }
is ParsedCommand.ChangeDisplayName -> { is ParsedCommand.ChangeDisplayName -> {
// TODO // TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
} }
@ -182,7 +187,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,




private fun handleSendReaction(action: RoomDetailActions.SendReaction) { private fun handleSendReaction(action: RoomDetailActions.SendReaction) {
room.sendReaction(action.reaction,action.targetEventId) room.sendReaction(action.reaction, action.targetEventId)
} }


private fun handleSendMedia(action: RoomDetailActions.SendMedia) { private fun handleSendMedia(action: RoomDetailActions.SendMedia) {
@ -214,6 +219,14 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
timeline.paginate(action.direction, PAGINATION_COUNT) timeline.paginate(action.direction, PAGINATION_COUNT)
} }


private fun handleRejectInvite() {
room.leave(object : MatrixCallback<Unit> {})
}

private fun handleAcceptInvite() {
room.join(object : MatrixCallback<Unit> {})
}

private fun observeEventDisplayedActions() { private fun observeEventDisplayedActions() {
// We are buffering scroll events for one second // We are buffering scroll events for one second
// and keep the most recent one to set the read receipt on. // and keep the most recent one to set the read receipt on.
@ -236,6 +249,18 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
} }
} }


private fun observeInvitationState() {
asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary ->
if (summary.membership == Membership.INVITE) {
summary.lastMessage?.sender?.let { senderId ->
session.getUser(senderId)
}?.also {
setState { copy(inviter = Success(it)) }
}
}
}
}

override fun onCleared() { override fun onCleared() {
timeline.dispose() timeline.dispose()
super.onCleared() super.onCleared()

View File

@ -22,11 +22,13 @@ import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineData import im.vector.matrix.android.api.session.room.timeline.TimelineData
import im.vector.matrix.android.api.session.user.model.User


data class RoomDetailViewState( data class RoomDetailViewState(
val roomId: String, val roomId: String,
val eventId: String?, val eventId: String?,
val timeline: Timeline? = null, val timeline: Timeline? = null,
val inviter: Async<User> = Uninitialized,
val asyncRoomSummary: Async<RoomSummary> = Uninitialized, val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
val asyncTimelineData: Async<TimelineData> = Uninitialized val asyncTimelineData: Async<TimelineData> = Uninitialized
) : MvRxState { ) : MvRxState {

View File

@ -24,6 +24,7 @@ import com.airbnb.mvrx.ViewModelContext
import com.jakewharton.rxrelay2.BehaviorRelay import com.jakewharton.rxrelay2.BehaviorRelay
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
@ -153,6 +154,7 @@ class RoomListViewModel(initialState: RoomListViewState,
} }


private fun buildRoomSummaries(rooms: List<RoomSummary>): RoomSummaries { private fun buildRoomSummaries(rooms: List<RoomSummary>): RoomSummaries {
val invites = ArrayList<RoomSummary>()
val favourites = ArrayList<RoomSummary>() val favourites = ArrayList<RoomSummary>()
val directChats = ArrayList<RoomSummary>() val directChats = ArrayList<RoomSummary>()
val groupRooms = ArrayList<RoomSummary>() val groupRooms = ArrayList<RoomSummary>()
@ -160,8 +162,10 @@ class RoomListViewModel(initialState: RoomListViewState,
val serverNotices = ArrayList<RoomSummary>() val serverNotices = ArrayList<RoomSummary>()


for (room in rooms) { for (room in rooms) {
if (room.membership.isLeft()) continue
val tags = room.tags.map { it.name } val tags = room.tags.map { it.name }
when { when {
room.membership == Membership.INVITE -> invites.add(room)
tags.contains(RoomTag.ROOM_TAG_SERVER_NOTICE) -> serverNotices.add(room) tags.contains(RoomTag.ROOM_TAG_SERVER_NOTICE) -> serverNotices.add(room)
tags.contains(RoomTag.ROOM_TAG_FAVOURITE) -> favourites.add(room) tags.contains(RoomTag.ROOM_TAG_FAVOURITE) -> favourites.add(room)
tags.contains(RoomTag.ROOM_TAG_LOW_PRIORITY) -> lowPriorities.add(room) tags.contains(RoomTag.ROOM_TAG_LOW_PRIORITY) -> lowPriorities.add(room)
@ -171,6 +175,7 @@ class RoomListViewModel(initialState: RoomListViewState,
} }


return RoomSummaries().apply { return RoomSummaries().apply {
put(RoomCategory.INVITE, invites.sortedWith(roomSummaryComparator))
put(RoomCategory.FAVOURITE, favourites.sortedWith(roomSummaryComparator)) put(RoomCategory.FAVOURITE, favourites.sortedWith(roomSummaryComparator))
put(RoomCategory.DIRECT, directChats.sortedWith(roomSummaryComparator)) put(RoomCategory.DIRECT, directChats.sortedWith(roomSummaryComparator))
put(RoomCategory.GROUP, groupRooms.sortedWith(roomSummaryComparator)) put(RoomCategory.GROUP, groupRooms.sortedWith(roomSummaryComparator))

View File

@ -26,15 +26,17 @@ import im.vector.riotredesign.R
data class RoomListViewState( data class RoomListViewState(
val asyncRooms: Async<RoomSummaries> = Uninitialized, val asyncRooms: Async<RoomSummaries> = Uninitialized,
val visibleRoomId: String? = null, val visibleRoomId: String? = null,
val isInviteExpanded: Boolean = true,
val isFavouriteRoomsExpanded: Boolean = true, val isFavouriteRoomsExpanded: Boolean = true,
val isDirectRoomsExpanded: Boolean = false, val isDirectRoomsExpanded: Boolean = true,
val isGroupRoomsExpanded: Boolean = false, val isGroupRoomsExpanded: Boolean = true,
val isLowPriorityRoomsExpanded: Boolean = false, val isLowPriorityRoomsExpanded: Boolean = true,
val isServerNoticeRoomsExpanded: Boolean = false val isServerNoticeRoomsExpanded: Boolean = true
) : MvRxState { ) : MvRxState {


fun isCategoryExpanded(roomCategory: RoomCategory): Boolean { fun isCategoryExpanded(roomCategory: RoomCategory): Boolean {
return when (roomCategory) { return when (roomCategory) {
RoomCategory.INVITE -> isInviteExpanded
RoomCategory.FAVOURITE -> isFavouriteRoomsExpanded RoomCategory.FAVOURITE -> isFavouriteRoomsExpanded
RoomCategory.DIRECT -> isDirectRoomsExpanded RoomCategory.DIRECT -> isDirectRoomsExpanded
RoomCategory.GROUP -> isGroupRoomsExpanded RoomCategory.GROUP -> isGroupRoomsExpanded
@ -45,6 +47,7 @@ data class RoomListViewState(


fun toggle(roomCategory: RoomCategory): RoomListViewState { fun toggle(roomCategory: RoomCategory): RoomListViewState {
return when (roomCategory) { return when (roomCategory) {
RoomCategory.INVITE -> copy(isInviteExpanded = !isInviteExpanded)
RoomCategory.FAVOURITE -> copy(isFavouriteRoomsExpanded = !isFavouriteRoomsExpanded) RoomCategory.FAVOURITE -> copy(isFavouriteRoomsExpanded = !isFavouriteRoomsExpanded)
RoomCategory.DIRECT -> copy(isDirectRoomsExpanded = !isDirectRoomsExpanded) RoomCategory.DIRECT -> copy(isDirectRoomsExpanded = !isDirectRoomsExpanded)
RoomCategory.GROUP -> copy(isGroupRoomsExpanded = !isGroupRoomsExpanded) RoomCategory.GROUP -> copy(isGroupRoomsExpanded = !isGroupRoomsExpanded)
@ -57,6 +60,7 @@ data class RoomListViewState(
typealias RoomSummaries = LinkedHashMap<RoomCategory, List<RoomSummary>> typealias RoomSummaries = LinkedHashMap<RoomCategory, List<RoomSummary>>


enum class RoomCategory(@StringRes val titleRes: Int) { enum class RoomCategory(@StringRes val titleRes: Int) {
INVITE(R.string.invitations_header),
FAVOURITE(R.string.bottom_action_favourites), FAVOURITE(R.string.bottom_action_favourites),
DIRECT(R.string.bottom_action_people), DIRECT(R.string.bottom_action_people),
GROUP(R.string.bottom_action_rooms), GROUP(R.string.bottom_action_rooms),

View File

@ -0,0 +1,68 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.riotredesign.features.invite

import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.setPadding
import androidx.core.view.updateLayoutParams
import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotredesign.R
import im.vector.riotredesign.features.home.AvatarRenderer
import kotlinx.android.synthetic.main.vector_invite_view.view.*

class VectorInviteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
: ConstraintLayout(context, attrs, defStyle) {

interface Callback {
fun onAcceptInvite()
fun onRejectInvite()
}

enum class Mode {
LARGE,
SMALL
}

var callback: Callback? = null

init {
View.inflate(context, R.layout.vector_invite_view, this)
setBackgroundColor(Color.WHITE)
inviteRejectView.setOnClickListener { callback?.onRejectInvite() }
inviteAcceptView.setOnClickListener { callback?.onAcceptInvite() }
}

fun render(sender: User, mode: Mode = Mode.LARGE) {
if (mode == Mode.LARGE) {
updateLayoutParams { height = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT }
AvatarRenderer.render(sender.avatarUrl, sender.userId, sender.displayName, inviteAvatarView)
inviteIdentifierView.text = sender.userId
inviteNameView.text = sender.displayName
inviteLabelView.text = context.getString(R.string.send_you_invite)
} else {
updateLayoutParams { height = ConstraintLayout.LayoutParams.WRAP_CONTENT }
inviteAvatarView.visibility = View.GONE
inviteIdentifierView.visibility = View.GONE
inviteNameView.visibility = View.GONE
inviteLabelView.text = context.getString(R.string.invited_by, sender.userId)
}
}
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->

<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/transparent"/>
<stroke android:width="1dp" android:color="?attr/colorAccent" />
</shape>
</item>
</selector>

View File

@ -139,4 +139,16 @@


</RelativeLayout> </RelativeLayout>


<im.vector.riotredesign.features.invite.VectorInviteView
android:id="@+id/inviteView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
app:layout_constraintVertical_bias="1.0" />

</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">

<ImageView
android:id="@+id/inviteAvatarView"
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="86dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />

<TextView
android:id="@+id/inviteNameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:textAppearance="@style/TextAppearance.Vector.Title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/inviteAvatarView"
tools:text="Matthew" />

<TextView
android:id="@+id/inviteIdentifierView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/TextAppearance.Vector.Subtitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/inviteNameView"
tools:text="\@matthew:matrix.org" />

<TextView
android:id="@+id/inviteLabelView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="@string/send_you_invite"
android:textAppearance="@style/TextAppearance.Vector.Subtitle2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/inviteIdentifierView" />

<Button
android:id="@+id/inviteRejectView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginRight="4dp"
android:text="@string/reject"
app:layout_constraintEnd_toStartOf="@+id/inviteAcceptView"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/inviteLabelView" />

<Button
android:id="@+id/inviteAcceptView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:text="@string/accept"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@+id/inviteRejectView"
app:layout_constraintTop_toTopOf="@id/inviteRejectView" />

<Space
android:layout_height="16dp"
android:layout_width="match_parent"
app:layout_constraintTop_toBottomOf="@id/inviteAcceptView"/>


</merge>

View File

@ -3,7 +3,6 @@
<color name="pale_grey">#f2f5f8</color> <color name="pale_grey">#f2f5f8</color>
<color name="dark">#2e3649</color> <color name="dark">#2e3649</color>
<color name="pale_teal">#7ac9a1</color> <color name="pale_teal">#7ac9a1</color>
<color name="black">#212121</color>
<color name="deep_sky_blue">#007aff</color> <color name="deep_sky_blue">#007aff</color>
<color name="rosy_pink">#f56679</color> <color name="rosy_pink">#f56679</color>
<color name="bluey_grey">#a5a5a6</color> <color name="bluey_grey">#a5a5a6</color>
@ -18,4 +17,9 @@
<color name="brown_grey">#a5a5a5</color> <color name="brown_grey">#a5a5a5</color>
<color name="grey_lynch">#61708B</color> <color name="grey_lynch">#61708B</color>


<color name="black">#000000</color>
<color name="black_87">#de000000</color>
<color name="black_38">#61000000</color>
<color name="black_37">#5d000000</color>

</resources> </resources>

View File

@ -4,6 +4,8 @@
<!-- Strings not defined in Riot --> <!-- Strings not defined in Riot -->
<string name="global_retry">"Retry"</string> <string name="global_retry">"Retry"</string>
<string name="room_list_empty">"Join a room to start using the app."</string> <string name="room_list_empty">"Join a room to start using the app."</string>
<string name="send_you_invite">"Sent you an invitation"</string>
<string name="invited_by">Invited by %s</string>




<string name="title_activity_emoji_reaction_picker">Reactions</string> <string name="title_activity_emoji_reaction_picker">Reactions</string>

View File

@ -1,11 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>


<style name="Widget.Button" parent="Widget.AppCompat.Button"> <style name="Widget.Vector.Button" parent="Widget.AppCompat.Button">
<item name="android:minHeight">48dp</item> <item name="android:minHeight">48dp</item>
<item name="android:background">?attr/colorAccent</item> <item name="android:minWidth">128dp</item>
<item name="android:textColor">@android:color/white</item> <item name="android:paddingRight">8dp</item>
<item name="android:paddingLeft">8dp</item>
<item name="android:textAllCaps">true</item>
</style> </style>



</resources> </resources>

View File

@ -1,5 +1,40 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!--
<resources> ~ 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.
-->


<resources xmlns:android="http://schemas.android.com/apk/res/android">

<style name="TextAppearance.Vector.Title" parent="TextAppearance.AppCompat">
<item name="android:textSize">16sp</item>
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textStyle">normal</item>
<item name="android:textColor">@color/black_87</item>
</style>

<style name="TextAppearance.Vector.Subtitle" parent="TextAppearance.AppCompat">
<item name="android:textSize">14sp</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:textStyle">normal</item>
<item name="android:textColor">@color/black_37</item>
</style>

<style name="TextAppearance.Vector.Subtitle2" parent="TextAppearance.AppCompat">
<item name="android:textSize">14sp</item>
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textStyle">normal</item>
<item name="android:textColor">@color/black_38</item>
</style>


</resources> </resources>

View File

@ -6,12 +6,14 @@
<style name="AppTheme.Base.Light" parent="Theme.MaterialComponents.Light.NoActionBar.Bridge"> <style name="AppTheme.Base.Light" parent="Theme.MaterialComponents.Light.NoActionBar.Bridge">
<item name="colorPrimaryDark">@color/primary_color_dark_light</item> <item name="colorPrimaryDark">@color/primary_color_dark_light</item>
<item name="colorPrimary">@color/primary_color_light</item> <item name="colorPrimary">@color/primary_color_light</item>
<item name="colorPrimaryVariant">@color/primary_color_dark_light</item>
<item name="colorSecondary">@color/accent_color_light</item>
<item name="colorOnSecondary">@android:color/white</item>
<item name="colorAccent">@color/accent_color_light</item> <item name="colorAccent">@color/accent_color_light</item>


<item name="android:textColorPrimary">@color/primary_text_color_selector_light</item> <item name="android:textColorPrimary">@color/black_87</item>
<item name="android:textColorSecondary">@color/riot_secondary_text_color_light</item> <item name="android:textColorSecondary">@color/black_38</item>
<!-- Default color for text View --> <item name="android:textColorTertiary">@color/black_37</item>
<item name="android:textColorTertiary">@color/riot_tertiary_text_color_light</item>


<item name="android:textColorLink">@color/link_color_light</item> <item name="android:textColorLink">@color/link_color_light</item>


@ -22,6 +24,9 @@
<item name="android:colorBackground">@color/riot_primary_background_color_light</item> <item name="android:colorBackground">@color/riot_primary_background_color_light</item>
<item name="vctr_bottom_nav_background_color">#FFF3F8FD</item> <item name="vctr_bottom_nav_background_color">#FFF3F8FD</item>


<!-- default button -->
<item name="android:buttonStyle">@style/Widget.Vector.Button</item>

<!-- waiting view background --> <!-- waiting view background -->
<item name="vctr_waiting_background_color">#AAAAAAAA</item> <item name="vctr_waiting_background_color">#AAAAAAAA</item>



View File

@ -24,6 +24,8 @@
<item name="vctr_bottom_nav_background_color">@color/riot_primary_background_color_status <item name="vctr_bottom_nav_background_color">@color/riot_primary_background_color_status
</item> </item>


<item name="buttonStyle">@style/Widget.Vector.Button</item>

<!-- waiting view background --> <!-- waiting view background -->
<item name="vctr_waiting_background_color">#AAAAAAAA</item> <item name="vctr_waiting_background_color">#AAAAAAAA</item>