Merge pull request #843 from vector-im/feature/room_profile

Feature/room profile
This commit is contained in:
ganfra 2020-01-15 19:17:50 +01:00 committed by GitHub
commit f128ed437f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
136 changed files with 3625 additions and 393 deletions

View file

@ -3,6 +3,8 @@ Changes in RiotX 0.13.0 (2020-XX-XX)
Features ✨:
- Send and render typing events (#564)
- Create Room Profile screen (#54)
- Create Room Member Profile screen (#59)
Improvements 🙌:
- Render events m.room.encryption and m.room.guest_access in the timeline

View file

@ -22,3 +22,9 @@ import io.reactivex.Observable
fun <T : Any> Observable<Optional<T>>.unwrap(): Observable<T> {
return filter { it.hasValue() }.map { it.get() }
}
fun <T : Any, U : Any> Observable<Optional<T>>.mapOptional(fn: (T) -> U?): Observable<Optional<U>> {
return map {
it.map(fn)
}
}

View file

@ -16,11 +16,12 @@
package im.vector.matrix.rx
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
import im.vector.matrix.android.api.session.room.send.UserDraft
@ -37,7 +38,7 @@ class RxRoom(private val room: Room) {
.startWith(room.roomSummary().toOptional())
}
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMember>> {
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
return room.getRoomMembersLive(queryParams).asObservable()
.startWith(room.getRoomMembers(queryParams))
}
@ -52,6 +53,11 @@ class RxRoom(private val room: Room) {
.startWith(room.getTimeLineEvent(eventId).toOptional())
}
fun liveStateEvent(eventType: String): Observable<Optional<Event>> {
return room.getStateEventLive(eventType).asObservable()
.startWith(room.getStateEvent(eventType).toOptional())
}
fun liveReadMarker(): Observable<Optional<String>> {
return room.getReadMarkerLive().asObservable()
}

View file

@ -26,7 +26,9 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.sync.SyncState
import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.util.toOptional
import io.reactivex.Observable
import io.reactivex.Single
@ -56,7 +58,8 @@ class RxSession(private val session: Session) {
}
fun liveUser(userId: String): Observable<Optional<User>> {
return session.getUserLive(userId).asObservable().distinctUntilChanged()
return session.getUserLive(userId).asObservable()
.startWith(session.getUser(userId).toOptional())
}
fun liveUsers(): Observable<List<User>> {
@ -91,6 +94,10 @@ class RxSession(private val session: Session) {
searchOnServer: Boolean): Single<Optional<String>> = singleBuilder {
session.getRoomIdByAlias(roomAlias, searchOnServer, it)
}
fun getProfileInfo(userId: String): Single<JsonDict> = singleBuilder {
session.getProfile(userId, it)
}
}
fun Session.rx(): RxSession {

View file

@ -21,7 +21,7 @@ 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.toContent
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.RoomMemberSummary
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
import kotlin.random.Random
@ -63,7 +63,7 @@ object RoomDataHelper {
}
fun createFakeRoomMemberEvent(): Event {
val roomMember = RoomMember(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
val roomMember = RoomMemberSummary(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember)
}
}

View file

@ -16,7 +16,8 @@
package im.vector.matrix.android.api.pushrules
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.PowerLevels
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
class SenderNotificationPermissionCondition(val key: String) : Condition(Kind.sender_notification_permission) {
@ -28,7 +29,8 @@ class SenderNotificationPermissionCondition(val key: String) : Condition(Kind.se
return "User power level <$key>"
}
fun isSatisfied(event: Event, powerLevels: PowerLevels): Boolean {
return event.senderId != null && powerLevels.getUserPowerLevel(event.senderId) >= powerLevels.notificationLevel(key)
fun isSatisfied(event: Event, powerLevels: PowerLevelsContent): Boolean {
val powerLevelsHelper = PowerLevelsHelper(powerLevels)
return event.senderId != null && powerLevelsHelper.getUserPowerLevel(event.senderId) >= powerLevelsHelper.notificationLevel(key)
}
}

View file

@ -28,6 +28,7 @@ import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.api.session.group.GroupService
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
import im.vector.matrix.android.api.session.profile.ProfileService
import im.vector.matrix.android.api.session.pushers.PushersService
import im.vector.matrix.android.api.session.room.RoomDirectoryService
import im.vector.matrix.android.api.session.room.RoomService
@ -51,6 +52,7 @@ interface Session :
SignOutService,
FilterService,
FileService,
ProfileService,
PushRuleService,
PushersService,
InitialSyncProgressService,

View file

@ -0,0 +1,56 @@
/*
* Copyright 2020 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.profile
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.api.util.Optional
/**
* This interface defines methods to handling profile information. It's implemented at the session level.
*/
interface ProfileService {
companion object Constants {
const val DISPLAY_NAME_KEY = "displayname"
const val AVATAR_URL_KEY = "avatar_url"
}
/**
* Return the current dispayname for this user
* @param userId the userId param to look for
*
*/
fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable
/**
* Return the current avatarUrl for this user.
* @param userId the userId param to look for
*
*/
fun getAvatarUrl(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable
/**
* Get the combined profile information for this user.
* This may return keys which are not limited to displayname or avatar_url.
* @param userId the userId param to look for
*
*/
fun getProfile(userId: String, matrixCallback: MatrixCallback<JsonDict>): Cancelable
}

View file

@ -18,7 +18,7 @@ package im.vector.matrix.android.api.session.room.members
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.api.util.Cancelable
/**
@ -38,21 +38,21 @@ interface MembershipService {
*
* @return the roomMember with userId or null
*/
fun getRoomMember(userId: String): RoomMember?
fun getRoomMember(userId: String): RoomMemberSummary?
/**
* Return all the roomMembers of the room with params
* @param queryParams the params to query for
* @return a roomMember list.
*/
fun getRoomMembers(queryParams: RoomMemberQueryParams): List<RoomMember>
fun getRoomMembers(queryParams: RoomMemberQueryParams): List<RoomMemberSummary>
/**
* Return all the roomMembers of the room filtered by memberships
* @param queryParams the params to query for
* @return a [LiveData] of roomMember list.
*/
fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMember>>
fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMemberSummary>>
fun getNumberOfJoinedMembers(): Int

View file

@ -29,11 +29,13 @@ fun roomMemberQueryParams(init: (RoomMemberQueryParams.Builder.() -> Unit) = {})
data class RoomMemberQueryParams(
val displayName: QueryStringValue,
val memberships: List<Membership>,
val userId: QueryStringValue,
val excludeSelf: Boolean
) {
class Builder {
var userId: QueryStringValue = QueryStringValue.NoCondition
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
var memberships: List<Membership> = Membership.all()
var excludeSelf: Boolean = false
@ -41,6 +43,7 @@ data class RoomMemberQueryParams(
fun build() = RoomMemberQueryParams(
displayName = displayName,
memberships = memberships,
userId = userId,
excludeSelf = excludeSelf
)
}

View file

@ -1,121 +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
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.EventType
/**
* Class representing the EventType.EVENT_TYPE_STATE_ROOM_POWER_LEVELS state event content.
*/
@JsonClass(generateAdapter = true)
data class PowerLevels(
@Json(name = "ban") val ban: Int = 50,
@Json(name = "kick") val kick: Int = 50,
@Json(name = "invite") val invite: Int = 50,
@Json(name = "redact") val redact: Int = 50,
@Json(name = "events_default") val eventsDefault: Int = 0,
@Json(name = "events") val events: MutableMap<String, Int> = HashMap(),
@Json(name = "users_default") val usersDefault: Int = 0,
@Json(name = "users") val users: MutableMap<String, Int> = HashMap(),
@Json(name = "state_default") val stateDefault: Int = 50,
@Json(name = "notifications") val notifications: Map<String, Any> = HashMap()
) {
/**
* Returns the user power level of a dedicated user Id
*
* @param userId the user id
* @return the power level
*/
fun getUserPowerLevel(userId: String): Int {
return users.getOrElse(userId) { usersDefault }
}
/**
* Updates the user power levels of a dedicated user id
*
* @param userId the user
* @param powerLevel the new power level
*/
fun setUserPowerLevel(userId: String, powerLevel: Int) {
users[userId] = powerLevel
}
/**
* Tell if an user can send an event of type 'eventTypeString'.
*
* @param eventTypeString the event type (in Event.EVENT_TYPE_XXX values)
* @param userId the user id
* @return true if the user can send the event
*/
fun maySendEventOfType(eventTypeString: String, userId: String): Boolean {
return if (eventTypeString.isNotEmpty() && userId.isNotEmpty()) {
getUserPowerLevel(userId) >= minimumPowerLevelForSendingEventAsMessage(eventTypeString)
} else false
}
/**
* Tells if an user can send a room message.
*
* @param userId the user id
* @return true if the user can send a room message
*/
fun maySendMessage(userId: String): Boolean {
return maySendEventOfType(EventType.MESSAGE, userId)
}
/**
* Helper to get the minimum power level the user must have to send an event of the given type
* as a message.
*
* @param eventTypeString the type of event (in Event.EVENT_TYPE_XXX values)
* @return the required minimum power level.
*/
fun minimumPowerLevelForSendingEventAsMessage(eventTypeString: String?): Int {
return events[eventTypeString] ?: eventsDefault
}
/**
* Helper to get the minimum power level the user must have to send an event of the given type
* as a state event.
*
* @param eventTypeString the type of event (in Event.EVENT_TYPE_STATE_ values).
* @return the required minimum power level.
*/
fun minimumPowerLevelForSendingEventAsStateEvent(eventTypeString: String?): Int {
return events[eventTypeString] ?: stateDefault
}
/**
* Get the notification level for a dedicated key.
*
* @param key the notification key
* @return the level
*/
fun notificationLevel(key: String): Int {
val valAsVoid = notifications[key] ?: return 50
// the first implementation was a string value
return if (valAsVoid is String) {
valAsVoid.toInt()
} else {
valAsVoid as Int
}
}
}

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.api.session.room.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsConstants
/**
* Class representing the EventType.EVENT_TYPE_STATE_ROOM_POWER_LEVELS state event content.
*/
@JsonClass(generateAdapter = true)
data class PowerLevelsContent(
@Json(name = "ban") val ban: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
@Json(name = "kick") val kick: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
@Json(name = "invite") val invite: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
@Json(name = "redact") val redact: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
@Json(name = "events_default") val eventsDefault: Int = PowerLevelsConstants.DEFAULT_ROOM_USER_LEVEL,
@Json(name = "events") val events: MutableMap<String, Int> = HashMap(),
@Json(name = "users_default") val usersDefault: Int = PowerLevelsConstants.DEFAULT_ROOM_USER_LEVEL,
@Json(name = "users") val users: MutableMap<String, Int> = HashMap(),
@Json(name = "state_default") val stateDefault: Int = PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL,
@Json(name = "notifications") val notifications: Map<String, Any> = HashMap()
)

View file

@ -19,7 +19,7 @@ package im.vector.matrix.android.api.session.room.model
/**
* Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content
*/
data class RoomMember(
data class RoomMemberSummary(
val membership: Membership,
val userId: String,
val displayName: String? = null,

View file

@ -32,6 +32,8 @@ data class RoomSummary(
val canonicalAlias: String? = null,
val aliases: List<String> = emptyList(),
val isDirect: Boolean = false,
val joinedMembersCount: Int? = 0,
val invitedMembersCount: Int? = 0,
val latestPreviewableEvent: TimelineEvent? = null,
val otherMemberIds: List<String> = emptyList(),
val notificationCount: Int = 0,

View file

@ -24,7 +24,7 @@ import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
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.toContent
import im.vector.matrix.android.api.session.room.model.PowerLevels
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
@ -115,7 +115,7 @@ class CreateRoomParams {
* The power level content to override in the default power level event
*/
@Json(name = "power_level_content_override")
var powerLevelContentOverride: PowerLevels? = null
var powerLevelContentOverride: PowerLevelsContent? = null
/**
* Add the crypto algorithm to the room creation parameters.

View file

@ -0,0 +1,25 @@
/*
* Copyright 2020 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.powerlevels
object PowerLevelsConstants {
const val DEFAULT_ROOM_ADMIN_LEVEL = 100
const val DEFAULT_ROOM_MODERATOR_LEVEL = 50
const val DEFAULT_ROOM_USER_LEVEL = 0
}

View file

@ -0,0 +1,74 @@
/*
* Copyright 2020 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.powerlevels
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
/**
* This class is an helper around PowerLevelsContent.
*/
class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
/**
* Returns the user power level of a dedicated user Id
*
* @param userId the user id
* @return the power level
*/
fun getUserPowerLevel(userId: String): Int {
return powerLevelsContent.users.getOrElse(userId) {
powerLevelsContent.usersDefault
}
}
/**
* Tell if an user can send an event of a certain type
*
* @param eventType the event type to check for
* @param userId the user id
* @return true if the user can send this type of event
*/
fun isAllowedToSend(eventType: String, userId: String): Boolean {
return if (eventType.isNotEmpty() && userId.isNotEmpty()) {
val powerLevel = getUserPowerLevel(userId)
val minimumPowerLevel = powerLevelsContent.events[eventType]
?: if (EventType.isStateEvent(eventType)) {
powerLevelsContent.stateDefault
} else {
powerLevelsContent.eventsDefault
}
powerLevel >= minimumPowerLevel
} else false
}
/**
* Get the notification level for a dedicated key.
*
* @param key the notification key
* @return the level
*/
fun notificationLevel(key: String): Int {
return when (val value = powerLevelsContent.notifications[key]) {
// the first implementation was a string value
is String -> value.toInt()
is Int -> value
else -> PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL
}
}
}

View file

@ -16,8 +16,10 @@
package im.vector.matrix.android.api.session.room.state
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.util.Optional
interface StateService {
@ -32,4 +34,6 @@ interface StateService {
fun enableEncryption(algorithm: String, callback: MatrixCallback<Unit>)
fun getStateEvent(eventType: String): Event?
fun getStateEventLive(eventType: String): LiveData<Optional<Event>>
}

View file

@ -18,7 +18,7 @@ package im.vector.matrix.android.api.util
import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
import im.vector.matrix.android.api.session.user.model.User
@ -147,4 +147,4 @@ fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, ava
fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name, avatarUrl)
fun RoomMember.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)

View file

@ -27,6 +27,14 @@ data class Optional<T : Any> constructor(private val value: T?) {
return value
}
fun <U : Any> map(fn: (T) -> U?): Optional<U> {
return if (value == null) {
from(null)
} else {
from(fn(value))
}
}
fun getOrElse(fn: () -> T): T {
return value ?: fn()
}

View file

@ -38,7 +38,7 @@ 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.RoomHistoryVisibility
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.internal.crypto.actions.MegolmSessionDataImporter
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting
@ -64,7 +64,7 @@ import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
@ -697,9 +697,9 @@ internal class DefaultCryptoService @Inject constructor(
&& shouldEncryptForInvitedMembers(roomId)
userIds = if (encryptForInvitedMembers) {
RoomMembers(realm, roomId).getActiveRoomMemberIds()
RoomMemberHelper(realm, roomId).getActiveRoomMemberIds()
} else {
RoomMembers(realm, roomId).getJoinedRoomMemberIds()
RoomMemberHelper(realm, roomId).getJoinedRoomMemberIds()
}
}
return userIds
@ -722,7 +722,7 @@ internal class DefaultCryptoService @Inject constructor(
return
}
event.stateKey?.let { userId ->
val roomMember: RoomMember? = event.content.toModel()
val roomMember: RoomMemberSummary? = event.content.toModel()
val membership = roomMember?.membership
if (membership == Membership.JOIN) {
// make sure we are tracking the deviceList for this user.

View file

@ -24,7 +24,7 @@ import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.query.fastContains
import im.vector.matrix.android.internal.extensions.assertIsManaged
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) {
chunks.remove(chunkEntity)
@ -59,7 +59,7 @@ internal fun RoomEntity.addSendingEvent(event: Event) {
val eventEntity = event.toEntity(roomId).apply {
this.sendState = SendState.UNSENT
}
val roomMembers = RoomMembers(realm, roomId)
val roomMembers = RoomMemberHelper(realm, roomId)
val myUser = roomMembers.getLastRoomMember(senderId)
val localId = TimelineEventEntity.nextId(realm)
val timelineEventEntity = TimelineEventEntity(localId).also {

View file

@ -26,7 +26,7 @@ import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.extensions.assertIsManaged
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
import io.realm.RealmList
import io.realm.RealmQuery
import javax.inject.Inject
@ -128,7 +128,7 @@ internal class TimelineEventSenderVisitor @Inject constructor() {
ContentMapper.map(senderRoomMemberContent).toModel<RoomMemberContent>()?.also {
result.senderAvatar = it.avatarUrl
result.senderName = it.displayName
result.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
result.isUniqueDisplayName = RoomMemberHelper(realm, roomId).isUniqueDisplayName(it.displayName)
}
// We try to fallback on prev content if we got a room member state events with null fields
if (root?.type == EventType.STATE_ROOM_MEMBER) {
@ -138,7 +138,7 @@ internal class TimelineEventSenderVisitor @Inject constructor() {
}
if (result.senderName == null && it.displayName != null) {
result.senderName = it.displayName
result.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
result.isUniqueDisplayName = RoomMemberHelper(realm, roomId).isUniqueDisplayName(it.displayName)
}
}
}

View file

@ -16,21 +16,21 @@
package im.vector.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.internal.database.model.RoomMemberEntity
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
internal object RoomMemberMapper {
internal object RoomMemberSummaryMapper {
fun map(roomMemberEntity: RoomMemberEntity): RoomMember {
return RoomMember(
userId = roomMemberEntity.userId,
avatarUrl = roomMemberEntity.avatarUrl,
displayName = roomMemberEntity.displayName,
membership = roomMemberEntity.membership
fun map(roomMemberSummaryEntity: RoomMemberSummaryEntity): RoomMemberSummary {
return RoomMemberSummary(
userId = roomMemberSummaryEntity.userId,
avatarUrl = roomMemberSummaryEntity.avatarUrl,
displayName = roomMemberSummaryEntity.displayName,
membership = roomMemberSummaryEntity.membership
)
}
}
internal fun RoomMemberEntity.asDomain(): RoomMember {
return RoomMemberMapper.map(this)
internal fun RoomMemberSummaryEntity.asDomain(): RoomMemberSummary {
return RoomMemberSummaryMapper.map(this)
}

View file

@ -60,6 +60,8 @@ internal class RoomSummaryMapper @Inject constructor(
avatarUrl = roomSummaryEntity.avatarUrl ?: "",
isDirect = roomSummaryEntity.isDirect,
latestPreviewableEvent = latestEvent,
joinedMembersCount = roomSummaryEntity.joinedMembersCount,
invitedMembersCount = roomSummaryEntity.invitedMembersCount,
otherMemberIds = roomSummaryEntity.otherMemberIds.toList(),
highlightCount = roomSummaryEntity.highlightCount,
notificationCount = roomSummaryEntity.notificationCount,

View file

@ -21,13 +21,13 @@ import io.realm.RealmObject
import io.realm.annotations.Index
import io.realm.annotations.PrimaryKey
internal open class RoomMemberEntity(@PrimaryKey var primaryKey: String = "",
@Index var userId: String = "",
@Index var roomId: String = "",
var displayName: String = "",
var avatarUrl: String = "",
var reason: String? = null,
var isDirect: Boolean = false
internal open class RoomMemberSummaryEntity(@PrimaryKey var primaryKey: String = "",
@Index var userId: String = "",
@Index var roomId: String = "",
var displayName: String? = null,
var avatarUrl: String? = null,
var reason: String? = null,
var isDirect: Boolean = false
) : RealmObject() {
private var membershipStr: String = Membership.NONE.name

View file

@ -50,6 +50,6 @@ import io.realm.annotations.RealmModule
UserDraftsEntity::class,
DraftEntity::class,
HomeServerCapabilitiesEntity::class,
RoomMemberEntity::class
RoomMemberSummaryEntity::class
])
internal class SessionRealmModule

View file

@ -60,20 +60,7 @@ internal fun EventEntity.Companion.types(realm: Realm,
return query
}
internal fun RealmQuery<EventEntity>.next(from: Int? = null, strict: Boolean = true): EventEntity? {
if (from != null) {
if (strict) {
this.greaterThan(EventEntityFields.STATE_INDEX, from)
} else {
this.greaterThanOrEqualTo(EventEntityFields.STATE_INDEX, from)
}
}
return this
.sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING)
.findFirst()
}
internal fun RealmQuery<EventEntity>.prev(since: Int? = null, strict: Boolean = false): EventEntity? {
internal fun RealmQuery<EventEntity>.descending(since: Int? = null, strict: Boolean = false): RealmQuery<EventEntity> {
if (since != null) {
if (strict) {
this.lessThan(EventEntityFields.STATE_INDEX, since)
@ -81,9 +68,26 @@ internal fun RealmQuery<EventEntity>.prev(since: Int? = null, strict: Boolean =
this.lessThanOrEqualTo(EventEntityFields.STATE_INDEX, since)
}
}
return this
.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
.findFirst()
return this.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
}
internal fun RealmQuery<EventEntity>.ascending(from: Int? = null, strict: Boolean = true): RealmQuery<EventEntity> {
if (from != null) {
if (strict) {
this.greaterThan(EventEntityFields.STATE_INDEX, from)
} else {
this.greaterThanOrEqualTo(EventEntityFields.STATE_INDEX, from)
}
}
return this.sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING)
}
internal fun RealmQuery<EventEntity>.next(from: Int? = null, strict: Boolean = true): EventEntity? {
return this.ascending(from, strict).findFirst()
}
internal fun RealmQuery<EventEntity>.prev(since: Int? = null, strict: Boolean = false): EventEntity? {
return descending(since, strict).findFirst()
}
internal fun RealmList<EventEntity>.find(eventId: String): EventEntity? {

View file

@ -16,19 +16,19 @@
package im.vector.matrix.android.internal.database.query
import im.vector.matrix.android.internal.database.model.RoomMemberEntity
import im.vector.matrix.android.internal.database.model.RoomMemberEntityFields
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.kotlin.where
internal fun RoomMemberEntity.Companion.where(realm: Realm, roomId: String, userId: String? = null): RealmQuery<RoomMemberEntity> {
internal fun RoomMemberSummaryEntity.Companion.where(realm: Realm, roomId: String, userId: String? = null): RealmQuery<RoomMemberSummaryEntity> {
val query = realm
.where<RoomMemberEntity>()
.equalTo(RoomMemberEntityFields.ROOM_ID, roomId)
.where<RoomMemberSummaryEntity>()
.equalTo(RoomMemberSummaryEntityFields.ROOM_ID, roomId)
if (userId != null) {
query.equalTo(RoomMemberEntityFields.USER_ID, userId)
query.equalTo(RoomMemberSummaryEntityFields.USER_ID, userId)
}
return query
}

View file

@ -33,6 +33,7 @@ import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.api.session.group.GroupService
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
import im.vector.matrix.android.api.session.profile.ProfileService
import im.vector.matrix.android.api.session.pushers.PushersService
import im.vector.matrix.android.api.session.room.RoomDirectoryService
import im.vector.matrix.android.api.session.room.RoomService
@ -80,6 +81,7 @@ internal class DefaultSession @Inject constructor(
private val cryptoService: Lazy<DefaultCryptoService>,
private val fileService: Lazy<FileService>,
private val secureStorageService: Lazy<SecureStorageService>,
private val profileService: Lazy<ProfileService>,
private val syncThreadProvider: Provider<SyncThread>,
private val contentUrlResolver: ContentUrlResolver,
private val syncTokenStore: SyncTokenStore,
@ -101,7 +103,8 @@ internal class DefaultSession @Inject constructor(
FileService by fileService.get(),
InitialSyncProgressService by initialSyncProgressService.get(),
SecureStorageService by secureStorageService.get(),
HomeServerCapabilitiesService by homeServerCapabilitiesService.get() {
HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
ProfileService by profileService.get() {
private var isOpen = false

View file

@ -31,6 +31,7 @@ import im.vector.matrix.android.internal.session.filter.FilterModule
import im.vector.matrix.android.internal.session.group.GetGroupDataWorker
import im.vector.matrix.android.internal.session.group.GroupModule
import im.vector.matrix.android.internal.session.homeserver.HomeServerCapabilitiesModule
import im.vector.matrix.android.internal.session.profile.ProfileModule
import im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker
import im.vector.matrix.android.internal.session.pushers.PushersModule
import im.vector.matrix.android.internal.session.room.RoomModule
@ -64,6 +65,7 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
CryptoModule::class,
PushersModule::class,
AccountDataModule::class,
ProfileModule::class,
SessionAssistedInjectModule::class
]
)

View file

@ -0,0 +1,76 @@
/*
* Copyright 2020 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.profile
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.profile.ProfileService
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import javax.inject.Inject
internal class DefaultProfileService @Inject constructor(private val taskExecutor: TaskExecutor,
private val getProfileInfoTask: GetProfileInfoTask) : ProfileService {
override fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable {
val params = GetProfileInfoTask.Params(userId)
return getProfileInfoTask
.configureWith(params) {
this.callback = object : MatrixCallback<JsonDict> {
override fun onSuccess(data: JsonDict) {
val displayName = data[ProfileService.DISPLAY_NAME_KEY] as? String
matrixCallback.onSuccess(Optional.from(displayName))
}
override fun onFailure(failure: Throwable) {
matrixCallback.onFailure(failure)
}
}
}
.executeBy(taskExecutor)
}
override fun getAvatarUrl(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable {
val params = GetProfileInfoTask.Params(userId)
return getProfileInfoTask
.configureWith(params) {
this.callback = object : MatrixCallback<JsonDict> {
override fun onSuccess(data: JsonDict) {
val avatarUrl = data[ProfileService.AVATAR_URL_KEY] as? String
matrixCallback.onSuccess(Optional.from(avatarUrl))
}
override fun onFailure(failure: Throwable) {
matrixCallback.onFailure(failure)
}
}
}
.executeBy(taskExecutor)
}
override fun getProfile(userId: String, matrixCallback: MatrixCallback<JsonDict>): Cancelable {
val params = GetProfileInfoTask.Params(userId)
return getProfileInfoTask
.configureWith(params) {
this.callback = matrixCallback
}
.executeBy(taskExecutor)
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright 2020 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.profile
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal abstract class GetProfileInfoTask : Task<GetProfileInfoTask.Params, JsonDict> {
data class Params(
val userId: String
)
}
internal class DefaultGetProfileInfoTask @Inject constructor(private val profileAPI: ProfileAPI,
private val eventBus: EventBus) : GetProfileInfoTask() {
override suspend fun execute(params: Params): JsonDict {
return executeRequest(eventBus) {
apiCall = profileAPI.getProfile(params.userId)
}
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright 2020 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.profile
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.network.NetworkConstants
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path
interface ProfileAPI {
/**
* Get the combined profile information for this user.
* This API may be used to fetch the user's own profile information or other users; either locally or on remote homeservers.
* This API may return keys which are not limited to displayname or avatar_url.
* @param userId the user id to fetch profile info
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}")
fun getProfile(@Path("userId") userId: String): Call<JsonDict>
}

View file

@ -0,0 +1,45 @@
/*
* Copyright 2020 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.profile
import dagger.Binds
import dagger.Module
import dagger.Provides
import im.vector.matrix.android.api.session.profile.ProfileService
import im.vector.matrix.android.internal.session.SessionScope
import retrofit2.Retrofit
@Module
internal abstract class ProfileModule {
@Module
companion object {
@Provides
@JvmStatic
@SessionScope
fun providesProfileAPI(retrofit: Retrofit): ProfileAPI {
return retrofit.create(ProfileAPI::class.java)
}
}
@Binds
abstract fun bindProfileService(service: DefaultProfileService): ProfileService
@Binds
abstract fun bindGetProfileTask(task: DefaultGetProfileInfoTask): GetProfileInfoTask
}

View file

@ -22,11 +22,11 @@ import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.RoomAvatarContent
import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.RoomMemberEntityFields
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
import javax.inject.Inject
internal class RoomAvatarResolver @Inject constructor(private val monarchy: Monarchy,
@ -45,13 +45,13 @@ internal class RoomAvatarResolver @Inject constructor(private val monarchy: Mona
if (!res.isNullOrEmpty()) {
return@doWithRealm
}
val roomMembers = RoomMembers(realm, roomId)
val roomMembers = RoomMemberHelper(realm, roomId)
val members = roomMembers.queryActiveRoomMembersEvent().findAll()
// detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat)
if (members.size == 1) {
res = members.firstOrNull()?.avatarUrl
} else if (members.size == 2) {
val firstOtherMember = members.where().notEqualTo(RoomMemberEntityFields.USER_ID, userId).findFirst()
val firstOtherMember = members.where().notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId).findFirst()
res = firstOtherMember?.avatarUrl
}
}

View file

@ -24,14 +24,14 @@ 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.RoomTopicContent
import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.model.*
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.RoomMemberEntityFields
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.query.*
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
import im.vector.matrix.android.internal.session.sync.RoomSyncHandler
import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary
import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications
@ -115,9 +115,9 @@ internal class RoomSummaryUpdater @Inject constructor(
roomSummaryEntity.typingUserIds.addAll(ephemeralResult?.typingUserIds.orEmpty())
if (updateMembers) {
val otherRoomMembers = RoomMembers(realm, roomId)
val otherRoomMembers = RoomMemberHelper(realm, roomId)
.queryRoomMembersEvent()
.notEqualTo(RoomMemberEntityFields.USER_ID, userId)
.notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
.findAll()
.asSequence()
.map { it.userId }

View file

@ -24,11 +24,11 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.room.members.MembershipService
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
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.RoomMemberSummary
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.RoomMemberEntity
import im.vector.matrix.android.internal.database.model.RoomMemberEntityFields
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.query.process
import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask
@ -66,14 +66,14 @@ internal class DefaultMembershipService @AssistedInject constructor(
.executeBy(taskExecutor)
}
override fun getRoomMember(userId: String): RoomMember? {
override fun getRoomMember(userId: String): RoomMemberSummary? {
val roomMemberEntity = monarchy.fetchCopied {
RoomMembers(it, roomId).getLastRoomMember(userId)
RoomMemberHelper(it, roomId).getLastRoomMember(userId)
}
return roomMemberEntity?.asDomain()
}
override fun getRoomMembers(queryParams: RoomMemberQueryParams): List<RoomMember> {
override fun getRoomMembers(queryParams: RoomMemberQueryParams): List<RoomMemberSummary> {
return monarchy.fetchAllMappedSync(
{
roomMembersQuery(it, queryParams)
@ -84,7 +84,7 @@ internal class DefaultMembershipService @AssistedInject constructor(
)
}
override fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMember>> {
override fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMemberSummary>> {
return monarchy.findAllMappedWithChanges(
{
roomMembersQuery(it, queryParams)
@ -95,20 +95,21 @@ internal class DefaultMembershipService @AssistedInject constructor(
)
}
private fun roomMembersQuery(realm: Realm, queryParams: RoomMemberQueryParams): RealmQuery<RoomMemberEntity> {
return RoomMembers(realm, roomId).queryRoomMembersEvent()
.process(RoomMemberEntityFields.MEMBERSHIP_STR, queryParams.memberships)
.process(RoomMemberEntityFields.DISPLAY_NAME, queryParams.displayName)
private fun roomMembersQuery(realm: Realm, queryParams: RoomMemberQueryParams): RealmQuery<RoomMemberSummaryEntity> {
return RoomMemberHelper(realm, roomId).queryRoomMembersEvent()
.process(RoomMemberSummaryEntityFields.USER_ID, queryParams.userId)
.process(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
.process(RoomMemberSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
.apply {
if (queryParams.excludeSelf) {
notEqualTo(RoomMemberEntityFields.USER_ID, userId)
notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
}
}
}
override fun getNumberOfJoinedMembers(): Int {
return Realm.getInstance(monarchy.realmConfiguration).use {
RoomMembers(it, roomId).getNumberOfJoinedMembers()
RoomMemberHelper(it, roomId).getNumberOfJoinedMembers()
}
}

View file

@ -26,7 +26,7 @@ import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.model.*
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.RoomMemberEntity
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where
@ -75,7 +75,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
return@doWithRealm
}
val roomMembers = RoomMembers(realm, roomId)
val roomMembers = RoomMemberHelper(realm, roomId)
val activeMembers = roomMembers.queryActiveRoomMembersEvent().findAll()
if (roomEntity?.membership == Membership.INVITE) {
@ -83,7 +83,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
val inviterId = inviteMeEvent?.sender
name = if (inviterId != null) {
activeMembers.where()
.equalTo(RoomMemberEntityFields.USER_ID, inviterId)
.equalTo(RoomMemberSummaryEntityFields.USER_ID, inviterId)
.findFirst()
?.displayName
} else {
@ -91,7 +91,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
}
} else if (roomEntity?.membership == Membership.JOIN) {
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
val otherMembersSubset: List<RoomMemberEntity> = if (roomSummary?.heroes?.isNotEmpty() == true) {
val otherMembersSubset: List<RoomMemberSummaryEntity> = if (roomSummary?.heroes?.isNotEmpty() == true) {
roomSummary.heroes.mapNotNull { userId ->
roomMembers.getLastRoomMember(userId)?.takeIf {
it.membership == Membership.INVITE || it.membership == Membership.JOIN
@ -99,7 +99,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
}
} else {
activeMembers.where()
.notEqualTo(RoomMemberEntityFields.USER_ID, userId)
.notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
.limit(3)
.findAll()
.createSnapshot()
@ -123,14 +123,14 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
return name ?: roomId
}
private fun resolveRoomMemberName(roomMember: RoomMemberEntity?,
roomMembers: RoomMembers): String? {
if (roomMember == null) return null
val isUnique = roomMembers.isUniqueDisplayName(roomMember.displayName)
private fun resolveRoomMemberName(roomMemberSummary: RoomMemberSummaryEntity?,
roomMemberHelper: RoomMemberHelper): String? {
if (roomMemberSummary == null) return null
val isUnique = roomMemberHelper.isUniqueDisplayName(roomMemberSummary.displayName)
return if (isUnique) {
roomMember.displayName
roomMemberSummary.displayName
} else {
"${roomMember.displayName} (${roomMember.userId})"
"${roomMemberSummary.displayName} (${roomMemberSummary.userId})"
}
}
}

View file

@ -17,18 +17,18 @@
package im.vector.matrix.android.internal.session.room.membership
import im.vector.matrix.android.api.session.room.model.RoomMemberContent
import im.vector.matrix.android.internal.database.model.RoomMemberEntity
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
internal object RoomMemberEntityFactory {
fun create(roomId: String, userId: String, roomMember: RoomMemberContent): RoomMemberEntity {
fun create(roomId: String, userId: String, roomMember: RoomMemberContent): RoomMemberSummaryEntity {
val primaryKey = "${roomId}_$userId"
return RoomMemberEntity(
return RoomMemberSummaryEntity(
primaryKey = primaryKey,
userId = userId,
roomId = roomId,
displayName = roomMember.displayName ?: "",
avatarUrl = roomMember.avatarUrl ?: ""
displayName = roomMember.displayName,
avatarUrl = roomMember.avatarUrl
).apply {
membership = roomMember.membership
}

View file

@ -20,7 +20,7 @@ import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.internal.database.model.*
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.RoomMemberEntity
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.query.where
import io.realm.Realm
@ -32,8 +32,8 @@ import io.realm.Sort
* It allows to get the live membership of a user.
*/
internal class RoomMembers(private val realm: Realm,
private val roomId: String
internal class RoomMemberHelper(private val realm: Realm,
private val roomId: String
) {
private val roomSummary: RoomSummaryEntity? by lazy {
@ -48,8 +48,8 @@ internal class RoomMembers(private val realm: Realm,
.findFirst()
}
fun getLastRoomMember(userId: String): RoomMemberEntity? {
return RoomMemberEntity
fun getLastRoomMember(userId: String): RoomMemberSummaryEntity? {
return RoomMemberSummaryEntity
.where(realm, roomId, userId)
.findFirst()
}
@ -66,26 +66,26 @@ internal class RoomMembers(private val realm: Realm,
.size == 1
}
fun queryRoomMembersEvent(): RealmQuery<RoomMemberEntity> {
return RoomMemberEntity.where(realm, roomId)
fun queryRoomMembersEvent(): RealmQuery<RoomMemberSummaryEntity> {
return RoomMemberSummaryEntity.where(realm, roomId)
}
fun queryJoinedRoomMembersEvent(): RealmQuery<RoomMemberEntity> {
fun queryJoinedRoomMembersEvent(): RealmQuery<RoomMemberSummaryEntity> {
return queryRoomMembersEvent()
.equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
.equalTo(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
}
fun queryInvitedRoomMembersEvent(): RealmQuery<RoomMemberEntity> {
fun queryInvitedRoomMembersEvent(): RealmQuery<RoomMemberSummaryEntity> {
return queryRoomMembersEvent()
.equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.INVITE.name)
.equalTo(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, Membership.INVITE.name)
}
fun queryActiveRoomMembersEvent(): RealmQuery<RoomMemberEntity> {
fun queryActiveRoomMembersEvent(): RealmQuery<RoomMemberSummaryEntity> {
return queryRoomMembersEvent()
.beginGroup()
.equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.INVITE.name)
.equalTo(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, Membership.INVITE.name)
.or()
.equalTo(RoomMemberEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
.equalTo(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
.endGroup()
}

View file

@ -16,27 +16,30 @@
package im.vector.matrix.android.internal.session.room.state
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
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.room.state.StateService
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.descending
import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import io.realm.Realm
import io.realm.RealmConfiguration
import java.security.InvalidParameterException
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
@SessionDatabase
private val realmConfiguration: RealmConfiguration,
private val monarchy: Monarchy,
private val taskExecutor: TaskExecutor,
private val sendStateTask: SendStateTask
) : StateService {
@ -47,11 +50,21 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
}
override fun getStateEvent(eventType: String): Event? {
return Realm.getInstance(realmConfiguration).use { realm ->
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
EventEntity.where(realm, roomId, eventType).prev()?.asDomain()
}
}
override fun getStateEventLive(eventType: String): LiveData<Optional<Event>> {
val liveData = monarchy.findAllMappedWithChanges(
{ realm -> EventEntity.where(realm, roomId, eventType).descending() },
{ it.asDomain() }
)
return Transformations.map(liveData) { results ->
results.firstOrNull().toOptional()
}
}
override fun updateTopic(topic: String, callback: MatrixCallback<Unit>) {
val params = SendStateTask.Params(roomId,
EventType.STATE_ROOM_TOPIC,

View file

@ -28,7 +28,7 @@ import im.vector.matrix.android.internal.database.query.getDirectRooms
import im.vector.matrix.android.internal.database.query.getOrCreate
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync
import im.vector.matrix.android.internal.session.sync.model.accountdata.*
import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper
@ -70,7 +70,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
var hasUpdate = false
monarchy.doWithRealm { realm ->
invites.forEach { (roomId, _) ->
val myUserStateEvent = RoomMembers(realm, roomId).getLastStateEvent(userId)
val myUserStateEvent = RoomMemberHelper(realm, roomId).getLastStateEvent(userId)
val inviterId = myUserStateEvent?.sender
val myUserRoomMember: RoomMemberContent? = myUserStateEvent?.let { it.asDomain().content?.toModel() }
val isDirect = myUserRoomMember?.isDirect

View file

@ -75,7 +75,7 @@
android:value=".features.home.HomeActivity" />
</activity>
<activity android:name=".features.debug.DebugMenuActivity" />
<activity android:name=".features.home.createdirect.CreateDirectRoomActivity" />
<activity android:name="im.vector.riotx.features.createdirect.CreateDirectRoomActivity" />
<activity android:name=".features.webview.VectorWebViewActivity" />
<activity android:name=".features.link.LinkHandlerActivity">
<intent-filter>
@ -106,6 +106,9 @@
<category android:name="android.intent.category.OPENABLE" />
</intent-filter>
</activity>
<activity android:name=".features.roomprofile.RoomProfileActivity" />
<activity android:name=".features.signout.hard.SignedOutActivity" />
<activity
android:name=".features.signout.soft.SoftLogoutActivity"
@ -122,6 +125,14 @@
<data android:host="matrix.to" />
</intent-filter>
</activity>
<activity android:name=".features.roommemberprofile.RoomMemberProfileActivity"
android:parentActivityName=".features.home.HomeActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".features.home.HomeActivity" />
</activity>
<!-- Services -->
<service

View file

@ -25,8 +25,8 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
import im.vector.matrix.rx.rx
import im.vector.riotx.features.home.HomeRoomListDataSource
import im.vector.riotx.features.home.group.ALL_COMMUNITIES_GROUP_ID
import im.vector.riotx.features.home.group.SelectedGroupDataSource
import im.vector.riotx.features.grouplist.ALL_COMMUNITIES_GROUP_ID
import im.vector.riotx.features.grouplist.SelectedGroupDataSource
import im.vector.riotx.features.home.room.list.ChronologicalRoomComparator
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers

View file

@ -0,0 +1,52 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.riotx.core.animations
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.AppBarLayout.OnOffsetChangedListener
import kotlin.math.abs
abstract class AppBarStateChangeListener : OnOffsetChangedListener {
enum class State {
EXPANDED, COLLAPSED, IDLE
}
private var currentState = State.IDLE
override fun onOffsetChanged(appBarLayout: AppBarLayout, i: Int) {
currentState = if (i == 0) {
if (currentState != State.EXPANDED) {
onStateChanged(appBarLayout, State.EXPANDED)
}
State.EXPANDED
} else if (abs(i) >= appBarLayout.totalScrollRange) {
if (currentState != State.COLLAPSED) {
onStateChanged(appBarLayout, State.COLLAPSED)
}
State.COLLAPSED
} else {
if (currentState != State.IDLE) {
onStateChanged(appBarLayout, State.IDLE)
}
State.IDLE
}
}
abstract fun onStateChanged(appBarLayout: AppBarLayout, state: State)
}

View file

@ -0,0 +1,38 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.riotx.core.animations
import android.view.View
import com.google.android.material.appbar.AppBarLayout
class MatrixItemAppBarStateChangeListener(private val headerView: View, private val toolbarViews: List<View>) : AppBarStateChangeListener() {
override fun onStateChanged(appBarLayout: AppBarLayout, state: State) {
if (state == State.COLLAPSED) {
headerView.visibility = View.INVISIBLE
toolbarViews.forEach {
it.animate().alpha(1f).duration = 150
}
} else {
headerView.visibility = View.VISIBLE
toolbarViews.forEach {
it.animate().alpha(0f).duration = 150
}
}
}
}

View file

@ -0,0 +1,221 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.core.animations.behavior
import android.animation.ArgbEvaluator
import android.content.Context
import android.graphics.drawable.ColorDrawable
import android.util.AttributeSet
import android.view.View
import androidx.coordinatorlayout.widget.CoordinatorLayout
import im.vector.riotx.R
import kotlin.math.abs
private const val UNSPECIFIED_INT = Integer.MAX_VALUE
private val UNSPECIFIED_FLOAT = Float.MAX_VALUE
private const val DEPEND_TYPE_HEIGHT = 0
private const val DEPEND_TYPE_WIDTH = 1
private const val DEPEND_TYPE_X = 2
private const val DEPEND_TYPE_Y = 3
class PercentViewBehavior<V : View>(context: Context, attrs: AttributeSet) : CoordinatorLayout.Behavior<V>(context, attrs) {
private var dependType: Int = 0
private var dependViewId: Int = 0
private var dependTarget: Int = 0
private var dependStartX: Int = 0
private var dependStartY: Int = 0
private var dependStartWidth: Int = 0
private var dependStartHeight: Int = 0
private var startX: Int = 0
private var startY: Int = 0
private var startWidth: Int = 0
private var startHeight: Int = 0
private var startBackgroundColor: Int = 0
private var startAlpha: Float = 0f
private var startRotateX: Float = 0f
private var startRotateY: Float = 0f
private var targetX: Int = 0
private var targetY: Int = 0
private var targetWidth: Int = 0
private var targetHeight: Int = 0
private var targetBackgroundColor: Int = 0
private var targetAlpha: Float = 0f
private var targetRotateX: Float = 0f
private var targetRotateY: Float = 0f
/**
* Is the values prepared to be use
*/
private var isPrepared: Boolean = false
init {
val a = context.obtainStyledAttributes(attrs, R.styleable.PercentViewBehavior)
dependViewId = a.getResourceId(R.styleable.PercentViewBehavior_behavior_dependsOn, 0)
dependType = a.getInt(R.styleable.PercentViewBehavior_behavior_dependType, DEPEND_TYPE_WIDTH)
dependTarget = a.getDimensionPixelOffset(R.styleable.PercentViewBehavior_behavior_dependTarget, UNSPECIFIED_INT)
targetX = a.getDimensionPixelOffset(R.styleable.PercentViewBehavior_behavior_targetX, UNSPECIFIED_INT)
targetY = a.getDimensionPixelOffset(R.styleable.PercentViewBehavior_behavior_targetY, UNSPECIFIED_INT)
targetWidth = a.getDimensionPixelOffset(R.styleable.PercentViewBehavior_behavior_targetWidth, UNSPECIFIED_INT)
targetHeight = a.getDimensionPixelOffset(R.styleable.PercentViewBehavior_behavior_targetHeight, UNSPECIFIED_INT)
targetBackgroundColor = a.getColor(R.styleable.PercentViewBehavior_behavior_targetBackgroundColor, UNSPECIFIED_INT)
targetAlpha = a.getFloat(R.styleable.PercentViewBehavior_behavior_targetAlpha, UNSPECIFIED_FLOAT)
targetRotateX = a.getFloat(R.styleable.PercentViewBehavior_behavior_targetRotateX, UNSPECIFIED_FLOAT)
targetRotateY = a.getFloat(R.styleable.PercentViewBehavior_behavior_targetRotateY, UNSPECIFIED_FLOAT)
a.recycle()
}
private fun prepare(parent: CoordinatorLayout, child: View, dependency: View) {
dependStartX = dependency.x.toInt()
dependStartY = dependency.y.toInt()
dependStartWidth = dependency.width
dependStartHeight = dependency.height
startX = child.x.toInt()
startY = child.y.toInt()
startWidth = child.width
startHeight = child.height
startAlpha = child.alpha
startRotateX = child.rotationX
startRotateY = child.rotationY
// only set the start background color when the background is color drawable
val background = child.background
if (background is ColorDrawable) {
startBackgroundColor = background.color
}
// if parent fitsSystemWindows is true, add status bar height to target y if specified
if (parent.fitsSystemWindows && targetY != UNSPECIFIED_INT) {
var result = 0
val resources = parent.context.resources
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {
result = resources.getDimensionPixelSize(resourceId)
}
targetY += result
}
isPrepared = true
}
override fun layoutDependsOn(parent: CoordinatorLayout, child: V, dependency: View): Boolean {
return dependency.id == dependViewId
}
override fun onDependentViewChanged(parent: CoordinatorLayout, child: V, dependency: View): Boolean {
// first time, prepare values before continue
if (!isPrepared) {
prepare(parent, child, dependency)
}
updateView(child, dependency)
return false
}
override fun onLayoutChild(parent: CoordinatorLayout, child: V, layoutDirection: Int): Boolean {
val bool = super.onLayoutChild(parent, child, layoutDirection)
if (isPrepared) {
updateView(child, parent.getDependencies(child)[0])
}
return bool
}
/**
* Update the child view from the dependency states
*
* @param child child view
* @param dependency dependency view
*/
private fun updateView(child: V, dependency: View) {
var percent = 0f
var start = 0f
var current = 0f
var end = UNSPECIFIED_INT.toFloat()
when (dependType) {
DEPEND_TYPE_WIDTH -> {
start = dependStartWidth.toFloat()
current = dependency.width.toFloat()
end = dependTarget.toFloat()
}
DEPEND_TYPE_HEIGHT -> {
start = dependStartHeight.toFloat()
current = dependency.height.toFloat()
end = dependTarget.toFloat()
}
DEPEND_TYPE_X -> {
start = dependStartX.toFloat()
current = dependency.x
end = dependTarget.toFloat()
}
DEPEND_TYPE_Y -> {
start = dependStartY.toFloat()
current = dependency.y
end = dependTarget.toFloat()
}
}
// need to define target value according to the depend type, if not then skip
if (end != UNSPECIFIED_INT.toFloat()) {
percent = abs(current - start) / abs(end - start)
}
updateViewWithPercent(child, if (percent > 1f) 1f else percent)
}
private fun updateViewWithPercent(child: View, percent: Float) {
var newX = if (targetX == UNSPECIFIED_INT) 0f else (targetX - startX) * percent
var newY = if (targetY == UNSPECIFIED_INT) 0f else (targetY - startY) * percent
// set scale
if (targetWidth != UNSPECIFIED_INT) {
val newWidth = startWidth + (targetWidth - startWidth) * percent
child.scaleX = newWidth / startWidth
newX -= (startWidth - newWidth) / 2
}
if (targetHeight != UNSPECIFIED_INT) {
val newHeight = startHeight + (targetHeight - startHeight) * percent
child.scaleY = newHeight / startHeight
newY -= (startHeight - newHeight) / 2
}
// set new position
child.translationX = newX
child.translationY = newY
// set alpha
if (targetAlpha != UNSPECIFIED_FLOAT) {
child.alpha = startAlpha + (targetAlpha - startAlpha) * percent
}
// set background color
if (targetBackgroundColor != UNSPECIFIED_INT && startBackgroundColor != 0) {
val evaluator = ArgbEvaluator()
val color = evaluator.evaluate(percent, startBackgroundColor, targetBackgroundColor) as Int
child.setBackgroundColor(color)
}
// set rotation
if (targetRotateX != UNSPECIFIED_FLOAT) {
child.rotationX = startRotateX + (targetRotateX - startRotateX) * percent
}
if (targetRotateY != UNSPECIFIED_FLOAT) {
child.rotationY = startRotateY + (targetRotateY - startRotateY) * percent
}
child.requestLayout()
}
}

View file

@ -30,20 +30,23 @@ import im.vector.riotx.features.crypto.verification.SASVerificationVerifiedFragm
import im.vector.riotx.features.home.HomeDetailFragment
import im.vector.riotx.features.home.HomeDrawerFragment
import im.vector.riotx.features.home.LoadingFragment
import im.vector.riotx.features.home.createdirect.CreateDirectRoomDirectoryUsersFragment
import im.vector.riotx.features.home.createdirect.CreateDirectRoomKnownUsersFragment
import im.vector.riotx.features.home.group.GroupListFragment
import im.vector.riotx.features.createdirect.CreateDirectRoomDirectoryUsersFragment
import im.vector.riotx.features.createdirect.CreateDirectRoomKnownUsersFragment
import im.vector.riotx.features.grouplist.GroupListFragment
import im.vector.riotx.features.home.room.breadcrumbs.BreadcrumbsFragment
import im.vector.riotx.features.home.room.detail.RoomDetailFragment
import im.vector.riotx.features.home.room.list.RoomListFragment
import im.vector.riotx.features.login.*
import im.vector.riotx.features.login.terms.LoginTermsFragment
import im.vector.riotx.features.roommemberprofile.RoomMemberProfileFragment
import im.vector.riotx.features.reactions.EmojiChooserFragment
import im.vector.riotx.features.reactions.EmojiSearchResultFragment
import im.vector.riotx.features.roomdirectory.PublicRoomsFragment
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment
import im.vector.riotx.features.roomprofile.RoomProfileFragment
import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
import im.vector.riotx.features.settings.*
import im.vector.riotx.features.settings.devices.VectorSettingsDevicesFragment
import im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment
@ -259,6 +262,21 @@ interface FragmentModule {
@FragmentKey(PublicRoomsFragment::class)
fun bindPublicRoomsFragment(fragment: PublicRoomsFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomProfileFragment::class)
fun bindRoomProfileFragment(fragment: RoomProfileFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomMemberListFragment::class)
fun bindRoomMemberListFragment(fragment: RoomMemberListFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomMemberProfileFragment::class)
fun bindRoomMemberProfileFragment(fragment: RoomMemberProfileFragment): Fragment
@Binds
@IntoMap
@FragmentKey(BreadcrumbsFragment::class)

View file

@ -27,7 +27,7 @@ import im.vector.riotx.features.MainActivity
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
import im.vector.riotx.features.home.HomeActivity
import im.vector.riotx.features.home.HomeModule
import im.vector.riotx.features.home.createdirect.CreateDirectRoomActivity
import im.vector.riotx.features.createdirect.CreateDirectRoomActivity
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet

View file

@ -36,7 +36,7 @@ import im.vector.riotx.features.crypto.keysrequest.KeyRequestHandler
import im.vector.riotx.features.crypto.verification.IncomingVerificationRequestHandler
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.HomeRoomListDataSource
import im.vector.riotx.features.home.group.SelectedGroupDataSource
import im.vector.riotx.features.grouplist.SelectedGroupDataSource
import im.vector.riotx.features.html.EventHtmlRenderer
import im.vector.riotx.features.html.VectorHtmlCompressor
import im.vector.riotx.features.navigation.Navigator

View file

@ -22,19 +22,20 @@ import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap
import im.vector.riotx.core.platform.ConfigurationViewModel
import im.vector.riotx.features.createdirect.CreateDirectRoomSharedActionViewModel
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyViewModel
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedViewModel
import im.vector.riotx.features.crypto.verification.SasVerificationViewModel
import im.vector.riotx.features.home.HomeSharedActionViewModel
import im.vector.riotx.features.home.createdirect.CreateDirectRoomSharedActionViewModel
import im.vector.riotx.features.home.room.detail.RoomDetailSharedActionViewModel
import im.vector.riotx.features.home.room.detail.timeline.action.MessageSharedActionViewModel
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
import im.vector.riotx.features.login.LoginSharedActionViewModel
import im.vector.riotx.features.reactions.EmojiChooserViewModel
import im.vector.riotx.features.roomdirectory.RoomDirectorySharedActionViewModel
import im.vector.riotx.features.roomprofile.RoomProfileSharedActionViewModel
import im.vector.riotx.features.workers.signout.SignOutViewModel
@Module
@ -124,4 +125,9 @@ interface ViewModelModule {
@IntoMap
@ViewModelKey(RoomDetailSharedActionViewModel::class)
fun bindRoomDetailSharedActionViewModel(viewModel: RoomDetailSharedActionViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(RoomProfileSharedActionViewModel::class)
fun bindRoomProfileSharedActionViewModel(viewModel: RoomProfileSharedActionViewModel): ViewModel
}

View file

@ -12,17 +12,14 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.riotx.core.epoxy.bottomsheet
package im.vector.riotx.core.epoxy
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
@EpoxyModelClass(layout = R.layout.item_bottom_sheet_divider)
abstract class BottomSheetSeparatorItem : VectorEpoxyModel<BottomSheetSeparatorItem.Holder>() {
@EpoxyModelClass(layout = R.layout.item_divider)
abstract class DividerItem : VectorEpoxyModel<DividerItem.Holder>() {
class Holder : VectorEpoxyHolder()
}

View file

@ -0,0 +1,74 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.core.epoxy.profiles
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.extensions.setTextOrHide
import im.vector.riotx.features.themes.ThemeUtils
@EpoxyModelClass(layout = R.layout.item_profile_action)
abstract class ProfileActionItem : VectorEpoxyModel<ProfileActionItem.Holder>() {
@EpoxyAttribute
lateinit var title: String
@EpoxyAttribute
var subtitle: String? = null
@EpoxyAttribute
var iconRes: Int = 0
@EpoxyAttribute
var editable: Boolean = true
@EpoxyAttribute
var destructive: Boolean = false
@EpoxyAttribute
lateinit var listener: View.OnClickListener
override fun bind(holder: Holder) {
super.bind(holder)
holder.view.setOnClickListener(listener)
holder.editable.isVisible = editable
holder.title.text = title
val tintColor = if (destructive) {
ContextCompat.getColor(holder.view.context, R.color.riotx_notice)
} else {
ThemeUtils.getColor(holder.view.context, R.attr.riotx_text_primary)
}
holder.title.setTextColor(tintColor)
holder.subtitle.setTextOrHide(subtitle)
if (iconRes != 0) {
holder.icon.setImageResource(iconRes)
holder.icon.isVisible = true
} else {
holder.icon.isVisible = false
}
}
class Holder : VectorEpoxyHolder() {
val icon by bind<ImageView>(R.id.actionIcon)
val title by bind<TextView>(R.id.actionTitle)
val subtitle by bind<TextView>(R.id.actionSubtitle)
val editable by bind<ImageView>(R.id.actionEditable)
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.riotx.core.epoxy.profiles
import androidx.annotation.DrawableRes
import com.airbnb.epoxy.EpoxyController
import im.vector.riotx.core.epoxy.DividerItem_
fun EpoxyController.buildProfileSection(title: String) {
profileSectionItem {
id("section_$title")
title(title)
}
}
fun EpoxyController.buildProfileAction(
id: String,
title: String,
subtitle: String? = null,
editable: Boolean = true,
@DrawableRes icon: Int = 0,
destructive: Boolean = false,
divider: Boolean = true,
action: () -> Unit
) {
profileActionItem {
iconRes(icon)
id("action_$id")
subtitle(subtitle)
editable(editable)
destructive(destructive)
title(title)
listener { _ ->
action()
}
}
DividerItem_()
.id("divider_$title")
.addIf(divider, this)
}

View file

@ -0,0 +1,53 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.riotx.core.epoxy.profiles
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.extensions.setTextOrHide
import im.vector.riotx.features.home.AvatarRenderer
@EpoxyModelClass(layout = R.layout.item_profile_matrix_item)
abstract class ProfileMatrixItem : VectorEpoxyModel<ProfileMatrixItem.Holder>() {
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var clickListener: View.OnClickListener? = null
override fun bind(holder: Holder) {
val bestName = matrixItem.getBestName()
val matrixId = matrixItem.id.takeIf { it != bestName }
holder.view.setOnClickListener(clickListener)
holder.titleView.text = bestName
holder.subtitleView.setTextOrHide(matrixId)
avatarRenderer.render(matrixItem, holder.avatarImageView)
}
class Holder : VectorEpoxyHolder() {
val titleView by bind<TextView>(R.id.matrixItemTitle)
val subtitleView by bind<TextView>(R.id.matrixItemSubtitle)
val avatarImageView by bind<ImageView>(R.id.matrixItemAvatar)
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.core.epoxy.profiles
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
@EpoxyModelClass(layout = R.layout.item_profile_section)
abstract class ProfileSectionItem: VectorEpoxyModel<ProfileSectionItem.Holder>() {
@EpoxyAttribute
lateinit var title: String
override fun bind(holder: Holder) {
super.bind(holder)
holder.sectionView.text = title
}
class Holder : VectorEpoxyHolder() {
val sectionView by bind<TextView>(R.id.itemProfileSectionView)
}
}

View file

@ -34,7 +34,7 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
data class Error(val message: CharSequence? = null) : State()
}
private var eventCallback: EventCallback? = null
var eventCallback: EventCallback? = null
var contentView: View? = null

View file

@ -23,11 +23,18 @@ import android.os.Parcelable
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.annotation.*
import androidx.annotation.AttrRes
import androidx.annotation.LayoutRes
import androidx.annotation.MainThread
import androidx.annotation.MenuRes
import androidx.annotation.Nullable
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentFactory
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
@ -41,7 +48,12 @@ import com.google.android.material.snackbar.Snackbar
import im.vector.matrix.android.api.failure.GlobalError
import im.vector.riotx.BuildConfig
import im.vector.riotx.R
import im.vector.riotx.core.di.*
import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.di.DaggerScreenComponent
import im.vector.riotx.core.di.HasScreenInjector
import im.vector.riotx.core.di.HasVectorInjector
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.di.VectorComponent
import im.vector.riotx.core.dialogs.DialogLocker
import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.utils.toast
@ -92,6 +104,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
lateinit var rageShake: RageShake
private set
protected lateinit var navigator: Navigator
private lateinit var fragmentFactory: FragmentFactory
private lateinit var activeSessionHolder: ActiveSessionHolder
private lateinit var vectorPreferences: VectorPreferences
@ -145,7 +158,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
}
Timber.v("Injecting dependencies into ${javaClass.simpleName} took $timeForInjection ms")
ThemeUtils.setActivityTheme(this, getOtherThemes())
supportFragmentManager.fragmentFactory = screenComponent.fragmentFactory()
fragmentFactory = screenComponent.fragmentFactory()
supportFragmentManager.fragmentFactory = fragmentFactory
super.onCreate(savedInstanceState)
viewModelFactory = screenComponent.viewModelFactory()
configurationViewModel = viewModelProvider.get(ConfigurationViewModel::class.java)
@ -196,7 +210,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
handleInvalidToken(globalError)
is GlobalError.ConsentNotGivenError ->
consentNotGivenHelper.displayDialog(globalError.consentUri,
activeSessionHolder.getActiveSession().sessionParams.homeServerConnectionConfig.homeServerUri.host ?: "")
activeSessionHolder.getActiveSession().sessionParams.homeServerConnectionConfig.homeServerUri.host
?: "")
}
}
@ -209,11 +224,11 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
mainActivityStarted = true
MainActivity.restartApp(this,
MainActivityArgs(
clearCredentials = !globalError.softLogout,
isUserLoggedOut = true,
isSoftLogout = globalError.softLogout
)
MainActivityArgs(
clearCredentials = !globalError.softLogout,
isUserLoggedOut = true,
isSoftLogout = globalError.softLogout
)
)
}
@ -276,6 +291,12 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
protected open fun injectWith(injector: ScreenComponent) = Unit
protected fun createFragment(fragmentClass: Class<out Fragment>, args: Bundle?): Fragment {
return fragmentFactory.instantiate(classLoader, fragmentClass.name).apply {
arguments = args
}
}
/* ==========================================================================================
* PRIVATE METHODS
* ========================================================================================== */
@ -372,12 +393,10 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
*/
protected fun configureToolbar(toolbar: Toolbar, displayBack: Boolean = true) {
setSupportActionBar(toolbar)
if (displayBack) {
supportActionBar?.let {
it.setDisplayShowHomeEnabled(true)
it.setDisplayHomeAsUpEnabled(true)
}
supportActionBar?.let {
it.setDisplayShowHomeEnabled(displayBack)
it.setDisplayHomeAsUpEnabled(displayBack)
it.title = null
}
}

View file

@ -14,8 +14,11 @@
* limitations under the License.
*/
@file:Suppress("DEPRECATION")
package im.vector.riotx.core.platform
import android.app.ProgressDialog
import android.content.Context
import android.os.Bundle
import android.os.Parcelable
@ -59,6 +62,8 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
protected lateinit var navigator: Navigator
protected lateinit var errorFormatter: ErrorFormatter
private var progress: ProgressDialog? = null
/* ==========================================================================================
* View model
* ========================================================================================== */
@ -96,6 +101,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
}
final override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
Timber.i("onCreateView Fragment ${this.javaClass.simpleName}")
return inflater.inflate(getLayoutResId(), container, false)
}
@ -117,6 +123,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
@CallSuper
override fun onDestroyView() {
super.onDestroyView()
Timber.i("onDestroyView Fragment ${this.javaClass.simpleName}")
mUnBinder?.unbind()
mUnBinder = null
uiDisposables.clear()
@ -175,6 +182,19 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
}
}
protected fun showLoadingDialog(message: CharSequence, cancelable: Boolean = false) {
progress = ProgressDialog(requireContext()).apply {
setCancelable(cancelable)
setMessage(message)
setProgressStyle(ProgressDialog.STYLE_SPINNER)
show()
}
}
protected fun dismissLoadingDialog() {
progress?.dismiss()
}
/* ==========================================================================================
* Toolbar
* ========================================================================================== */

View file

@ -19,7 +19,7 @@ package im.vector.riotx.core.utils
import com.jakewharton.rxrelay2.BehaviorRelay
import com.jakewharton.rxrelay2.PublishRelay
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import io.reactivex.android.schedulers.AndroidSchedulers
interface DataSource<T> {
fun observe(): Observable<T>
@ -37,7 +37,7 @@ open class BehaviorDataSource<T>(private val defaultValue: T? = null) : MutableD
private val behaviorRelay = createRelay()
override fun observe(): Observable<T> {
return behaviorRelay.hide().observeOn(Schedulers.computation())
return behaviorRelay.hide().observeOn(AndroidSchedulers.mainThread())
}
override fun post(value: T) {
@ -61,7 +61,7 @@ open class PublishDataSource<T> : MutableDataSource<T> {
private val publishRelay = PublishRelay.create<T>()
override fun observe(): Observable<T> {
return publishRelay.hide()
return publishRelay.hide().observeOn(AndroidSchedulers.mainThread())
}
override fun post(value: T) {

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package im.vector.riotx.features.home
package im.vector.riotx.core.utils
import androidx.annotation.ColorRes
import im.vector.riotx.R

View file

@ -17,20 +17,20 @@
package im.vector.riotx.features.autocomplete.member
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
import im.vector.riotx.features.autocomplete.autocompleteMatrixItem
import im.vector.riotx.features.home.AvatarRenderer
import javax.inject.Inject
class AutocompleteMemberController @Inject constructor() : TypedEpoxyController<List<RoomMember>>() {
class AutocompleteMemberController @Inject constructor() : TypedEpoxyController<List<RoomMemberSummary>>() {
var listener: AutocompleteClickListener<RoomMember>? = null
var listener: AutocompleteClickListener<RoomMemberSummary>? = null
@Inject lateinit var avatarRenderer: AvatarRenderer
override fun buildModels(data: List<RoomMember>?) {
override fun buildModels(data: List<RoomMemberSummary>?) {
if (data.isNullOrEmpty()) {
return
}

View file

@ -25,14 +25,14 @@ import im.vector.matrix.android.api.query.QueryStringValue
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.members.roomMemberQueryParams
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.RoomMemberSummary
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
@Assisted val roomId: String,
private val session: Session,
private val controller: AutocompleteMemberController
) : RecyclerViewPresenter<RoomMember>(context), AutocompleteClickListener<RoomMember> {
) : RecyclerViewPresenter<RoomMemberSummary>(context), AutocompleteClickListener<RoomMemberSummary> {
private val room = session.getRoom(roomId)!!
@ -51,7 +51,7 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
return controller.adapter
}
override fun onItemClick(t: RoomMember) {
override fun onItemClick(t: RoomMemberSummary) {
dispatchClick(t)
}

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package im.vector.riotx.features.home.createdirect
package im.vector.riotx.features.createdirect
import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotx.core.platform.VectorViewModelAction

View file

@ -16,7 +16,7 @@
*
*/
package im.vector.riotx.features.home.createdirect
package im.vector.riotx.features.createdirect
import android.content.Context
import android.content.Intent

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package im.vector.riotx.features.home.createdirect
package im.vector.riotx.features.createdirect
import android.os.Bundle
import android.view.View

View file

@ -16,7 +16,7 @@
*
*/
package im.vector.riotx.features.home.createdirect
package im.vector.riotx.features.createdirect
import android.os.Bundle
import android.view.Menu

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package im.vector.riotx.features.home.createdirect
package im.vector.riotx.features.createdirect
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package im.vector.riotx.features.home.createdirect
package im.vector.riotx.features.createdirect
import im.vector.riotx.core.platform.VectorSharedAction

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package im.vector.riotx.features.home.createdirect
package im.vector.riotx.features.createdirect
import im.vector.riotx.core.platform.VectorSharedActionViewModel
import javax.inject.Inject

View file

@ -16,7 +16,7 @@
*
*/
package im.vector.riotx.features.home.createdirect
package im.vector.riotx.features.createdirect
import android.view.View
import android.widget.ImageView

View file

@ -16,7 +16,7 @@
*
*/
package im.vector.riotx.features.home.createdirect
package im.vector.riotx.features.createdirect
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData

View file

@ -16,7 +16,7 @@
*
*/
package im.vector.riotx.features.home.createdirect
package im.vector.riotx.features.createdirect
import androidx.paging.PagedList
import arrow.core.Option

View file

@ -16,7 +16,7 @@
*
*/
package im.vector.riotx.features.home.createdirect
package im.vector.riotx.features.createdirect
import com.airbnb.epoxy.EpoxyController
import com.airbnb.mvrx.Fail

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package im.vector.riotx.features.home.createdirect
package im.vector.riotx.features.createdirect
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.paging.PagedListEpoxyController

View file

@ -12,9 +12,10 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.riotx.features.home.group
package im.vector.riotx.features.grouplist
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.riotx.core.platform.VectorViewModelAction

View file

@ -12,9 +12,10 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.riotx.features.home.group
package im.vector.riotx.features.grouplist
import android.os.Bundle
import android.view.View

View file

@ -12,9 +12,10 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.riotx.features.home.group
package im.vector.riotx.features.grouplist
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package im.vector.riotx.features.home.group
package im.vector.riotx.features.grouplist
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState

View file

@ -12,9 +12,10 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.riotx.features.home.group
package im.vector.riotx.features.grouplist
import com.airbnb.epoxy.EpoxyController
import im.vector.matrix.android.api.session.group.model.GroupSummary

View file

@ -12,9 +12,10 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.riotx.features.home.group
package im.vector.riotx.features.grouplist
import android.widget.ImageView
import android.widget.TextView

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package im.vector.riotx.features.home.group
package im.vector.riotx.features.grouplist
import arrow.core.Option
import im.vector.matrix.android.api.session.group.model.GroupSummary

View file

@ -32,6 +32,7 @@ import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.glide.GlideApp
import im.vector.riotx.core.glide.GlideRequest
import im.vector.riotx.core.glide.GlideRequests
import im.vector.riotx.core.utils.getColorFromUserId
import javax.inject.Inject
/**

View file

@ -26,7 +26,7 @@ import im.vector.matrix.rx.rx
import im.vector.riotx.core.di.HasScreenInjector
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.group.SelectedGroupDataSource
import im.vector.riotx.features.grouplist.SelectedGroupDataSource
import im.vector.riotx.features.ui.UiStateRepository
import io.reactivex.schedulers.Schedulers

View file

@ -24,7 +24,7 @@ import im.vector.riotx.R
import im.vector.riotx.core.extensions.observeK
import im.vector.riotx.core.extensions.replaceChildFragment
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.home.group.GroupListFragment
import im.vector.riotx.features.grouplist.GroupListFragment
import kotlinx.android.synthetic.main.fragment_home_drawer.*
import javax.inject.Inject

View file

@ -27,7 +27,7 @@ import com.otaliastudios.autocomplete.CharPolicy
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem
@ -108,13 +108,13 @@ class AutoCompleter @AssistedInject constructor(
private fun setupMembers(backgroundDrawable: ColorDrawable, editText: EditText) {
val autocompleteMemberPresenter = autocompleteMemberPresenterFactory.create(roomId)
Autocomplete.on<RoomMember>(editText)
Autocomplete.on<RoomMemberSummary>(editText)
.with(CharPolicy('@', true))
.with(autocompleteMemberPresenter)
.with(ELEVATION)
.with(backgroundDrawable)
.with(object : AutocompleteCallback<RoomMember> {
override fun onPopupItemClicked(editable: Editable, item: RoomMember): Boolean {
.with(object : AutocompleteCallback<RoomMemberSummary> {
override fun onPopupItemClicked(editable: Editable, item: RoomMemberSummary): Boolean {
insertMatrixItem(editText, editable, "@", item.toMatrixItem())
return true
}

View file

@ -26,7 +26,12 @@ import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.text.Spannable
import android.view.*
import android.view.HapticFeedbackConstants
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.Window
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.DrawableRes
@ -46,7 +51,13 @@ import butterknife.BindView
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.EpoxyVisibilityTracker
import com.airbnb.epoxy.OnModelBuildFinishedListener
import com.airbnb.mvrx.*
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import com.github.piasy.biv.BigImageViewer
import com.github.piasy.biv.loader.ImageLoader
import com.google.android.material.snackbar.Snackbar
@ -57,7 +68,13 @@ import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.message.*
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.MessageImageInfoContent
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.MessageVideoContent
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
@ -67,19 +84,36 @@ import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.dialogs.withColoredButton
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
import im.vector.riotx.core.extensions.*
import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.hideKeyboard
import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.extensions.setTextOrHide
import im.vector.riotx.core.extensions.showKeyboard
import im.vector.riotx.core.files.addEntryToDownloadManager
import im.vector.riotx.core.glide.GlideApp
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.ui.views.JumpToReadMarkerView
import im.vector.riotx.core.ui.views.NotificationAreaView
import im.vector.riotx.core.utils.*
import im.vector.riotx.core.utils.Debouncer
import im.vector.riotx.core.utils.KeyboardStateUtils
import im.vector.riotx.core.utils.PERMISSIONS_FOR_WRITING_FILES
import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_DOWNLOAD_FILE
import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_INCOMING_URI
import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_PICK_ATTACHMENT
import im.vector.riotx.core.utils.TextUtils
import im.vector.riotx.core.utils.allGranted
import im.vector.riotx.core.utils.checkPermissions
import im.vector.riotx.core.utils.copyToClipboard
import im.vector.riotx.core.utils.createUIHandler
import im.vector.riotx.core.utils.getColorFromUserId
import im.vector.riotx.core.utils.openUrlInExternalBrowser
import im.vector.riotx.core.utils.shareMedia
import im.vector.riotx.core.utils.toast
import im.vector.riotx.features.attachments.AttachmentTypeSelectorView
import im.vector.riotx.features.attachments.AttachmentsHelper
import im.vector.riotx.features.attachments.ContactAttachment
import im.vector.riotx.features.command.Command
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.getColorFromUserId
import im.vector.riotx.features.home.room.detail.composer.TextComposerView
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
@ -87,7 +121,12 @@ import im.vector.riotx.features.home.room.detail.timeline.action.EventSharedActi
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.action.MessageSharedActionViewModel
import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.item.*
import im.vector.riotx.features.home.room.detail.timeline.item.AbsMessageItem
import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem
import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
import im.vector.riotx.features.html.EventHtmlRenderer
import im.vector.riotx.features.html.PillImageSpan
@ -97,7 +136,7 @@ import im.vector.riotx.features.media.ImageMediaViewerActivity
import im.vector.riotx.features.media.VideoContentRenderer
import im.vector.riotx.features.media.VideoMediaViewerActivity
import im.vector.riotx.features.notifications.NotificationDrawerManager
import im.vector.riotx.features.permalink.NavigateToRoomInterceptor
import im.vector.riotx.features.permalink.NavigationInterceptor
import im.vector.riotx.features.permalink.PermalinkHandler
import im.vector.riotx.features.reactions.EmojiReactionPickerActivity
import im.vector.riotx.features.settings.VectorPreferences
@ -205,7 +244,9 @@ class RoomDetailFragment @Inject constructor(
setupNotificationView()
setupJumpToReadMarkerView()
setupJumpToBottomView()
roomToolbarContentView.setOnClickListener {
navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId)
}
roomDetailViewModel.subscribe { renderState(it) }
roomDetailViewModel.sendMessageResultLiveData.observeEvent(viewLifecycleOwner) { renderSendMessageResult(it) }
@ -328,9 +369,9 @@ class RoomDetailFragment @Inject constructor(
AlertDialog.Builder(requireActivity())
.setTitle(R.string.dialog_title_error)
.setMessage(getString(R.string.error_file_too_big,
error.filename,
TextUtils.formatFileSize(requireContext(), error.fileSizeInBytes),
TextUtils.formatFileSize(requireContext(), error.homeServerLimitInBytes)
error.filename,
TextUtils.formatFileSize(requireContext(), error.fileSizeInBytes),
TextUtils.formatFileSize(requireContext(), error.homeServerLimitInBytes)
))
.setPositiveButton(R.string.ok, null)
.show()
@ -417,9 +458,10 @@ class RoomDetailFragment @Inject constructor(
avatarRenderer.render(
MatrixItem.UserItem(event.root.senderId
?: "", event.getDisambiguatedDisplayName(), event.senderAvatar),
?: "", event.getDisambiguatedDisplayName(), event.senderAvatar),
composerLayout.composerRelatedMessageAvatar
)
composerLayout.expand {
if (isAdded) {
// need to do it here also when not using quick reply
@ -435,7 +477,7 @@ class RoomDetailFragment @Inject constructor(
// Ignore update to avoid saving a draft
composerLayout.composerEditText.setText(text)
composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text?.length
?: 0)
?: 0)
}
}
@ -799,7 +841,7 @@ class RoomDetailFragment @Inject constructor(
override fun onUrlClicked(url: String): Boolean {
permalinkHandler
.launch(requireActivity(), url, object : NavigateToRoomInterceptor {
.launch(requireActivity(), url, object : NavigationInterceptor {
override fun navToRoom(roomId: String?, eventId: String?): Boolean {
// Same room?
if (roomId == roomDetailArgs.roomId) {
@ -815,6 +857,11 @@ class RoomDetailFragment @Inject constructor(
// Not handled
return false
}
override fun navToMemberProfile(userId: String): Boolean {
navigator.openRoomMemberProfile(userId, roomDetailArgs.roomId, vectorBaseActivity)
return true
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@ -944,7 +991,7 @@ class RoomDetailFragment @Inject constructor(
}
override fun onAvatarClicked(informationData: MessageInformationData) {
vectorBaseActivity.notImplemented("Click on user avatar")
navigator.openRoomMemberProfile(userId = informationData.senderId, roomId = roomDetailArgs.roomId, context = requireActivity())
}
override fun onMemberNameClicked(informationData: MessageInformationData) {
@ -973,7 +1020,7 @@ class RoomDetailFragment @Inject constructor(
override fun onRoomCreateLinkClicked(url: String) {
permalinkHandler
.launch(requireContext(), url, object : NavigateToRoomInterceptor {
.launch(requireContext(), url, object : NavigationInterceptor {
override fun navToRoom(roomId: String?, eventId: String?): Boolean {
requireActivity().finish()
return false
@ -1124,7 +1171,7 @@ class RoomDetailFragment @Inject constructor(
val startToCompose = composerLayout.composerEditText.text.isNullOrBlank()
if (startToCompose
&& userId == session.myUserId) {
&& userId == session.myUserId) {
// Empty composer, current user: start an emote
composerLayout.composerEditText.setText(Command.EMOTE.command + " ")
composerLayout.composerEditText.setSelection(Command.EMOTE.length)
@ -1162,7 +1209,6 @@ class RoomDetailFragment @Inject constructor(
}
}
}
focusComposerAndShowKeyboard()
}

View file

@ -35,7 +35,7 @@ import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities
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.RoomMemberSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
@ -207,7 +207,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
invisibleEventsObservable.accept(action)
}
fun getMember(userId: String): RoomMember? {
fun getMember(userId: String): RoomMemberSummary? {
return room.getRoomMember(userId)
}

View file

@ -21,6 +21,7 @@ import com.airbnb.mvrx.Success
import im.vector.riotx.EmojiCompatFontProvider
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.bottomsheet.*
import im.vector.riotx.core.epoxy.dividerItem
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
@ -70,7 +71,7 @@ class MessageActionsEpoxyController @Inject constructor(private val stringProvid
// Quick reactions
if (state.canReact() && state.quickStates is Success) {
// Separator
bottomSheetSeparatorItem {
dividerItem {
id("reaction_separator")
}
@ -88,14 +89,14 @@ class MessageActionsEpoxyController @Inject constructor(private val stringProvid
}
// Separator
bottomSheetSeparatorItem {
dividerItem {
id("actions_separator")
}
// Action
state.actions.forEachIndexed { index, action ->
if (action is EventSharedAction.Separator) {
bottomSheetSeparatorItem {
dividerItem {
id("separator_$index")
}
} else {

View file

@ -24,7 +24,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited
import im.vector.riotx.core.extensions.localDateTime
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.features.home.getColorFromUserId
import im.vector.riotx.core.utils.getColorFromUserId
import im.vector.riotx.core.date.VectorDateFormatter
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotx.features.home.room.detail.timeline.item.ReactionInfoData

View file

@ -39,6 +39,7 @@ import im.vector.riotx.core.platform.OnBackPressed
import im.vector.riotx.core.platform.StateView
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.home.RoomListDisplayMode
import im.vector.riotx.features.home.room.list.actions.RoomListActionsArgs
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedAction
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
@ -228,7 +229,7 @@ class RoomListFragment @Inject constructor(
roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.MUTE))
}
is RoomListQuickActionsSharedAction.Settings -> {
vectorBaseActivity.notImplemented("Opening room settings")
navigator.openRoomProfile(requireActivity(), quickAction.roomId)
}
is RoomListQuickActionsSharedAction.Leave -> {
AlertDialog.Builder(requireContext())
@ -346,7 +347,7 @@ class RoomListFragment @Inject constructor(
roomController.onRoomLongClicked()
RoomListQuickActionsBottomSheet
.newInstance(room.roomId)
.newInstance(room.roomId, RoomListActionsArgs.Mode.FULL)
.show(childFragmentManager, "ROOM_LIST_QUICK_ACTIONS")
return true
}

View file

@ -37,8 +37,15 @@ import javax.inject.Inject
@Parcelize
data class RoomListActionsArgs(
val roomId: String
) : Parcelable
val roomId: String,
val mode: Mode
) : Parcelable {
enum class Mode {
FULL,
NOTIFICATIONS
}
}
/**
* Bottom sheet fragment that shows room information with list of contextual actions
@ -93,9 +100,9 @@ class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), R
}
companion object {
fun newInstance(roomId: String): RoomListQuickActionsBottomSheet {
fun newInstance(roomId: String, mode: RoomListActionsArgs.Mode): RoomListQuickActionsBottomSheet {
return RoomListQuickActionsBottomSheet().apply {
setArguments(RoomListActionsArgs(roomId))
setArguments(RoomListActionsArgs(roomId, mode))
}
}
}

View file

@ -21,7 +21,7 @@ import im.vector.matrix.android.api.session.room.notification.RoomNotificationSt
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetActionItem
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetRoomPreviewItem
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetSeparatorItem
import im.vector.riotx.core.epoxy.dividerItem
import im.vector.riotx.features.home.AvatarRenderer
import javax.inject.Inject
@ -35,18 +35,21 @@ class RoomListQuickActionsEpoxyController @Inject constructor(private val avatar
override fun buildModels(state: RoomListQuickActionsState) {
val roomSummary = state.roomSummary() ?: return
val showAll = state.mode == RoomListActionsArgs.Mode.FULL
// Preview
bottomSheetRoomPreviewItem {
id("preview")
avatarRenderer(avatarRenderer)
matrixItem(roomSummary.toMatrixItem())
settingsClickListener(View.OnClickListener { listener?.didSelectMenuAction(RoomListQuickActionsSharedAction.Settings(roomSummary.roomId)) })
}
if (showAll) {
// Preview
bottomSheetRoomPreviewItem {
id("room_preview")
avatarRenderer(avatarRenderer)
matrixItem(roomSummary.toMatrixItem())
settingsClickListener(View.OnClickListener { listener?.didSelectMenuAction(RoomListQuickActionsSharedAction.Settings(roomSummary.roomId)) })
}
// Notifications
bottomSheetSeparatorItem {
id("notifications_separator")
// Notifications
dividerItem {
id("notifications_separator")
}
}
val selectedRoomState = state.roomNotificationState()
@ -55,11 +58,13 @@ class RoomListQuickActionsEpoxyController @Inject constructor(private val avatar
RoomListQuickActionsSharedAction.NotificationsMentionsOnly(roomSummary.roomId).toBottomSheetItem(2, selectedRoomState)
RoomListQuickActionsSharedAction.NotificationsMute(roomSummary.roomId).toBottomSheetItem(3, selectedRoomState)
// Leave
bottomSheetSeparatorItem {
id("leave_separator")
if (showAll) {
// Leave
dividerItem {
id("leave_separator")
}
RoomListQuickActionsSharedAction.Leave(roomSummary.roomId).toBottomSheetItem(5)
}
RoomListQuickActionsSharedAction.Leave(roomSummary.roomId).toBottomSheetItem(5)
}
private fun RoomListQuickActionsSharedAction.toBottomSheetItem(index: Int, roomNotificationState: RoomNotificationState? = null) {

View file

@ -24,9 +24,10 @@ import im.vector.matrix.android.api.session.room.notification.RoomNotificationSt
data class RoomListQuickActionsState(
val roomId: String,
val mode: RoomListActionsArgs.Mode,
val roomSummary: Async<RoomSummary> = Uninitialized,
val roomNotificationState: Async<RoomNotificationState> = Uninitialized
) : MvRxState {
constructor(args: RoomListActionsArgs) : this(roomId = args.roomId)
constructor(args: RoomListActionsArgs) : this(roomId = args.roomId, mode = args.mode)
}

View file

@ -26,20 +26,22 @@ import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.error.fatalError
import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.core.utils.toast
import im.vector.riotx.features.createdirect.CreateDirectRoomActivity
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupActivity
import im.vector.riotx.features.debug.DebugMenuActivity
import im.vector.riotx.features.home.createdirect.CreateDirectRoomActivity
import im.vector.riotx.features.home.room.detail.RoomDetailActivity
import im.vector.riotx.features.home.room.detail.RoomDetailArgs
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewActivity
import im.vector.riotx.features.roommemberprofile.RoomMemberProfileActivity
import im.vector.riotx.features.roommemberprofile.RoomMemberProfileArgs
import im.vector.riotx.features.roomprofile.RoomProfileActivity
import im.vector.riotx.features.settings.VectorPreferences
import im.vector.riotx.features.settings.VectorSettingsActivity
import im.vector.riotx.features.share.SharedData
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@ -54,16 +56,9 @@ class DefaultNavigator @Inject constructor(
fatalError("Trying to open an unknown room $roomId", vectorPreferences.failFast())
return
}
val args = RoomDetailArgs(roomId, eventId)
val intent = RoomDetailActivity.newIntent(context, args)
if (buildTask) {
val stackBuilder = TaskStackBuilder.create(context)
stackBuilder.addNextIntentWithParentStack(intent)
stackBuilder.startActivities()
} else {
context.startActivity(intent)
}
startActivity(context, intent, buildTask)
}
override fun openNotJoinedRoom(context: Context, roomIdOrAlias: String?, eventId: String?, buildTask: Boolean) {
@ -82,12 +77,10 @@ class DefaultNavigator @Inject constructor(
}
}
override fun openUserDetail(userId: String, context: Context, buildTask: Boolean) {
if (context is VectorBaseActivity) {
context.notImplemented("Open user detail")
} else {
context.toast(R.string.not_implemented)
}
override fun openRoomMemberProfile(userId: String, roomId: String?, context: Context, buildTask: Boolean) {
val args = RoomMemberProfileArgs(userId = userId, roomId = roomId)
val intent = RoomMemberProfileActivity.newIntent(context, args)
startActivity(context, intent, buildTask)
}
override fun openRoomForSharing(activity: Activity, roomId: String, sharedData: SharedData) {
@ -139,7 +132,17 @@ class DefaultNavigator @Inject constructor(
context.startActivity(KeysBackupManageActivity.intent(context))
}
override fun openRoomSettings(context: Context, roomId: String) {
Timber.v("Open room settings$roomId")
override fun openRoomProfile(context: Context, roomId: String) {
context.startActivity(RoomProfileActivity.newIntent(context, roomId))
}
private fun startActivity(context: Context, intent: Intent, buildTask: Boolean) {
if (buildTask) {
val stackBuilder = TaskStackBuilder.create(context)
stackBuilder.addNextIntentWithParentStack(intent)
stackBuilder.startActivities()
} else {
context.startActivity(intent)
}
}
}

View file

@ -50,7 +50,7 @@ interface Navigator {
fun openGroupDetail(groupId: String, context: Context, buildTask: Boolean = false)
fun openUserDetail(userId: String, context: Context, buildTask: Boolean = false)
fun openRoomMemberProfile(userId: String, roomId: String?, context: Context, buildTask: Boolean = false)
fun openRoomSettings(context: Context, roomId: String)
fun openRoomProfile(context: Context, roomId: String)
}

View file

@ -35,17 +35,17 @@ class PermalinkHandler @Inject constructor(private val session: Session,
fun launch(
context: Context,
deepLink: String?,
navigateToRoomInterceptor: NavigateToRoomInterceptor? = null,
navigationInterceptor: NavigationInterceptor? = null,
buildTask: Boolean = false
): Single<Boolean> {
val uri = deepLink?.let { Uri.parse(it) }
return launch(context, uri, navigateToRoomInterceptor, buildTask)
return launch(context, uri, navigationInterceptor, buildTask)
}
fun launch(
context: Context,
deepLink: Uri?,
navigateToRoomInterceptor: NavigateToRoomInterceptor? = null,
navigationInterceptor: NavigationInterceptor? = null,
buildTask: Boolean = false
): Single<Boolean> {
if (deepLink == null) {
@ -57,7 +57,7 @@ class PermalinkHandler @Inject constructor(private val session: Session,
.observeOn(AndroidSchedulers.mainThread())
.map {
val roomId = it.getOrNull()
if (navigateToRoomInterceptor?.navToRoom(roomId, permalinkData.eventId) != true) {
if (navigationInterceptor?.navToRoom(roomId, permalinkData.eventId) != true) {
openRoom(context, roomId, permalinkData.eventId, buildTask)
}
true
@ -68,7 +68,9 @@ class PermalinkHandler @Inject constructor(private val session: Session,
Single.just(true)
}
is PermalinkData.UserLink -> {
navigator.openUserDetail(permalinkData.userId, context, buildTask)
if (navigationInterceptor?.navToMemberProfile(permalinkData.userId) != true) {
navigator.openRoomMemberProfile(userId = permalinkData.userId, roomId = null, context = context, buildTask = buildTask)
}
Single.just(true)
}
is PermalinkData.FallbackLink -> {
@ -98,10 +100,19 @@ class PermalinkHandler @Inject constructor(private val session: Session,
}
}
interface NavigateToRoomInterceptor {
interface NavigationInterceptor {
/**
* Return true if the navigation has been intercepted
*/
fun navToRoom(roomId: String?, eventId: String? = null): Boolean
fun navToRoom(roomId: String?, eventId: String? = null): Boolean {
return false
}
/**
* Return true if the navigation has been intercepted
*/
fun navToMemberProfile(userId: String): Boolean {
return false
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.riotx.features.roommemberprofile
import im.vector.riotx.core.platform.VectorViewModelAction
sealed class RoomMemberProfileAction : VectorViewModelAction {
object RetryFetchingInfo: RoomMemberProfileAction()
object IgnoreUser: RoomMemberProfileAction()
}

View file

@ -0,0 +1,54 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.riotx.features.roommemberprofile
import android.content.Context
import android.content.Intent
import androidx.appcompat.widget.Toolbar
import im.vector.riotx.R
import im.vector.riotx.core.extensions.addFragment
import im.vector.riotx.core.platform.ToolbarConfigurable
import im.vector.riotx.core.platform.VectorBaseActivity
class RoomMemberProfileActivity : VectorBaseActivity(), ToolbarConfigurable {
companion object {
private const val EXTRA_FRAGMENT_ARGS = "EXTRA_FRAGMENT_ARGS"
fun newIntent(context: Context, args: RoomMemberProfileArgs): Intent {
return Intent(context, RoomMemberProfileActivity::class.java).apply {
putExtra(EXTRA_FRAGMENT_ARGS, args)
}
}
}
override fun getLayoutRes() = R.layout.activity_simple
override fun initUiAndData() {
if (isFirstCreation()) {
val fragmentArgs: RoomMemberProfileArgs = intent?.extras?.getParcelable(EXTRA_FRAGMENT_ARGS)
?: return
addFragment(R.id.simpleFragmentContainer, RoomMemberProfileFragment::class.java, fragmentArgs)
}
}
override fun configure(toolbar: Toolbar) {
configureToolbar(toolbar)
}
}

Some files were not shown because too many files have changed in this diff Show more