mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-15 01:35:07 +08:00
Merge pull request #843 from vector-im/feature/room_profile
Feature/room profile
This commit is contained in:
commit
f128ed437f
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
)
|
@ -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,
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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>>
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -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,
|
||||
|
@ -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
|
@ -50,6 +50,6 @@ import io.realm.annotations.RealmModule
|
||||
UserDraftsEntity::class,
|
||||
DraftEntity::class,
|
||||
HomeServerCapabilitiesEntity::class,
|
||||
RoomMemberEntity::class
|
||||
RoomMemberSummaryEntity::class
|
||||
])
|
||||
internal class SessionRealmModule
|
||||
|
@ -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? {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
]
|
||||
)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 }
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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})"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
* ========================================================================================== */
|
||||
|
@ -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) {
|
||||
|
@ -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
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user