mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-15 01:35:07 +08:00
Merge branch 'develop' into feature/event_deletion_dialog
# Conflicts: # CHANGES.md # vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
This commit is contained in:
commit
afbd9cff70
27
CHANGES.md
27
CHANGES.md
@ -1,13 +1,30 @@
|
||||
Changes in RiotX 0.15.0 (2020-XX-XX)
|
||||
Changes in RiotX 0.16.0 (2020-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
-
|
||||
|
||||
Improvements 🙌:
|
||||
- Show confirmation dialog before deleting a message (#967)
|
||||
|
||||
Other changes:
|
||||
-
|
||||
|
||||
Bugfix 🐛:
|
||||
-
|
||||
|
||||
Translations 🗣:
|
||||
-
|
||||
|
||||
Build 🧱:
|
||||
-
|
||||
|
||||
Changes in RiotX 0.15.0 (2020-02-10)
|
||||
===================================================
|
||||
|
||||
Improvements 🙌:
|
||||
- Improve navigation to the timeline (#789, #862)
|
||||
- Improve network detection. It is now based on the sync request status (#873, #882)
|
||||
- Show confirmation dialog before deleting a message (#967)
|
||||
|
||||
Other changes:
|
||||
- Support SSO login with Firefox account (#606)
|
||||
@ -16,12 +33,6 @@ Bugfix 🐛:
|
||||
- Ask for permission before opening the camera (#934)
|
||||
- Encrypt for invited users by default, if the room state allows it (#803)
|
||||
|
||||
Translations 🗣:
|
||||
-
|
||||
|
||||
Build 🧱:
|
||||
-
|
||||
|
||||
Changes in RiotX 0.14.3 (2020-02-03)
|
||||
===================================================
|
||||
|
||||
|
@ -15,30 +15,32 @@
|
||||
*/
|
||||
package im.vector.matrix.android.api.pushrules
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
|
||||
abstract class Condition(val kind: Kind) {
|
||||
|
||||
enum class Kind(val value: String) {
|
||||
event_match("event_match"),
|
||||
contains_display_name("contains_display_name"),
|
||||
room_member_count("room_member_count"),
|
||||
sender_notification_permission("sender_notification_permission"),
|
||||
UNRECOGNIZE("");
|
||||
EventMatch("event_match"),
|
||||
ContainsDisplayName("contains_display_name"),
|
||||
RoomMemberCount("room_member_count"),
|
||||
SenderNotificationPermission("sender_notification_permission"),
|
||||
Unrecognised("");
|
||||
|
||||
companion object {
|
||||
|
||||
fun fromString(value: String): Kind {
|
||||
return when (value) {
|
||||
"event_match" -> event_match
|
||||
"contains_display_name" -> contains_display_name
|
||||
"room_member_count" -> room_member_count
|
||||
"sender_notification_permission" -> sender_notification_permission
|
||||
else -> UNRECOGNIZE
|
||||
"event_match" -> EventMatch
|
||||
"contains_display_name" -> ContainsDisplayName
|
||||
"room_member_count" -> RoomMemberCount
|
||||
"sender_notification_permission" -> SenderNotificationPermission
|
||||
else -> Unrecognised
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun isSatisfied(conditionResolver: ConditionResolver): Boolean
|
||||
abstract fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean
|
||||
|
||||
open fun technicalDescription(): String {
|
||||
return "Kind: $kind"
|
||||
|
@ -15,14 +15,22 @@
|
||||
*/
|
||||
package im.vector.matrix.android.api.pushrules
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
|
||||
/**
|
||||
* Acts like a visitor on Conditions.
|
||||
* This class as all required context needed to evaluate rules
|
||||
*/
|
||||
interface ConditionResolver {
|
||||
fun resolveEventMatchCondition(event: Event,
|
||||
condition: EventMatchCondition): Boolean
|
||||
|
||||
fun resolveEventMatchCondition(eventMatchCondition: EventMatchCondition): Boolean
|
||||
fun resolveRoomMemberCountCondition(roomMemberCountCondition: RoomMemberCountCondition): Boolean
|
||||
fun resolveSenderNotificationPermissionCondition(senderNotificationPermissionCondition: SenderNotificationPermissionCondition): Boolean
|
||||
fun resolveContainsDisplayNameCondition(containsDisplayNameCondition: ContainsDisplayNameCondition) : Boolean
|
||||
fun resolveRoomMemberCountCondition(event: Event,
|
||||
condition: RoomMemberCountCondition): Boolean
|
||||
|
||||
fun resolveSenderNotificationPermissionCondition(event: Event,
|
||||
condition: SenderNotificationPermissionCondition): Boolean
|
||||
|
||||
fun resolveContainsDisplayNameCondition(event: Event,
|
||||
condition: ContainsDisplayNameCondition): Boolean
|
||||
}
|
||||
|
@ -21,10 +21,10 @@ import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import timber.log.Timber
|
||||
|
||||
class ContainsDisplayNameCondition : Condition(Kind.contains_display_name) {
|
||||
class ContainsDisplayNameCondition : Condition(Kind.ContainsDisplayName) {
|
||||
|
||||
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
|
||||
return conditionResolver.resolveContainsDisplayNameCondition(this)
|
||||
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
|
||||
return conditionResolver.resolveContainsDisplayNameCondition(event, this)
|
||||
}
|
||||
|
||||
override fun technicalDescription(): String {
|
||||
|
@ -19,10 +19,20 @@ import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import timber.log.Timber
|
||||
|
||||
class EventMatchCondition(val key: String, val pattern: String) : Condition(Kind.event_match) {
|
||||
class EventMatchCondition(
|
||||
/**
|
||||
* The dot-separated field of the event to match, e.g. content.body
|
||||
*/
|
||||
val key: String,
|
||||
/**
|
||||
* The glob-style pattern to match against. Patterns with no special glob characters should
|
||||
* be treated as having asterisks prepended and appended when testing the condition.
|
||||
*/
|
||||
val pattern: String
|
||||
) : Condition(Kind.EventMatch) {
|
||||
|
||||
override fun isSatisfied(conditionResolver: ConditionResolver) : Boolean {
|
||||
return conditionResolver.resolveEventMatchCondition(this)
|
||||
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
|
||||
return conditionResolver.resolveEventMatchCondition(event, this)
|
||||
}
|
||||
|
||||
override fun technicalDescription(): String {
|
||||
|
@ -16,25 +16,32 @@
|
||||
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.RoomService
|
||||
import im.vector.matrix.android.internal.session.room.RoomGetter
|
||||
import timber.log.Timber
|
||||
|
||||
private val regex = Regex("^(==|<=|>=|<|>)?(\\d*)$")
|
||||
|
||||
class RoomMemberCountCondition(val iz: String) : Condition(Kind.room_member_count) {
|
||||
class RoomMemberCountCondition(
|
||||
/**
|
||||
* A decimal integer optionally prefixed by one of ==, <, >, >= or <=.
|
||||
* A prefix of < matches rooms where the member count is strictly less than the given number and so forth.
|
||||
* If no prefix is present, this parameter defaults to ==.
|
||||
*/
|
||||
val iz: String
|
||||
) : Condition(Kind.RoomMemberCount) {
|
||||
|
||||
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
|
||||
return conditionResolver.resolveRoomMemberCountCondition(this)
|
||||
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
|
||||
return conditionResolver.resolveRoomMemberCountCondition(event, this)
|
||||
}
|
||||
|
||||
override fun technicalDescription(): String {
|
||||
return "Room member count is $iz"
|
||||
}
|
||||
|
||||
fun isSatisfied(event: Event, session: RoomService?): Boolean {
|
||||
// sanity check^
|
||||
internal fun isSatisfied(event: Event, roomGetter: RoomGetter): Boolean {
|
||||
// sanity checks
|
||||
val roomId = event.roomId ?: return false
|
||||
val room = session?.getRoom(roomId) ?: return false
|
||||
val room = roomGetter.getRoom(roomId) ?: return false
|
||||
|
||||
// Parse the is field into prefix and number the first time
|
||||
val (prefix, count) = parseIsField() ?: return false
|
||||
|
@ -19,10 +19,18 @@ import im.vector.matrix.android.api.session.events.model.Event
|
||||
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) {
|
||||
class SenderNotificationPermissionCondition(
|
||||
/**
|
||||
* A string that determines the power level the sender must have to trigger notifications of a given type,
|
||||
* such as room. Refer to the m.room.power_levels event schema for information about what the defaults are
|
||||
* and how to interpret the event. The key is used to look up the power level required to send a notification
|
||||
* type from the notifications object in the power level event content.
|
||||
*/
|
||||
val key: String
|
||||
) : Condition(Kind.SenderNotificationPermission) {
|
||||
|
||||
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
|
||||
return conditionResolver.resolveSenderNotificationPermissionCondition(this)
|
||||
override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean {
|
||||
return conditionResolver.resolveSenderNotificationPermissionCondition(event, this)
|
||||
}
|
||||
|
||||
override fun technicalDescription(): String {
|
||||
|
@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass
|
||||
* All push rulesets for a user.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GetPushRulesResponse(
|
||||
internal data class GetPushRulesResponse(
|
||||
/**
|
||||
* Global rules, account level applying to all devices
|
||||
*/
|
||||
|
@ -17,7 +17,11 @@ package im.vector.matrix.android.api.pushrules.rest
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.pushrules.*
|
||||
import im.vector.matrix.android.api.pushrules.Condition
|
||||
import im.vector.matrix.android.api.pushrules.ContainsDisplayNameCondition
|
||||
import im.vector.matrix.android.api.pushrules.EventMatchCondition
|
||||
import im.vector.matrix.android.api.pushrules.RoomMemberCountCondition
|
||||
import im.vector.matrix.android.api.pushrules.SenderNotificationPermissionCondition
|
||||
import timber.log.Timber
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
@ -30,13 +34,13 @@ data class PushCondition(
|
||||
/**
|
||||
* Required for event_match conditions. The dot- separated field of the event to match.
|
||||
*/
|
||||
|
||||
val key: String? = null,
|
||||
/**
|
||||
*Required for event_match conditions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Required for event_match conditions.
|
||||
*/
|
||||
val pattern: String? = null,
|
||||
|
||||
/**
|
||||
* Required for room_member_count conditions.
|
||||
* A decimal integer optionally prefixed by one of, ==, <, >, >= or <=.
|
||||
@ -47,30 +51,35 @@ data class PushCondition(
|
||||
) {
|
||||
|
||||
fun asExecutableCondition(): Condition? {
|
||||
return when (Condition.Kind.fromString(this.kind)) {
|
||||
Condition.Kind.event_match -> {
|
||||
if (this.key != null && this.pattern != null) {
|
||||
return when (Condition.Kind.fromString(kind)) {
|
||||
Condition.Kind.EventMatch -> {
|
||||
if (key != null && pattern != null) {
|
||||
EventMatchCondition(key, pattern)
|
||||
} else {
|
||||
Timber.e("Malformed Event match condition")
|
||||
null
|
||||
}
|
||||
}
|
||||
Condition.Kind.contains_display_name -> {
|
||||
Condition.Kind.ContainsDisplayName -> {
|
||||
ContainsDisplayNameCondition()
|
||||
}
|
||||
Condition.Kind.room_member_count -> {
|
||||
if (this.iz.isNullOrBlank()) {
|
||||
Condition.Kind.RoomMemberCount -> {
|
||||
if (iz.isNullOrEmpty()) {
|
||||
Timber.e("Malformed ROOM_MEMBER_COUNT condition")
|
||||
null
|
||||
} else {
|
||||
RoomMemberCountCondition(this.iz)
|
||||
RoomMemberCountCondition(iz)
|
||||
}
|
||||
}
|
||||
Condition.Kind.sender_notification_permission -> {
|
||||
this.key?.let { SenderNotificationPermissionCondition(it) }
|
||||
Condition.Kind.SenderNotificationPermission -> {
|
||||
if (key == null) {
|
||||
Timber.e("Malformed Sender Notification Permission condition")
|
||||
null
|
||||
} else {
|
||||
SenderNotificationPermissionCondition(key)
|
||||
}
|
||||
}
|
||||
Condition.Kind.UNRECOGNIZE -> {
|
||||
Condition.Kind.Unrecognised -> {
|
||||
Timber.e("Unknown kind $kind")
|
||||
null
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ package im.vector.matrix.android.api.pushrules.rest
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Ruleset(
|
||||
internal data class Ruleset(
|
||||
val content: List<PushRule>? = null,
|
||||
val override: List<PushRule>? = null,
|
||||
val room: List<PushRule>? = null,
|
||||
|
@ -110,9 +110,11 @@ internal class ShieldTrustUpdater @Inject constructor(
|
||||
|
||||
val map = HashMap<String, List<String>>()
|
||||
impactedRoomsId.forEach { roomId ->
|
||||
RoomMemberSummaryEntity.where(backgroundSessionRealm.get(), roomId).findAll()?.let { results ->
|
||||
map[roomId] = results.map { it.userId }
|
||||
}
|
||||
RoomMemberSummaryEntity.where(backgroundSessionRealm.get(), roomId)
|
||||
.findAll()
|
||||
.let { results ->
|
||||
map[roomId] = results.map { it.userId }
|
||||
}
|
||||
}
|
||||
|
||||
map.forEach { entry ->
|
||||
|
@ -38,7 +38,7 @@ internal object PushRulesMapper {
|
||||
enabled = pushrule.enabled,
|
||||
ruleId = pushrule.ruleId,
|
||||
conditions = listOf(
|
||||
PushCondition(Condition.Kind.event_match.name, "content.body", pushrule.pattern)
|
||||
PushCondition(Condition.Kind.EventMatch.value, "content.body", pushrule.pattern)
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -59,7 +59,7 @@ internal object PushRulesMapper {
|
||||
enabled = pushrule.enabled,
|
||||
ruleId = pushrule.ruleId,
|
||||
conditions = listOf(
|
||||
PushCondition(Condition.Kind.event_match.name, "room_id", pushrule.ruleId)
|
||||
PushCondition(Condition.Kind.EventMatch.value, "room_id", pushrule.ruleId)
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -71,7 +71,7 @@ internal object PushRulesMapper {
|
||||
enabled = pushrule.enabled,
|
||||
ruleId = pushrule.ruleId,
|
||||
conditions = listOf(
|
||||
PushCondition(Condition.Kind.event_match.name, "user_id", pushrule.ruleId)
|
||||
PushCondition(Condition.Kind.EventMatch.value, "user_id", pushrule.ruleId)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -38,12 +38,13 @@ import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@SessionScope
|
||||
internal class DefaultPushRuleService @Inject constructor(private val getPushRulesTask: GetPushRulesTask,
|
||||
private val updatePushRuleEnableStatusTask: UpdatePushRuleEnableStatusTask,
|
||||
private val addPushRuleTask: AddPushRuleTask,
|
||||
private val removePushRuleTask: RemovePushRuleTask,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val monarchy: Monarchy
|
||||
internal class DefaultPushRuleService @Inject constructor(
|
||||
private val getPushRulesTask: GetPushRulesTask,
|
||||
private val updatePushRuleEnableStatusTask: UpdatePushRuleEnableStatusTask,
|
||||
private val addPushRuleTask: AddPushRuleTask,
|
||||
private val removePushRuleTask: RemovePushRuleTask,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val monarchy: Monarchy
|
||||
) : PushRuleService {
|
||||
|
||||
private var listeners = mutableSetOf<PushRuleService.PushRuleListener>()
|
||||
|
@ -16,12 +16,11 @@
|
||||
|
||||
package im.vector.matrix.android.internal.session.notification
|
||||
|
||||
import im.vector.matrix.android.api.pushrules.ConditionResolver
|
||||
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
||||
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.RoomService
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.session.pushers.DefaultConditionResolver
|
||||
import im.vector.matrix.android.internal.session.sync.model.RoomsSyncResponse
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import timber.log.Timber
|
||||
@ -36,7 +35,7 @@ internal interface ProcessEventForPushTask : Task<ProcessEventForPushTask.Params
|
||||
|
||||
internal class DefaultProcessEventForPushTask @Inject constructor(
|
||||
private val defaultPushRuleService: DefaultPushRuleService,
|
||||
private val roomService: RoomService,
|
||||
private val conditionResolver: ConditionResolver,
|
||||
@UserId private val userId: String
|
||||
) : ProcessEventForPushTask {
|
||||
|
||||
@ -97,12 +96,10 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
|
||||
}
|
||||
|
||||
private fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? {
|
||||
// TODO This should be injected
|
||||
val conditionResolver = DefaultConditionResolver(event, roomService, userId)
|
||||
return rules.firstOrNull { rule ->
|
||||
// All conditions must hold true for an event in order to apply the action for the event.
|
||||
rule.enabled && rule.conditions?.all {
|
||||
it.asExecutableCondition()?.isSatisfied(conditionResolver) ?: false
|
||||
it.asExecutableCondition()?.isSatisfied(event, conditionResolver) ?: false
|
||||
} ?: false
|
||||
}
|
||||
}
|
||||
|
@ -15,37 +15,52 @@
|
||||
*/
|
||||
package im.vector.matrix.android.internal.session.pushers
|
||||
|
||||
import im.vector.matrix.android.api.pushrules.*
|
||||
import im.vector.matrix.android.api.pushrules.ConditionResolver
|
||||
import im.vector.matrix.android.api.pushrules.ContainsDisplayNameCondition
|
||||
import im.vector.matrix.android.api.pushrules.EventMatchCondition
|
||||
import im.vector.matrix.android.api.pushrules.RoomMemberCountCondition
|
||||
import im.vector.matrix.android.api.pushrules.SenderNotificationPermissionCondition
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.RoomService
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import timber.log.Timber
|
||||
import im.vector.matrix.android.internal.session.room.RoomGetter
|
||||
import javax.inject.Inject
|
||||
|
||||
// TODO Inject constructor
|
||||
internal class DefaultConditionResolver(private val event: Event,
|
||||
private val roomService: RoomService,
|
||||
@UserId private val userId: String) : ConditionResolver {
|
||||
internal class DefaultConditionResolver @Inject constructor(
|
||||
private val roomGetter: RoomGetter,
|
||||
@UserId private val userId: String
|
||||
) : ConditionResolver {
|
||||
|
||||
override fun resolveEventMatchCondition(eventMatchCondition: EventMatchCondition): Boolean {
|
||||
return eventMatchCondition.isSatisfied(event)
|
||||
override fun resolveEventMatchCondition(event: Event,
|
||||
condition: EventMatchCondition): Boolean {
|
||||
return condition.isSatisfied(event)
|
||||
}
|
||||
|
||||
override fun resolveRoomMemberCountCondition(roomMemberCountCondition: RoomMemberCountCondition): Boolean {
|
||||
return roomMemberCountCondition.isSatisfied(event, roomService)
|
||||
override fun resolveRoomMemberCountCondition(event: Event,
|
||||
condition: RoomMemberCountCondition): Boolean {
|
||||
return condition.isSatisfied(event, roomGetter)
|
||||
}
|
||||
|
||||
override fun resolveSenderNotificationPermissionCondition(senderNotificationPermissionCondition: SenderNotificationPermissionCondition): Boolean {
|
||||
// val roomId = event.roomId ?: return false
|
||||
// val room = roomService.getRoom(roomId) ?: return false
|
||||
// TODO RoomState not yet managed
|
||||
Timber.e("POWER LEVELS STATE NOT YET MANAGED BY RIOTX")
|
||||
return false // senderNotificationPermissionCondition.isSatisfied(event, )
|
||||
}
|
||||
|
||||
override fun resolveContainsDisplayNameCondition(containsDisplayNameCondition: ContainsDisplayNameCondition): Boolean {
|
||||
override fun resolveSenderNotificationPermissionCondition(event: Event,
|
||||
condition: SenderNotificationPermissionCondition): Boolean {
|
||||
val roomId = event.roomId ?: return false
|
||||
val room = roomService.getRoom(roomId) ?: return false
|
||||
val room = roomGetter.getRoom(roomId) ?: return false
|
||||
|
||||
val powerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, "")
|
||||
?.content
|
||||
?.toModel<PowerLevelsContent>()
|
||||
?: PowerLevelsContent()
|
||||
|
||||
return condition.isSatisfied(event, powerLevelsContent)
|
||||
}
|
||||
|
||||
override fun resolveContainsDisplayNameCondition(event: Event,
|
||||
condition: ContainsDisplayNameCondition): Boolean {
|
||||
val roomId = event.roomId ?: return false
|
||||
val room = roomGetter.getRoom(roomId) ?: return false
|
||||
val myDisplayName = room.getRoomMember(userId)?.displayName ?: return false
|
||||
return containsDisplayNameCondition.isSatisfied(event, myDisplayName)
|
||||
return condition.isSatisfied(event, myDisplayName)
|
||||
}
|
||||
}
|
||||
|
@ -22,14 +22,12 @@ import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.api.session.room.RoomService
|
||||
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.VersioningState
|
||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
|
||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
||||
import im.vector.matrix.android.internal.database.query.findByAlias
|
||||
@ -37,7 +35,6 @@ import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.query.process
|
||||
import im.vector.matrix.android.internal.session.room.alias.GetRoomIdByAliasTask
|
||||
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
|
||||
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
|
||||
import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.UpdateBreadcrumbsTask
|
||||
@ -48,15 +45,17 @@ import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultRoomService @Inject constructor(private val monarchy: Monarchy,
|
||||
private val roomSummaryMapper: RoomSummaryMapper,
|
||||
private val createRoomTask: CreateRoomTask,
|
||||
private val joinRoomTask: JoinRoomTask,
|
||||
private val markAllRoomsReadTask: MarkAllRoomsReadTask,
|
||||
private val updateBreadcrumbsTask: UpdateBreadcrumbsTask,
|
||||
private val roomIdByAliasTask: GetRoomIdByAliasTask,
|
||||
private val roomFactory: RoomFactory,
|
||||
private val taskExecutor: TaskExecutor) : RoomService {
|
||||
internal class DefaultRoomService @Inject constructor(
|
||||
private val monarchy: Monarchy,
|
||||
private val roomSummaryMapper: RoomSummaryMapper,
|
||||
private val createRoomTask: CreateRoomTask,
|
||||
private val joinRoomTask: JoinRoomTask,
|
||||
private val markAllRoomsReadTask: MarkAllRoomsReadTask,
|
||||
private val updateBreadcrumbsTask: UpdateBreadcrumbsTask,
|
||||
private val roomIdByAliasTask: GetRoomIdByAliasTask,
|
||||
private val roomGetter: RoomGetter,
|
||||
private val taskExecutor: TaskExecutor
|
||||
) : RoomService {
|
||||
|
||||
override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>): Cancelable {
|
||||
return createRoomTask
|
||||
@ -67,33 +66,11 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
|
||||
}
|
||||
|
||||
override fun getRoom(roomId: String): Room? {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use {
|
||||
if (RoomEntity.where(it, roomId).findFirst() != null) {
|
||||
roomFactory.create(roomId)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
return roomGetter.getRoom(roomId)
|
||||
}
|
||||
|
||||
override fun getExistingDirectRoomWithUser(otherUserId: String): Room? {
|
||||
Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
val candidates = RoomSummaryEntity.where(realm)
|
||||
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
|
||||
.findAll()?.filter { dm ->
|
||||
dm.otherMemberIds.contains(otherUserId)
|
||||
&& dm.membership == Membership.JOIN
|
||||
}?.map {
|
||||
it.roomId
|
||||
}
|
||||
?: return null
|
||||
candidates.forEach { roomId ->
|
||||
if (RoomMemberHelper(realm, roomId).getActiveRoomMemberIds().any { it == otherUserId }) {
|
||||
return RoomEntity.where(realm, roomId).findFirst()?.let { roomFactory.create(roomId) }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
return roomGetter.getDirectRoomWith(otherUserId)
|
||||
}
|
||||
|
||||
override fun getRoomSummary(roomIdOrAlias: String): RoomSummary? {
|
||||
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 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.room
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
|
||||
import io.realm.Realm
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface RoomGetter {
|
||||
fun getRoom(roomId: String): Room?
|
||||
|
||||
fun getDirectRoomWith(otherUserId: String): Room?
|
||||
}
|
||||
|
||||
@SessionScope
|
||||
internal class DefaultRoomGetter @Inject constructor(
|
||||
private val monarchy: Monarchy,
|
||||
private val roomFactory: RoomFactory
|
||||
) : RoomGetter {
|
||||
|
||||
override fun getRoom(roomId: String): Room? {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
createRoom(realm, roomId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDirectRoomWith(otherUserId: String): Room? {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
RoomSummaryEntity.where(realm)
|
||||
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
|
||||
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
||||
.findAll()
|
||||
.filter { dm -> dm.otherMemberIds.contains(otherUserId) }
|
||||
.map { it.roomId }
|
||||
.firstOrNull { roomId -> otherUserId in RoomMemberHelper(realm, roomId).getActiveRoomMemberIds() }
|
||||
?.let { roomId -> createRoom(realm, roomId) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun createRoom(realm: Realm, roomId: String): Room? {
|
||||
return RoomEntity.where(realm, roomId).findFirst()
|
||||
?.let { roomFactory.create(roomId) }
|
||||
}
|
||||
}
|
@ -46,12 +46,20 @@ import im.vector.matrix.android.internal.session.room.read.DefaultMarkAllRoomsRe
|
||||
import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask
|
||||
import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask
|
||||
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
|
||||
import im.vector.matrix.android.internal.session.room.relation.*
|
||||
import im.vector.matrix.android.internal.session.room.relation.DefaultFetchEditHistoryTask
|
||||
import im.vector.matrix.android.internal.session.room.relation.DefaultFindReactionEventForUndoTask
|
||||
import im.vector.matrix.android.internal.session.room.relation.DefaultUpdateQuickReactionTask
|
||||
import im.vector.matrix.android.internal.session.room.relation.FetchEditHistoryTask
|
||||
import im.vector.matrix.android.internal.session.room.relation.FindReactionEventForUndoTask
|
||||
import im.vector.matrix.android.internal.session.room.relation.UpdateQuickReactionTask
|
||||
import im.vector.matrix.android.internal.session.room.reporting.DefaultReportContentTask
|
||||
import im.vector.matrix.android.internal.session.room.reporting.ReportContentTask
|
||||
import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask
|
||||
import im.vector.matrix.android.internal.session.room.state.SendStateTask
|
||||
import im.vector.matrix.android.internal.session.room.timeline.*
|
||||
import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask
|
||||
import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask
|
||||
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
|
||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
|
||||
import im.vector.matrix.android.internal.session.room.typing.DefaultSendTypingTask
|
||||
import im.vector.matrix.android.internal.session.room.typing.SendTypingTask
|
||||
import retrofit2.Retrofit
|
||||
@ -72,6 +80,9 @@ internal abstract class RoomModule {
|
||||
@Binds
|
||||
abstract fun bindRoomFactory(factory: DefaultRoomFactory): RoomFactory
|
||||
|
||||
@Binds
|
||||
abstract fun bindRoomGetter(getter: DefaultRoomGetter): RoomGetter
|
||||
|
||||
@Binds
|
||||
abstract fun bindRoomService(service: DefaultRoomService): RoomService
|
||||
|
||||
|
@ -54,7 +54,7 @@ internal fun RoomNotificationState.toRoomPushRule(roomId: String): RoomPushRule?
|
||||
}
|
||||
else -> {
|
||||
val condition = PushCondition(
|
||||
kind = Condition.Kind.event_match.value,
|
||||
kind = Condition.Kind.EventMatch.value,
|
||||
key = "room_id",
|
||||
pattern = roomId
|
||||
)
|
||||
|
@ -19,18 +19,23 @@ package im.vector.matrix.android.api.pushrules
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.api.session.room.RoomService
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||
import im.vector.matrix.android.internal.session.room.RoomGetter
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.amshove.kluent.shouldBe
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class PushrulesConditionTest {
|
||||
|
||||
/* ==========================================================================================
|
||||
* Test EventMatchCondition
|
||||
* ========================================================================================== */
|
||||
|
||||
@Test
|
||||
fun test_eventmatch_type_condition() {
|
||||
val condition = EventMatchCondition("type", "m.room.message")
|
||||
@ -120,6 +125,24 @@ class PushrulesConditionTest {
|
||||
assert(condition.isSatisfied(simpleTextEvent))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_notice_condition() {
|
||||
val conditionEqual = EventMatchCondition("content.msgtype", "m.notice")
|
||||
|
||||
Event(
|
||||
type = "m.room.message",
|
||||
eventId = "mx0",
|
||||
content = MessageTextContent("m.notice", "A").toContent(),
|
||||
originServerTs = 0,
|
||||
roomId = "2joined").also {
|
||||
assertTrue("Notice", conditionEqual.isSatisfied(it))
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* Test RoomMemberCountCondition
|
||||
* ========================================================================================== */
|
||||
|
||||
@Test
|
||||
fun test_roommember_condition() {
|
||||
val conditionEqual3 = RoomMemberCountCondition("3")
|
||||
@ -137,7 +160,7 @@ class PushrulesConditionTest {
|
||||
every { getNumberOfJoinedMembers() } returns 3
|
||||
}
|
||||
|
||||
val sessionStub = mockk<RoomService> {
|
||||
val roomGetterStub = mockk<RoomGetter> {
|
||||
every { getRoom(room2JoinedId) } returns roomStub2Joined
|
||||
every { getRoom(room3JoinedId) } returns roomStub3Joined
|
||||
}
|
||||
@ -148,9 +171,9 @@ class PushrulesConditionTest {
|
||||
content = MessageTextContent("m.text", "A").toContent(),
|
||||
originServerTs = 0,
|
||||
roomId = room2JoinedId).also {
|
||||
assertFalse("This room does not have 3 members", conditionEqual3.isSatisfied(it, sessionStub))
|
||||
assertFalse("This room does not have 3 members", conditionEqual3Bis.isSatisfied(it, sessionStub))
|
||||
assertTrue("This room has less than 3 members", conditionLessThan3.isSatisfied(it, sessionStub))
|
||||
assertFalse("This room does not have 3 members", conditionEqual3.isSatisfied(it, roomGetterStub))
|
||||
assertFalse("This room does not have 3 members", conditionEqual3Bis.isSatisfied(it, roomGetterStub))
|
||||
assertTrue("This room has less than 3 members", conditionLessThan3.isSatisfied(it, roomGetterStub))
|
||||
}
|
||||
|
||||
Event(
|
||||
@ -159,23 +182,36 @@ class PushrulesConditionTest {
|
||||
content = MessageTextContent("m.text", "A").toContent(),
|
||||
originServerTs = 0,
|
||||
roomId = room3JoinedId).also {
|
||||
assertTrue("This room has 3 members", conditionEqual3.isSatisfied(it, sessionStub))
|
||||
assertTrue("This room has 3 members", conditionEqual3Bis.isSatisfied(it, sessionStub))
|
||||
assertFalse("This room has more than 3 members", conditionLessThan3.isSatisfied(it, sessionStub))
|
||||
assertTrue("This room has 3 members", conditionEqual3.isSatisfied(it, roomGetterStub))
|
||||
assertTrue("This room has 3 members", conditionEqual3Bis.isSatisfied(it, roomGetterStub))
|
||||
assertFalse("This room has more than 3 members", conditionLessThan3.isSatisfied(it, roomGetterStub))
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* Test ContainsDisplayNameCondition
|
||||
* ========================================================================================== */
|
||||
|
||||
@Test
|
||||
fun test_notice_condition() {
|
||||
val conditionEqual = EventMatchCondition("content.msgtype", "m.notice")
|
||||
fun test_displayName_condition() {
|
||||
val condition = ContainsDisplayNameCondition()
|
||||
|
||||
Event(
|
||||
val event = Event(
|
||||
type = "m.room.message",
|
||||
eventId = "mx0",
|
||||
content = MessageTextContent("m.notice", "A").toContent(),
|
||||
content = MessageTextContent("m.text", "How was the cake benoit?").toContent(),
|
||||
originServerTs = 0,
|
||||
roomId = "2joined").also {
|
||||
assertTrue("Notice", conditionEqual.isSatisfied(it))
|
||||
}
|
||||
roomId = "2joined")
|
||||
|
||||
condition.isSatisfied(event, "how") shouldBe true
|
||||
condition.isSatisfied(event, "How") shouldBe true
|
||||
condition.isSatisfied(event, "benoit") shouldBe true
|
||||
condition.isSatisfied(event, "Benoit") shouldBe true
|
||||
condition.isSatisfied(event, "cake") shouldBe true
|
||||
|
||||
condition.isSatisfied(event, "ben") shouldBe false
|
||||
condition.isSatisfied(event, "oit") shouldBe false
|
||||
condition.isSatisfied(event, "enoi") shouldBe false
|
||||
condition.isSatisfied(event, "H") shouldBe false
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ androidExtensions {
|
||||
}
|
||||
|
||||
ext.versionMajor = 0
|
||||
ext.versionMinor = 15
|
||||
ext.versionMinor = 16
|
||||
ext.versionPatch = 0
|
||||
|
||||
static def getGitTimestamp() {
|
||||
|
@ -37,6 +37,9 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import im.vector.riotx.core.di.DaggerScreenComponent
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
import im.vector.riotx.core.utils.DimensionConverter
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
@ -87,10 +90,18 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment()
|
||||
return view
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
unBinder?.unbind()
|
||||
unBinder = null
|
||||
uiDisposables.clear()
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onDestroy() {
|
||||
uiDisposables.dispose()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
@ -146,4 +157,29 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment()
|
||||
protected fun setArguments(args: Parcelable? = null) {
|
||||
arguments = args?.let { Bundle().apply { putParcelable(MvRx.KEY_ARG, it) } }
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* Disposable
|
||||
* ========================================================================================== */
|
||||
|
||||
private val uiDisposables = CompositeDisposable()
|
||||
|
||||
protected fun Disposable.disposeOnDestroyView(): Disposable {
|
||||
uiDisposables.add(this)
|
||||
return this
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* ViewEvents
|
||||
* ========================================================================================== */
|
||||
|
||||
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
|
||||
viewEvents
|
||||
.observe()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
observer(it)
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,11 @@ import com.google.android.material.chip.ChipGroup
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.*
|
||||
import im.vector.riotx.core.extensions.cleanup
|
||||
import im.vector.riotx.core.extensions.configureWith
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.extensions.hideKeyboard
|
||||
import im.vector.riotx.core.extensions.setupAsSearch
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import im.vector.riotx.core.utils.DimensionConverter
|
||||
import kotlinx.android.synthetic.main.fragment_create_direct_room.*
|
||||
@ -57,8 +61,10 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor(
|
||||
setupFilterView()
|
||||
setupAddByMatrixIdView()
|
||||
setupCloseView()
|
||||
viewModel.selectUserEvent.observeEvent(this) {
|
||||
updateChipsView(it)
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is CreateDirectRoomViewEvents.SelectUserAction -> updateChipsView(it)
|
||||
}.exhaustive
|
||||
}
|
||||
viewModel.selectSubscribe(this, CreateDirectRoomViewState::selectedUsers) {
|
||||
renderSelectedUsers(it)
|
||||
@ -132,7 +138,7 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor(
|
||||
knownUsersController.setData(it)
|
||||
}
|
||||
|
||||
private fun updateChipsView(data: SelectUserAction) {
|
||||
private fun updateChipsView(data: CreateDirectRoomViewEvents.SelectUserAction) {
|
||||
if (data.isAdded) {
|
||||
addChipToGroup(data.user, chipGroup)
|
||||
} else {
|
||||
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 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.createdirect
|
||||
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
|
||||
/**
|
||||
* Transient events for create direct room screen
|
||||
*/
|
||||
sealed class CreateDirectRoomViewEvents : VectorViewEvents {
|
||||
data class SelectUserAction(
|
||||
val user: User,
|
||||
val isAdded: Boolean,
|
||||
val index: Int
|
||||
) : CreateDirectRoomViewEvents()
|
||||
}
|
@ -18,8 +18,6 @@
|
||||
|
||||
package im.vector.riotx.features.createdirect
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import arrow.core.Option
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
@ -32,10 +30,7 @@ import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import im.vector.matrix.android.api.util.toMatrixItem
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotx.core.extensions.postLiveEvent
|
||||
import im.vector.riotx.core.platform.EmptyViewEvents
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import java.util.concurrent.TimeUnit
|
||||
@ -43,16 +38,10 @@ import java.util.concurrent.TimeUnit
|
||||
private typealias KnowUsersFilter = String
|
||||
private typealias DirectoryUsersSearch = String
|
||||
|
||||
data class SelectUserAction(
|
||||
val user: User,
|
||||
val isAdded: Boolean,
|
||||
val index: Int
|
||||
)
|
||||
|
||||
class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||
initialState: CreateDirectRoomViewState,
|
||||
private val session: Session)
|
||||
: VectorViewModel<CreateDirectRoomViewState, CreateDirectRoomAction, EmptyViewEvents>(initialState) {
|
||||
: VectorViewModel<CreateDirectRoomViewState, CreateDirectRoomAction, CreateDirectRoomViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
@ -62,10 +51,6 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||
private val knownUsersFilter = BehaviorRelay.createDefault<Option<KnowUsersFilter>>(Option.empty())
|
||||
private val directoryUsersSearch = BehaviorRelay.create<DirectoryUsersSearch>()
|
||||
|
||||
private val _selectUserEvent = MutableLiveData<LiveEvent<SelectUserAction>>()
|
||||
val selectUserEvent: LiveData<LiveEvent<SelectUserAction>>
|
||||
get() = _selectUserEvent
|
||||
|
||||
companion object : MvRxViewModelFactory<CreateDirectRoomViewModel, CreateDirectRoomViewState> {
|
||||
|
||||
@JvmStatic
|
||||
@ -109,7 +94,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||
val index = state.selectedUsers.indexOfFirst { it.userId == action.user.userId }
|
||||
val selectedUsers = state.selectedUsers.minus(action.user)
|
||||
setState { copy(selectedUsers = selectedUsers) }
|
||||
_selectUserEvent.postLiveEvent(SelectUserAction(action.user, false, index))
|
||||
_viewEvents.post(CreateDirectRoomViewEvents.SelectUserAction(action.user, false, index))
|
||||
}
|
||||
|
||||
private fun handleSelectUser(action: CreateDirectRoomAction.SelectUser) = withState { state ->
|
||||
@ -129,7 +114,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||
isAddOperation = false
|
||||
}
|
||||
setState { copy(selectedUsers = selectedUsers) }
|
||||
_selectUserEvent.postLiveEvent(SelectUserAction(action.user, isAddOperation, changeIndex))
|
||||
_viewEvents.post(CreateDirectRoomViewEvents.SelectUserAction(action.user, isAddOperation, changeIndex))
|
||||
}
|
||||
|
||||
private fun observeDirectoryUsers() {
|
||||
|
@ -23,18 +23,17 @@ import android.widget.TextView
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.transition.AutoTransition
|
||||
import androidx.transition.TransitionManager
|
||||
import butterknife.BindView
|
||||
import com.airbnb.mvrx.MvRx
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
import im.vector.riotx.core.extensions.commitTransactionNow
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
||||
import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment
|
||||
import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment
|
||||
@ -56,7 +55,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
val verificationId: String? = null,
|
||||
val roomId: String? = null,
|
||||
// Special mode where UX should show loading wheel until other user sends a request/tx
|
||||
val waitForIncomingRequest : Boolean = false
|
||||
val waitForIncomingRequest: Boolean = false
|
||||
) : Parcelable
|
||||
|
||||
@Inject
|
||||
@ -84,17 +83,11 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewModel.requestLiveData.observe(viewLifecycleOwner, Observer {
|
||||
it.peekContent().let { va ->
|
||||
when (va) {
|
||||
is Success -> {
|
||||
if (va.invoke() is VerificationAction.GotItConclusion) {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is VerificationBottomSheetViewEvents.Dismiss -> dismiss()
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
@ -250,7 +243,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
val WAITING_SELF_VERIF_TAG : String = "WAITING_SELF_VERIF_TAG"
|
||||
const val WAITING_SELF_VERIF_TAG: String = "WAITING_SELF_VERIF_TAG"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 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.crypto.verification
|
||||
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
|
||||
/**
|
||||
* Transient events for the verification bottom sheet
|
||||
*/
|
||||
sealed class VerificationBottomSheetViewEvents : VectorViewEvents {
|
||||
object Dismiss : VerificationBottomSheetViewEvents()
|
||||
}
|
@ -15,8 +15,6 @@
|
||||
*/
|
||||
package im.vector.riotx.features.crypto.verification
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
@ -43,9 +41,7 @@ import im.vector.matrix.android.api.util.MatrixItem
|
||||
import im.vector.matrix.android.api.util.toMatrixItem
|
||||
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.EmptyViewEvents
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
|
||||
data class VerificationBottomSheetViewState(
|
||||
val otherUserMxItem: MatrixItem? = null,
|
||||
@ -63,14 +59,9 @@ data class VerificationBottomSheetViewState(
|
||||
class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted initialState: VerificationBottomSheetViewState,
|
||||
@Assisted args: VerificationBottomSheet.VerificationArgs,
|
||||
private val session: Session)
|
||||
: VectorViewModel<VerificationBottomSheetViewState, VerificationAction, EmptyViewEvents>(initialState),
|
||||
: VectorViewModel<VerificationBottomSheetViewState, VerificationAction, VerificationBottomSheetViewEvents>(initialState),
|
||||
VerificationService.VerificationListener {
|
||||
|
||||
// Can be used for several actions, for a one shot result
|
||||
private val _requestLiveData = MutableLiveData<LiveEvent<Async<VerificationAction>>>()
|
||||
val requestLiveData: LiveData<LiveEvent<Async<VerificationAction>>>
|
||||
get() = _requestLiveData
|
||||
|
||||
init {
|
||||
session.getVerificationService().addListener(this)
|
||||
|
||||
@ -255,7 +246,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
|
||||
?.shortCodeDoesNotMatch()
|
||||
}
|
||||
is VerificationAction.GotItConclusion -> {
|
||||
_requestLiveData.postValue(LiveEvent(Success(action)))
|
||||
_viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.cleanup
|
||||
import im.vector.riotx.core.extensions.configureWith
|
||||
import im.vector.riotx.core.extensions.observeEvent
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.StateView
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import im.vector.riotx.features.home.HomeActivitySharedAction
|
||||
@ -51,8 +51,10 @@ class GroupListFragment @Inject constructor(
|
||||
stateView.contentView = groupListView
|
||||
groupListView.configureWith(groupController)
|
||||
viewModel.subscribe { renderState(it) }
|
||||
viewModel.openGroupLiveData.observeEvent(this) {
|
||||
sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup)
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is GroupListViewEvents.OpenGroupSummary -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup)
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* Copyright (c) 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
|
||||
* 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,
|
||||
@ -14,12 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.home.room.detail
|
||||
package im.vector.riotx.features.grouplist
|
||||
|
||||
import java.io.File
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
|
||||
data class DownloadFileState(
|
||||
val mimeType: String,
|
||||
val file: File?,
|
||||
val throwable: Throwable?
|
||||
)
|
||||
/**
|
||||
* Transient events for group list screen
|
||||
*/
|
||||
sealed class GroupListViewEvents : VectorViewEvents {
|
||||
object OpenGroupSummary : GroupListViewEvents()
|
||||
}
|
@ -17,8 +17,6 @@
|
||||
|
||||
package im.vector.riotx.features.grouplist
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import arrow.core.Option
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
@ -32,11 +30,8 @@ import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.postLiveEvent
|
||||
import im.vector.riotx.core.platform.EmptyViewEvents
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.functions.BiFunction
|
||||
|
||||
@ -46,7 +41,7 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
|
||||
private val selectedGroupStore: SelectedGroupDataSource,
|
||||
private val session: Session,
|
||||
private val stringProvider: StringProvider
|
||||
) : VectorViewModel<GroupListViewState, GroupListAction, EmptyViewEvents>(initialState) {
|
||||
) : VectorViewModel<GroupListViewState, GroupListAction, GroupListViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
@ -62,9 +57,7 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
|
||||
}
|
||||
}
|
||||
|
||||
private val _openGroupLiveData = MutableLiveData<LiveEvent<GroupSummary>>()
|
||||
val openGroupLiveData: LiveData<LiveEvent<GroupSummary>>
|
||||
get() = _openGroupLiveData
|
||||
private var currentGroupId = ""
|
||||
|
||||
init {
|
||||
observeGroupSummaries()
|
||||
@ -74,10 +67,10 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
|
||||
private fun observeSelectionState() {
|
||||
selectSubscribe(GroupListViewState::selectedGroup) { groupSummary ->
|
||||
if (groupSummary != null) {
|
||||
val selectedGroup = _openGroupLiveData.value?.peekContent()
|
||||
// We only want to open group if the updated selectedGroup is a different one.
|
||||
if (selectedGroup?.groupId != groupSummary.groupId) {
|
||||
_openGroupLiveData.postLiveEvent(groupSummary)
|
||||
if (currentGroupId != groupSummary.groupId) {
|
||||
currentGroupId = groupSummary.groupId
|
||||
_viewEvents.post(GroupListViewEvents.OpenGroupSummary)
|
||||
}
|
||||
val optionGroup = Option.just(groupSummary)
|
||||
selectedGroupStore.post(optionGroup)
|
||||
|
@ -90,7 +90,6 @@ import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
|
||||
import im.vector.riotx.core.extensions.cleanup
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
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
|
||||
@ -254,12 +253,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId)
|
||||
}
|
||||
roomDetailViewModel.subscribe { renderState(it) }
|
||||
roomDetailViewModel.sendMessageResultLiveData.observeEvent(viewLifecycleOwner) { renderSendMessageResult(it) }
|
||||
|
||||
roomDetailViewModel.nonBlockingPopAlert.observeEvent(this) { pair ->
|
||||
val message = getString(pair.first, *pair.second.toTypedArray())
|
||||
showSnackWithMessage(message, Snackbar.LENGTH_LONG)
|
||||
}
|
||||
sharedActionViewModel
|
||||
.observe()
|
||||
.subscribe {
|
||||
@ -267,34 +261,10 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
|
||||
roomDetailViewModel.navigateToEvent.observeEvent(this) {
|
||||
val scrollPosition = timelineEventController.searchPositionOfEvent(it)
|
||||
if (scrollPosition == null) {
|
||||
scrollOnHighlightedEventCallback.scheduleScrollTo(it)
|
||||
} else {
|
||||
recyclerView.stopScroll()
|
||||
layoutManager.scrollToPosition(scrollPosition)
|
||||
}
|
||||
}
|
||||
|
||||
roomDetailViewModel.fileTooBigEvent.observeEvent(this) {
|
||||
displayFileTooBigWarning(it)
|
||||
}
|
||||
|
||||
roomDetailViewModel.selectSubscribe(this, RoomDetailViewState::tombstoneEventHandling, uniqueOnly("tombstoneEventHandling")) {
|
||||
renderTombstoneEventHandling(it)
|
||||
}
|
||||
|
||||
roomDetailViewModel.downloadedFileEvent.observeEvent(this) { downloadFileState ->
|
||||
val activity = requireActivity()
|
||||
if (downloadFileState.throwable != null) {
|
||||
activity.toast(errorFormatter.toHumanReadable(downloadFileState.throwable))
|
||||
} else if (downloadFileState.file != null) {
|
||||
activity.toast(getString(R.string.downloaded_file, downloadFileState.file.path))
|
||||
addEntryToDownloadManager(activity, downloadFileState.file, downloadFileState.mimeType)
|
||||
}
|
||||
}
|
||||
|
||||
roomDetailViewModel.selectSubscribe(RoomDetailViewState::sendMode) { mode ->
|
||||
when (mode) {
|
||||
is SendMode.REGULAR -> renderRegularMode(mode.text)
|
||||
@ -308,14 +278,17 @@ class RoomDetailFragment @Inject constructor(
|
||||
syncStateView.render(syncState)
|
||||
}
|
||||
|
||||
roomDetailViewModel.requestLiveData.observeEvent(this) {
|
||||
displayRoomDetailActionResult(it)
|
||||
}
|
||||
|
||||
roomDetailViewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable)
|
||||
is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds)
|
||||
is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it)
|
||||
is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it)
|
||||
is RoomDetailViewEvents.ShowMessage -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG)
|
||||
is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it)
|
||||
is RoomDetailViewEvents.FileTooBigError -> displayFileTooBigError(it)
|
||||
is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it)
|
||||
is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it)
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
@ -370,18 +343,38 @@ class RoomDetailFragment @Inject constructor(
|
||||
jumpToReadMarkerView.callback = this
|
||||
}
|
||||
|
||||
private fun displayFileTooBigWarning(error: FileTooBigError) {
|
||||
private fun navigateToEvent(action: RoomDetailViewEvents.NavigateToEvent) {
|
||||
val scrollPosition = timelineEventController.searchPositionOfEvent(action.eventId)
|
||||
if (scrollPosition == null) {
|
||||
scrollOnHighlightedEventCallback.scheduleScrollTo(action.eventId)
|
||||
} else {
|
||||
recyclerView.stopScroll()
|
||||
layoutManager.scrollToPosition(scrollPosition)
|
||||
}
|
||||
}
|
||||
|
||||
private fun displayFileTooBigError(action: RoomDetailViewEvents.FileTooBigError) {
|
||||
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)
|
||||
action.filename,
|
||||
TextUtils.formatFileSize(requireContext(), action.fileSizeInBytes),
|
||||
TextUtils.formatFileSize(requireContext(), action.homeServerLimitInBytes)
|
||||
))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun handleDownloadFileState(action: RoomDetailViewEvents.DownloadFileState) {
|
||||
val activity = requireActivity()
|
||||
if (action.throwable != null) {
|
||||
activity.toast(errorFormatter.toHumanReadable(action.throwable))
|
||||
} else if (action.file != null) {
|
||||
activity.toast(getString(R.string.downloaded_file, action.file.path))
|
||||
addEntryToDownloadManager(activity, action.file, action.mimeType)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupNotificationView() {
|
||||
notificationAreaView.delegate = object : NotificationAreaView.Delegate {
|
||||
override fun onTombstoneEventClicked(tombstoneEvent: Event) {
|
||||
@ -740,31 +733,31 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderSendMessageResult(sendMessageResult: SendMessageResult) {
|
||||
private fun renderSendMessageResult(sendMessageResult: RoomDetailViewEvents.SendMessageResult) {
|
||||
when (sendMessageResult) {
|
||||
is SendMessageResult.MessageSent -> {
|
||||
is RoomDetailViewEvents.MessageSent -> {
|
||||
updateComposerText("")
|
||||
}
|
||||
is SendMessageResult.SlashCommandHandled -> {
|
||||
is RoomDetailViewEvents.SlashCommandHandled -> {
|
||||
sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) }
|
||||
updateComposerText("")
|
||||
}
|
||||
is SendMessageResult.SlashCommandError -> {
|
||||
is RoomDetailViewEvents.SlashCommandError -> {
|
||||
displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command))
|
||||
}
|
||||
is SendMessageResult.SlashCommandUnknown -> {
|
||||
is RoomDetailViewEvents.SlashCommandUnknown -> {
|
||||
displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command))
|
||||
}
|
||||
is SendMessageResult.SlashCommandResultOk -> {
|
||||
is RoomDetailViewEvents.SlashCommandResultOk -> {
|
||||
updateComposerText("")
|
||||
}
|
||||
is SendMessageResult.SlashCommandResultError -> {
|
||||
is RoomDetailViewEvents.SlashCommandResultError -> {
|
||||
displayCommandError(sendMessageResult.throwable.localizedMessage)
|
||||
}
|
||||
is SendMessageResult.SlashCommandNotImplemented -> {
|
||||
is RoomDetailViewEvents.SlashCommandNotImplemented -> {
|
||||
displayCommandError(getString(R.string.not_implemented))
|
||||
}
|
||||
}
|
||||
} // .exhaustive
|
||||
|
||||
lockSendButton = false
|
||||
}
|
||||
@ -814,84 +807,81 @@ class RoomDetailFragment @Inject constructor(
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun displayRoomDetailActionResult(result: Async<RoomDetailAction>) {
|
||||
when (result) {
|
||||
is Fail -> {
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.dialog_title_error)
|
||||
.setMessage(errorFormatter.toHumanReadable(result.error))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
is Success -> {
|
||||
when (val data = result.invoke()) {
|
||||
is RoomDetailAction.ReportContent -> {
|
||||
when {
|
||||
data.spam -> {
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.content_reported_as_spam_title)
|
||||
.setMessage(R.string.content_reported_as_spam_content)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.setNegativeButton(R.string.block_user) { _, _ ->
|
||||
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
|
||||
}
|
||||
.show()
|
||||
.withColoredButton(DialogInterface.BUTTON_NEGATIVE)
|
||||
}
|
||||
data.inappropriate -> {
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.content_reported_as_inappropriate_title)
|
||||
.setMessage(R.string.content_reported_as_inappropriate_content)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.setNegativeButton(R.string.block_user) { _, _ ->
|
||||
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
|
||||
}
|
||||
.show()
|
||||
.withColoredButton(DialogInterface.BUTTON_NEGATIVE)
|
||||
}
|
||||
else -> {
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.content_reported_title)
|
||||
.setMessage(R.string.content_reported_content)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.setNegativeButton(R.string.block_user) { _, _ ->
|
||||
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
|
||||
}
|
||||
.show()
|
||||
.withColoredButton(DialogInterface.BUTTON_NEGATIVE)
|
||||
}
|
||||
}
|
||||
private fun displayRoomDetailActionFailure(result: RoomDetailViewEvents.ActionFailure) {
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.dialog_title_error)
|
||||
.setMessage(errorFormatter.toHumanReadable(result.throwable))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun displayRoomDetailActionSuccess(result: RoomDetailViewEvents.ActionSuccess) {
|
||||
when (val data = result.action) {
|
||||
is RoomDetailAction.ReportContent -> {
|
||||
when {
|
||||
data.spam -> {
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.content_reported_as_spam_title)
|
||||
.setMessage(R.string.content_reported_as_spam_content)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.setNegativeButton(R.string.block_user) { _, _ ->
|
||||
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
|
||||
}
|
||||
.show()
|
||||
.withColoredButton(DialogInterface.BUTTON_NEGATIVE)
|
||||
}
|
||||
is RoomDetailAction.RequestVerification -> {
|
||||
Timber.v("## SAS RequestVerification action")
|
||||
VerificationBottomSheet.withArgs(
|
||||
roomDetailArgs.roomId,
|
||||
data.userId
|
||||
).show(parentFragmentManager, "REQ")
|
||||
data.inappropriate -> {
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.content_reported_as_inappropriate_title)
|
||||
.setMessage(R.string.content_reported_as_inappropriate_content)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.setNegativeButton(R.string.block_user) { _, _ ->
|
||||
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
|
||||
}
|
||||
.show()
|
||||
.withColoredButton(DialogInterface.BUTTON_NEGATIVE)
|
||||
}
|
||||
is RoomDetailAction.AcceptVerificationRequest -> {
|
||||
Timber.v("## SAS AcceptVerificationRequest action")
|
||||
VerificationBottomSheet.withArgs(
|
||||
roomDetailArgs.roomId,
|
||||
data.otherUserId,
|
||||
data.transactionId
|
||||
).show(parentFragmentManager, "REQ")
|
||||
}
|
||||
is RoomDetailAction.ResumeVerification -> {
|
||||
val otherUserId = data.otherUserId ?: return
|
||||
VerificationBottomSheet().apply {
|
||||
arguments = Bundle().apply {
|
||||
putParcelable(MvRx.KEY_ARG, VerificationBottomSheet.VerificationArgs(
|
||||
otherUserId, data.transactionId, roomId = roomDetailArgs.roomId))
|
||||
}
|
||||
}.show(parentFragmentManager, "REQ")
|
||||
else -> {
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.content_reported_title)
|
||||
.setMessage(R.string.content_reported_content)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.setNegativeButton(R.string.block_user) { _, _ ->
|
||||
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
|
||||
}
|
||||
.show()
|
||||
.withColoredButton(DialogInterface.BUTTON_NEGATIVE)
|
||||
}
|
||||
}
|
||||
}
|
||||
is RoomDetailAction.RequestVerification -> {
|
||||
Timber.v("## SAS RequestVerification action")
|
||||
VerificationBottomSheet.withArgs(
|
||||
roomDetailArgs.roomId,
|
||||
data.userId
|
||||
).show(parentFragmentManager, "REQ")
|
||||
}
|
||||
is RoomDetailAction.AcceptVerificationRequest -> {
|
||||
Timber.v("## SAS AcceptVerificationRequest action")
|
||||
VerificationBottomSheet.withArgs(
|
||||
roomDetailArgs.roomId,
|
||||
data.otherUserId,
|
||||
data.transactionId
|
||||
).show(parentFragmentManager, "REQ")
|
||||
}
|
||||
is RoomDetailAction.ResumeVerification -> {
|
||||
val otherUserId = data.otherUserId ?: return
|
||||
VerificationBottomSheet().apply {
|
||||
arguments = Bundle().apply {
|
||||
putParcelable(MvRx.KEY_ARG, VerificationBottomSheet.VerificationArgs(
|
||||
otherUserId, data.transactionId, roomId = roomDetailArgs.roomId))
|
||||
}
|
||||
}.show(parentFragmentManager, "REQ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TimelineEventController.Callback ************************************************************
|
||||
// TimelineEventController.Callback ************************************************************
|
||||
|
||||
override fun onUrlClicked(url: String): Boolean {
|
||||
permalinkHandler
|
||||
|
@ -16,7 +16,10 @@
|
||||
|
||||
package im.vector.riotx.features.home.room.detail
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
import im.vector.riotx.features.command.Command
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Transient events for RoomDetail
|
||||
@ -24,4 +27,34 @@ import im.vector.riotx.core.platform.VectorViewEvents
|
||||
sealed class RoomDetailViewEvents : VectorViewEvents {
|
||||
data class Failure(val throwable: Throwable) : RoomDetailViewEvents()
|
||||
data class OnNewTimelineEvents(val eventIds: List<String>) : RoomDetailViewEvents()
|
||||
|
||||
data class ActionSuccess(val action: RoomDetailAction) : RoomDetailViewEvents()
|
||||
data class ActionFailure(val action: RoomDetailAction, val throwable: Throwable) : RoomDetailViewEvents()
|
||||
|
||||
data class ShowMessage(val message: String) : RoomDetailViewEvents()
|
||||
|
||||
data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents()
|
||||
|
||||
data class FileTooBigError(
|
||||
val filename: String,
|
||||
val fileSizeInBytes: Long,
|
||||
val homeServerLimitInBytes: Long
|
||||
) : RoomDetailViewEvents()
|
||||
|
||||
data class DownloadFileState(
|
||||
val mimeType: String,
|
||||
val file: File?,
|
||||
val throwable: Throwable?
|
||||
) : RoomDetailViewEvents()
|
||||
|
||||
abstract class SendMessageResult : RoomDetailViewEvents()
|
||||
|
||||
object MessageSent : SendMessageResult()
|
||||
class SlashCommandError(val command: Command) : SendMessageResult()
|
||||
class SlashCommandUnknown(val command: String) : SendMessageResult()
|
||||
data class SlashCommandHandled(@StringRes val messageRes: Int? = null) : SendMessageResult()
|
||||
object SlashCommandResultOk : SendMessageResult()
|
||||
class SlashCommandResultError(val throwable: Throwable) : SendMessageResult()
|
||||
// TODO Remove
|
||||
object SlashCommandNotImplemented : SendMessageResult()
|
||||
}
|
||||
|
@ -18,10 +18,6 @@ package im.vector.riotx.features.home.room.detail
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.Success
|
||||
@ -32,6 +28,7 @@ import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.MatrixPatterns
|
||||
import im.vector.matrix.android.api.NoOpMatrixCallback
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
@ -61,17 +58,14 @@ import im.vector.matrix.rx.rx
|
||||
import im.vector.matrix.rx.unwrap
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.extensions.postLiveEvent
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.core.resources.UserPreferencesProvider
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
import im.vector.matrix.android.api.NoOpMatrixCallback
|
||||
import im.vector.riotx.core.utils.subscribeLogError
|
||||
import im.vector.riotx.features.command.CommandParser
|
||||
import im.vector.riotx.features.command.ParsedCommand
|
||||
import im.vector.riotx.features.home.room.detail.composer.rainbow.RainbowGenerator
|
||||
import im.vector.riotx.features.crypto.verification.supportedVerificationMethods
|
||||
import im.vector.riotx.features.home.room.detail.composer.rainbow.RainbowGenerator
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
|
||||
import im.vector.riotx.features.home.room.typing.TypingHelper
|
||||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
@ -116,11 +110,6 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
var timeline = room.createTimeline(eventId, timelineSettings)
|
||||
private set
|
||||
|
||||
// Can be used for several actions, for a one shot result
|
||||
private val _requestLiveData = MutableLiveData<LiveEvent<Async<RoomDetailAction>>>()
|
||||
val requestLiveData: LiveData<LiveEvent<Async<RoomDetailAction>>>
|
||||
get() = _requestLiveData
|
||||
|
||||
// Slot to keep a pending action during permission request
|
||||
var pendingAction: RoomDetailAction? = null
|
||||
// Slot to keep a pending uri during permission request
|
||||
@ -320,27 +309,6 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Cleanup this and use ViewEvents
|
||||
private val _nonBlockingPopAlert = MutableLiveData<LiveEvent<Pair<Int, List<Any>>>>()
|
||||
val nonBlockingPopAlert: LiveData<LiveEvent<Pair<Int, List<Any>>>>
|
||||
get() = _nonBlockingPopAlert
|
||||
|
||||
private val _sendMessageResultLiveData = MutableLiveData<LiveEvent<SendMessageResult>>()
|
||||
val sendMessageResultLiveData: LiveData<LiveEvent<SendMessageResult>>
|
||||
get() = _sendMessageResultLiveData
|
||||
|
||||
private val _navigateToEvent = MutableLiveData<LiveEvent<String>>()
|
||||
val navigateToEvent: LiveData<LiveEvent<String>>
|
||||
get() = _navigateToEvent
|
||||
|
||||
private val _fileTooBigEvent = MutableLiveData<LiveEvent<FileTooBigError>>()
|
||||
val fileTooBigEvent: LiveData<LiveEvent<FileTooBigError>>
|
||||
get() = _fileTooBigEvent
|
||||
|
||||
private val _downloadedFileEvent = MutableLiveData<LiveEvent<DownloadFileState>>()
|
||||
val downloadedFileEvent: LiveData<LiveEvent<DownloadFileState>>
|
||||
get() = _downloadedFileEvent
|
||||
|
||||
fun isMenuItemVisible(@IdRes itemId: Int) = when (itemId) {
|
||||
R.id.clear_message_queue ->
|
||||
/* For now always disable on production, worker cancellation is not working properly */
|
||||
@ -360,17 +328,17 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
is ParsedCommand.ErrorNotACommand -> {
|
||||
// Send the text message to the room
|
||||
room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown)
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.MessageSent)
|
||||
_viewEvents.post(RoomDetailViewEvents.MessageSent)
|
||||
popDraft()
|
||||
}
|
||||
is ParsedCommand.ErrorSyntax -> {
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandError(slashCommandResult.command))
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandError(slashCommandResult.command))
|
||||
}
|
||||
is ParsedCommand.ErrorEmptySlashCommand -> {
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandUnknown("/"))
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandUnknown("/"))
|
||||
}
|
||||
is ParsedCommand.ErrorUnknownSlashCommand -> {
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandUnknown(slashCommandResult.slashCommand))
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandUnknown(slashCommandResult.slashCommand))
|
||||
}
|
||||
is ParsedCommand.Invite -> {
|
||||
handleInviteSlashCommand(slashCommandResult)
|
||||
@ -378,55 +346,55 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
}
|
||||
is ParsedCommand.SetUserPowerLevel -> {
|
||||
// TODO
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented)
|
||||
}
|
||||
is ParsedCommand.ClearScalarToken -> {
|
||||
// TODO
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented)
|
||||
}
|
||||
is ParsedCommand.SetMarkdown -> {
|
||||
vectorPreferences.setMarkdownEnabled(slashCommandResult.enable)
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled(
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled(
|
||||
if (slashCommandResult.enable) R.string.markdown_has_been_enabled else R.string.markdown_has_been_disabled))
|
||||
popDraft()
|
||||
}
|
||||
is ParsedCommand.UnbanUser -> {
|
||||
// TODO
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented)
|
||||
}
|
||||
is ParsedCommand.BanUser -> {
|
||||
// TODO
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented)
|
||||
}
|
||||
is ParsedCommand.KickUser -> {
|
||||
// TODO
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented)
|
||||
}
|
||||
is ParsedCommand.JoinRoom -> {
|
||||
// TODO
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented)
|
||||
}
|
||||
is ParsedCommand.PartRoom -> {
|
||||
// TODO
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented)
|
||||
}
|
||||
is ParsedCommand.SendEmote -> {
|
||||
room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE)
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
is ParsedCommand.SendRainbow -> {
|
||||
slashCommandResult.message.toString().let {
|
||||
room.sendFormattedTextMessage(it, rainbowGenerator.generate(it))
|
||||
}
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
is ParsedCommand.SendRainbowEmote -> {
|
||||
slashCommandResult.message.toString().let {
|
||||
room.sendFormattedTextMessage(it, rainbowGenerator.generate(it), MessageType.MSGTYPE_EMOTE)
|
||||
}
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
is ParsedCommand.SendSpoiler -> {
|
||||
@ -434,7 +402,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
"[${stringProvider.getString(R.string.spoiler)}](${slashCommandResult.message})",
|
||||
"<span data-mx-spoiler>${slashCommandResult.message}</span>"
|
||||
)
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
is ParsedCommand.SendShrug -> {
|
||||
@ -446,12 +414,12 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
}
|
||||
}
|
||||
room.sendTextMessage(sequence)
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
is ParsedCommand.VerifyUser -> {
|
||||
session.getVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, slashCommandResult.userId, room.roomId)
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
is ParsedCommand.ChangeTopic -> {
|
||||
@ -460,7 +428,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
}
|
||||
is ParsedCommand.ChangeDisplayName -> {
|
||||
// TODO
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented)
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
@ -487,7 +455,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
Timber.w("Same message content, do not send edition")
|
||||
}
|
||||
}
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.MessageSent)
|
||||
_viewEvents.post(RoomDetailViewEvents.MessageSent)
|
||||
popDraft()
|
||||
}
|
||||
is SendMode.QUOTE -> {
|
||||
@ -510,13 +478,13 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
} else {
|
||||
room.sendFormattedTextMessage(finalText, htmlText)
|
||||
}
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.MessageSent)
|
||||
_viewEvents.post(RoomDetailViewEvents.MessageSent)
|
||||
popDraft()
|
||||
}
|
||||
is SendMode.REPLY -> {
|
||||
state.sendMode.timelineEvent.let {
|
||||
room.replyToMessage(it, action.text.toString(), action.autoMarkdown)
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.MessageSent)
|
||||
_viewEvents.post(RoomDetailViewEvents.MessageSent)
|
||||
popDraft()
|
||||
}
|
||||
}
|
||||
@ -549,29 +517,29 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
}
|
||||
|
||||
private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||
|
||||
room.updateTopic(changeTopic.topic, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultOk)
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandResultOk)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultError(failure))
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) {
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||
|
||||
room.invite(invite.userId, invite.reason, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultOk)
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandResultOk)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultError(failure))
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure))
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -608,8 +576,11 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
} else {
|
||||
when (val tooBigFile = attachments.find { it.size > maxUploadFileSize }) {
|
||||
null -> room.sendMedias(attachments)
|
||||
else -> _fileTooBigEvent.postValue(LiveEvent(FileTooBigError(tooBigFile.name
|
||||
?: tooBigFile.path, tooBigFile.size, maxUploadFileSize)))
|
||||
else -> _viewEvents.post(RoomDetailViewEvents.FileTooBigError(
|
||||
tooBigFile.name ?: tooBigFile.path,
|
||||
tooBigFile.size,
|
||||
maxUploadFileSize
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -721,7 +692,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
action.messageFileContent.encryptedFileInfo?.toElementToDecrypt(),
|
||||
object : MatrixCallback<File> {
|
||||
override fun onSuccess(data: File) {
|
||||
_downloadedFileEvent.postLiveEvent(DownloadFileState(
|
||||
_viewEvents.post(RoomDetailViewEvents.DownloadFileState(
|
||||
action.messageFileContent.getMimeType(),
|
||||
data,
|
||||
null
|
||||
@ -729,7 +700,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_downloadedFileEvent.postLiveEvent(DownloadFileState(
|
||||
_viewEvents.post(RoomDetailViewEvents.DownloadFileState(
|
||||
action.messageFileContent.getMimeType(),
|
||||
null,
|
||||
failure
|
||||
@ -750,7 +721,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
if (action.highlight) {
|
||||
setState { copy(highlightedEventId = correctedEventId) }
|
||||
}
|
||||
_navigateToEvent.postLiveEvent(correctedEventId)
|
||||
_viewEvents.post(RoomDetailViewEvents.NavigateToEvent(correctedEventId))
|
||||
}
|
||||
|
||||
private fun handleResendEvent(action: RoomDetailAction.ResendMessage) {
|
||||
@ -821,11 +792,11 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
private fun handleReportContent(action: RoomDetailAction.ReportContent) {
|
||||
room.reportContent(action.eventId, -100, action.reason, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
_requestLiveData.postValue(LiveEvent(Success(action)))
|
||||
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_requestLiveData.postValue(LiveEvent(Fail(failure)))
|
||||
_viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure))
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -837,11 +808,11 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
|
||||
session.ignoreUserIds(listOf(action.userId), object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
_requestLiveData.postValue(LiveEvent(Success(action)))
|
||||
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_requestLiveData.postValue(LiveEvent(Fail(failure)))
|
||||
_viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure))
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -853,7 +824,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
action.otherUserId,
|
||||
room.roomId,
|
||||
action.transactionId)) {
|
||||
_requestLiveData.postValue(LiveEvent(Success(action)))
|
||||
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
|
||||
} else {
|
||||
// TODO
|
||||
}
|
||||
@ -869,7 +840,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
|
||||
private fun handleRequestVerification(action: RoomDetailAction.RequestVerification) {
|
||||
if (action.userId == session.myUserId) return
|
||||
_requestLiveData.postValue(LiveEvent(Success(action)))
|
||||
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
|
||||
}
|
||||
|
||||
private fun handleResumeRequestVerification(action: RoomDetailAction.ResumeVerification) {
|
||||
@ -877,9 +848,9 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
session.getVerificationService().getExistingVerificationRequestInRoom(room.roomId, action.transactionId)?.let {
|
||||
if (it.handledByOtherSession) return
|
||||
if (!it.isFinished) {
|
||||
_requestLiveData.postValue(LiveEvent(Success(action.copy(
|
||||
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action.copy(
|
||||
otherUserId = it.otherUserId
|
||||
))))
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,31 +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.riotx.features.home.room.detail
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import im.vector.riotx.features.command.Command
|
||||
|
||||
sealed class SendMessageResult {
|
||||
object MessageSent : SendMessageResult()
|
||||
class SlashCommandError(val command: Command) : SendMessageResult()
|
||||
class SlashCommandUnknown(val command: String) : SendMessageResult()
|
||||
data class SlashCommandHandled(@StringRes val messageRes: Int? = null) : SendMessageResult()
|
||||
object SlashCommandResultOk : SendMessageResult()
|
||||
class SlashCommandResultError(val throwable: Throwable) : SendMessageResult()
|
||||
// TODO Remove
|
||||
object SlashCommandNotImplemented : SendMessageResult()
|
||||
}
|
@ -28,7 +28,7 @@ import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.cleanup
|
||||
import im.vector.riotx.core.extensions.configureWith
|
||||
import im.vector.riotx.core.extensions.observeEvent
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import kotlinx.android.synthetic.main.fragment_public_rooms.*
|
||||
@ -75,13 +75,20 @@ class PublicRoomsFragment @Inject constructor(
|
||||
sharedActionViewModel.post(RoomDirectorySharedAction.CreateRoom)
|
||||
}
|
||||
|
||||
// TODO remove this, replace by ViewEvents
|
||||
viewModel.joinRoomErrorLiveData.observeEvent(this) { throwable ->
|
||||
Snackbar.make(publicRoomsCoordinator, errorFormatter.toHumanReadable(throwable), Snackbar.LENGTH_SHORT)
|
||||
.show()
|
||||
viewModel.observeViewEvents {
|
||||
handleViewEvents(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleViewEvents(viewEvents: RoomDirectoryViewEvents) {
|
||||
when (viewEvents) {
|
||||
is RoomDirectoryViewEvents.Failure -> {
|
||||
Snackbar.make(publicRoomsCoordinator, errorFormatter.toHumanReadable(viewEvents.throwable), Snackbar.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
publicRoomsController.callback = null
|
||||
publicRoomsList.cleanup()
|
||||
|
@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* Copyright (c) 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
|
||||
* 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,
|
||||
@ -14,10 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.home.room.detail
|
||||
package im.vector.riotx.features.roomdirectory
|
||||
|
||||
data class FileTooBigError(
|
||||
val filename: String,
|
||||
val fileSizeInBytes: Long,
|
||||
val homeServerLimitInBytes: Long
|
||||
)
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
|
||||
/**
|
||||
* Transient events for room directory screen
|
||||
*/
|
||||
sealed class RoomDirectoryViewEvents : VectorViewEvents {
|
||||
data class Failure(val throwable: Throwable) : RoomDirectoryViewEvents()
|
||||
}
|
@ -16,9 +16,13 @@
|
||||
|
||||
package im.vector.riotx.features.roomdirectory
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.airbnb.mvrx.*
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.airbnb.mvrx.appendAt
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
@ -32,17 +36,14 @@ import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryD
|
||||
import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotx.core.extensions.postLiveEvent
|
||||
import im.vector.riotx.core.platform.EmptyViewEvents
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
import timber.log.Timber
|
||||
|
||||
private const val PUBLIC_ROOMS_LIMIT = 20
|
||||
|
||||
class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: PublicRoomsViewState,
|
||||
private val session: Session)
|
||||
: VectorViewModel<PublicRoomsViewState, RoomDirectoryAction, EmptyViewEvents>(initialState) {
|
||||
: VectorViewModel<PublicRoomsViewState, RoomDirectoryAction, RoomDirectoryViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
@ -58,10 +59,6 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
||||
}
|
||||
}
|
||||
|
||||
private val _joinRoomErrorLiveData = MutableLiveData<LiveEvent<Throwable>>()
|
||||
val joinRoomErrorLiveData: LiveData<LiveEvent<Throwable>>
|
||||
get() = _joinRoomErrorLiveData
|
||||
|
||||
private var since: String? = null
|
||||
|
||||
private var currentTask: Cancelable? = null
|
||||
@ -109,9 +106,9 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
||||
override fun handle(action: RoomDirectoryAction) {
|
||||
when (action) {
|
||||
is RoomDirectoryAction.SetRoomDirectoryData -> setRoomDirectoryData(action)
|
||||
is RoomDirectoryAction.FilterWith -> filterWith(action)
|
||||
RoomDirectoryAction.LoadMore -> loadMore()
|
||||
is RoomDirectoryAction.JoinRoom -> joinRoom(action)
|
||||
is RoomDirectoryAction.FilterWith -> filterWith(action)
|
||||
RoomDirectoryAction.LoadMore -> loadMore()
|
||||
is RoomDirectoryAction.JoinRoom -> joinRoom(action)
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,7 +224,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
// Notify the user
|
||||
_joinRoomErrorLiveData.postLiveEvent(failure)
|
||||
_viewEvents.post(RoomDirectoryViewEvents.Failure(failure))
|
||||
|
||||
setState {
|
||||
copy(
|
||||
|
@ -20,8 +20,7 @@ package im.vector.riotx.features.roommemberprofile
|
||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class RoomMemberProfileAction : VectorViewModelAction {
|
||||
|
||||
object RetryFetchingInfo: RoomMemberProfileAction()
|
||||
object IgnoreUser: RoomMemberProfileAction()
|
||||
data class VerifyUser(val userId: String? = null, val roomId: String? = null, val canCrossSign: Boolean? = true): RoomMemberProfileAction()
|
||||
object RetryFetchingInfo : RoomMemberProfileAction()
|
||||
object IgnoreUser : RoomMemberProfileAction()
|
||||
object VerifyUser : RoomMemberProfileAction()
|
||||
}
|
||||
|
@ -35,7 +35,6 @@ import im.vector.riotx.core.animations.MatrixItemAppBarStateChangeListener
|
||||
import im.vector.riotx.core.extensions.cleanup
|
||||
import im.vector.riotx.core.extensions.configureWith
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.extensions.observeEvent
|
||||
import im.vector.riotx.core.extensions.setTextOrHide
|
||||
import im.vector.riotx.core.platform.StateView
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
@ -94,33 +93,27 @@ class RoomMemberProfileFragment @Inject constructor(
|
||||
is RoomMemberProfileViewEvents.Loading -> showLoading(it.message)
|
||||
is RoomMemberProfileViewEvents.Failure -> showFailure(it.throwable)
|
||||
is RoomMemberProfileViewEvents.OnIgnoreActionSuccess -> Unit
|
||||
is RoomMemberProfileViewEvents.StartVerification -> handleStartVerification(it)
|
||||
}.exhaustive
|
||||
}
|
||||
viewModel.actionResultLiveData.observeEvent(this) { async ->
|
||||
when (async) {
|
||||
is Success -> {
|
||||
when (val action = async.invoke()) {
|
||||
is RoomMemberProfileAction.VerifyUser -> {
|
||||
if (action.canCrossSign == true) {
|
||||
VerificationBottomSheet
|
||||
.withArgs(roomId = null, otherUserId = action.userId!!)
|
||||
.show(parentFragmentManager, "VERIF")
|
||||
} else {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.dialog_title_warning)
|
||||
.setMessage(R.string.verify_cannot_cross_sign)
|
||||
.setPositiveButton(R.string.verification_profile_verify) { _, _ ->
|
||||
VerificationBottomSheet
|
||||
.withArgs(roomId = null, otherUserId = action.userId!!)
|
||||
.show(parentFragmentManager, "VERIF")
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleStartVerification(startVerification: RoomMemberProfileViewEvents.StartVerification) {
|
||||
if (startVerification.canCrossSign) {
|
||||
VerificationBottomSheet
|
||||
.withArgs(roomId = null, otherUserId = startVerification.userId)
|
||||
.show(parentFragmentManager, "VERIF")
|
||||
} else {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.dialog_title_warning)
|
||||
.setMessage(R.string.verify_cannot_cross_sign)
|
||||
.setPositiveButton(R.string.verification_profile_verify) { _, _ ->
|
||||
VerificationBottomSheet
|
||||
.withArgs(roomId = null, otherUserId = startVerification.userId)
|
||||
.show(parentFragmentManager, "VERIF")
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,7 +190,7 @@ class RoomMemberProfileFragment @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onTapVerify() {
|
||||
viewModel.handle(RoomMemberProfileAction.VerifyUser())
|
||||
viewModel.handle(RoomMemberProfileAction.VerifyUser)
|
||||
}
|
||||
|
||||
override fun onShowDeviceList() = withState(viewModel) {
|
||||
|
@ -26,4 +26,9 @@ sealed class RoomMemberProfileViewEvents : VectorViewEvents {
|
||||
data class Failure(val throwable: Throwable) : RoomMemberProfileViewEvents()
|
||||
|
||||
object OnIgnoreActionSuccess : RoomMemberProfileViewEvents()
|
||||
|
||||
data class StartVerification(
|
||||
val userId: String,
|
||||
val canCrossSign: Boolean
|
||||
) : RoomMemberProfileViewEvents()
|
||||
}
|
||||
|
@ -17,10 +17,7 @@
|
||||
|
||||
package im.vector.riotx.features.roommemberprofile
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.Success
|
||||
@ -49,7 +46,6 @@ import im.vector.matrix.rx.unwrap
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.functions.BiFunction
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -75,10 +71,6 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
|
||||
}
|
||||
}
|
||||
|
||||
private val _actionResultLiveData = MutableLiveData<LiveEvent<Async<RoomMemberProfileAction>>>()
|
||||
val actionResultLiveData: LiveData<LiveEvent<Async<RoomMemberProfileAction>>>
|
||||
get() = _actionResultLiveData
|
||||
|
||||
private val room = if (initialState.roomId != null) {
|
||||
session.getRoom(initialState.roomId)
|
||||
} else {
|
||||
@ -145,23 +137,19 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
|
||||
when (action) {
|
||||
is RoomMemberProfileAction.RetryFetchingInfo -> fetchProfileInfo()
|
||||
is RoomMemberProfileAction.IgnoreUser -> handleIgnoreAction()
|
||||
is RoomMemberProfileAction.VerifyUser -> prepareVerification(action)
|
||||
is RoomMemberProfileAction.VerifyUser -> prepareVerification()
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareVerification(action: RoomMemberProfileAction.VerifyUser) = withState { state ->
|
||||
private fun prepareVerification() = withState { state ->
|
||||
// Sanity
|
||||
if (state.isRoomEncrypted) {
|
||||
if (!state.isMine && state.userMXCrossSigningInfo?.isTrusted() == false) {
|
||||
// ok, let's find or create the DM room
|
||||
_actionResultLiveData.postValue(
|
||||
LiveEvent(Success(
|
||||
action.copy(
|
||||
userId = state.userId,
|
||||
canCrossSign = session.getCrossSigningService().canCrossSign()
|
||||
)
|
||||
))
|
||||
)
|
||||
_viewEvents.post(RoomMemberProfileViewEvents.StartVerification(
|
||||
userId = state.userId,
|
||||
canCrossSign = session.getCrossSigningService().canCrossSign()
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 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.devices
|
||||
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class DeviceListAction : VectorViewModelAction {
|
||||
data class SelectDevice(val device: CryptoDeviceInfo) : DeviceListAction()
|
||||
object DeselectDevice : DeviceListAction()
|
||||
|
||||
data class ManuallyVerify(val deviceId: String) : DeviceListAction()
|
||||
}
|
@ -21,15 +21,13 @@ import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.airbnb.mvrx.MvRx
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
import im.vector.riotx.core.extensions.commitTransaction
|
||||
import im.vector.riotx.core.extensions.observeEvent
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
|
||||
import im.vector.riotx.features.crypto.verification.VerificationAction
|
||||
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
|
||||
import javax.inject.Inject
|
||||
import kotlin.reflect.KClass
|
||||
@ -48,20 +46,16 @@ class DeviceListBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
viewModel.requestLiveData.observeEvent(this) { async ->
|
||||
when (async) {
|
||||
is Success -> {
|
||||
when (val action = async.invoke()) {
|
||||
is VerificationAction.StartSASVerification -> {
|
||||
VerificationBottomSheet.withArgs(
|
||||
roomId = null,
|
||||
otherUserId = action.otherUserId,
|
||||
transactionId = action.pendingRequestTransactionId
|
||||
).show(requireActivity().supportFragmentManager, "REQPOP")
|
||||
}
|
||||
}
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is DeviceListBottomSheetViewEvents.Verify -> {
|
||||
VerificationBottomSheet.withArgs(
|
||||
roomId = null,
|
||||
otherUserId = it.userId,
|
||||
transactionId = it.txID
|
||||
).show(requireActivity().supportFragmentManager, "REQPOP")
|
||||
}
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,7 +63,7 @@ class DeviceListBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
withState(viewModel) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
if (it.selectedDevice != null) {
|
||||
viewModel.selectDevice(null)
|
||||
viewModel.handle(DeviceListAction.DeselectDevice)
|
||||
return@withState true
|
||||
} else {
|
||||
return@withState false
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 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.devices
|
||||
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
|
||||
/**
|
||||
* Transient events for device list screen
|
||||
*/
|
||||
sealed class DeviceListBottomSheetViewEvents : VectorViewEvents {
|
||||
data class Verify(val userId: String, val txID: String) : DeviceListBottomSheetViewEvents()
|
||||
}
|
@ -16,14 +16,11 @@
|
||||
*/
|
||||
package im.vector.riotx.features.roommemberprofile.devices
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
@ -35,12 +32,8 @@ import im.vector.matrix.android.api.util.toMatrixItem
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotx.core.di.HasScreenInjector
|
||||
import im.vector.riotx.core.platform.EmptyAction
|
||||
import im.vector.riotx.core.platform.EmptyViewEvents
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
import im.vector.riotx.features.crypto.verification.VerificationAction
|
||||
|
||||
data class DeviceListViewState(
|
||||
val userItem: MatrixItem? = null,
|
||||
@ -52,14 +45,8 @@ data class DeviceListViewState(
|
||||
|
||||
class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted private val initialState: DeviceListViewState,
|
||||
@Assisted private val userId: String,
|
||||
private val stringProvider: StringProvider,
|
||||
private val session: Session)
|
||||
: VectorViewModel<DeviceListViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
// Can be used for several actions, for a one shot result
|
||||
private val _requestLiveData = MutableLiveData<LiveEvent<Async<VerificationAction>>>()
|
||||
val requestLiveData: LiveData<LiveEvent<Async<VerificationAction>>>
|
||||
get() = _requestLiveData
|
||||
: VectorViewModel<DeviceListViewState, DeviceListAction, DeviceListBottomSheetViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
@ -67,7 +54,6 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva
|
||||
}
|
||||
|
||||
init {
|
||||
|
||||
session.rx().liveUserCryptoDevices(userId)
|
||||
.execute {
|
||||
copy(cryptoDevices = it).also {
|
||||
@ -81,6 +67,14 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva
|
||||
}
|
||||
}
|
||||
|
||||
override fun handle(action: DeviceListAction) {
|
||||
when (action) {
|
||||
is DeviceListAction.SelectDevice -> selectDevice(action)
|
||||
is DeviceListAction.DeselectDevice -> deselectDevice()
|
||||
is DeviceListAction.ManuallyVerify -> manuallyVerify(action)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun refreshSelectedId() = withState { state ->
|
||||
if (state.selectedDevice != null) {
|
||||
state.cryptoDevices.invoke()?.firstOrNull { state.selectedDevice.deviceId == it.deviceId }?.let {
|
||||
@ -93,19 +87,23 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva
|
||||
}
|
||||
}
|
||||
|
||||
fun selectDevice(device: CryptoDeviceInfo?) {
|
||||
private fun selectDevice(action: DeviceListAction.SelectDevice) {
|
||||
setState {
|
||||
copy(selectedDevice = device)
|
||||
copy(selectedDevice = action.device)
|
||||
}
|
||||
}
|
||||
|
||||
fun manuallyVerify(device: CryptoDeviceInfo) {
|
||||
session.getVerificationService().beginKeyVerification(VerificationMethod.SAS, userId, device.deviceId, null)?.let { txID ->
|
||||
_requestLiveData.postValue(LiveEvent(Success(VerificationAction.StartSASVerification(userId, txID))))
|
||||
private fun deselectDevice() {
|
||||
setState {
|
||||
copy(selectedDevice = null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handle(action: EmptyAction) {}
|
||||
private fun manuallyVerify(action: DeviceListAction.ManuallyVerify) {
|
||||
session.getVerificationService().beginKeyVerification(VerificationMethod.SAS, userId, action.deviceId, null)?.let { txID ->
|
||||
_viewEvents.post(DeviceListBottomSheetViewEvents.Verify(userId, txID))
|
||||
}
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<DeviceListBottomSheetViewModel, DeviceListViewState> {
|
||||
@JvmStatic
|
||||
|
@ -62,6 +62,6 @@ class DeviceListFragment @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onDeviceSelected(device: CryptoDeviceInfo) {
|
||||
viewModel.selectDevice(device)
|
||||
viewModel.handle(DeviceListAction.SelectDevice(device))
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +62,6 @@ class DeviceTrustInfoActionFragment @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onVerifyManually(device: CryptoDeviceInfo) {
|
||||
viewModel.manuallyVerify(device)
|
||||
viewModel.handle(DeviceListAction.ManuallyVerify(device.deviceId))
|
||||
}
|
||||
}
|
||||
|
@ -18,16 +18,13 @@ package im.vector.riotx.features.settings.crosssigning
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.dialogs.PromptPasswordDialog
|
||||
import im.vector.riotx.core.extensions.cleanup
|
||||
import im.vector.riotx.core.extensions.configureWith
|
||||
import im.vector.riotx.core.extensions.observeEvent
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
||||
@ -44,23 +41,20 @@ class CrossSigningSettingsFragment @Inject constructor(
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
viewModel.requestLiveData.observeEvent(this) {
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is Fail -> {
|
||||
is CrossSigningSettingsViewEvents.Failure -> {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.dialog_title_error)
|
||||
.setMessage(it.error.message)
|
||||
.setMessage(errorFormatter.toHumanReadable(it.throwable))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
Unit
|
||||
}
|
||||
is Success -> {
|
||||
when (val action = it.invoke()) {
|
||||
is CrossSigningAction.RequestPasswordAuth -> {
|
||||
requestPassword(action.sessionId)
|
||||
}
|
||||
}
|
||||
is CrossSigningSettingsViewEvents.RequestPassword -> {
|
||||
requestPassword()
|
||||
}
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,18 +83,14 @@ class CrossSigningSettingsFragment @Inject constructor(
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun requestPassword(sessionId: String) {
|
||||
private fun requestPassword() {
|
||||
PromptPasswordDialog().show(requireActivity()) { password ->
|
||||
// TODO sessionId should never get out the ViewModel
|
||||
viewModel.handle(CrossSigningAction.InitializeCrossSigning(UserPasswordAuth(
|
||||
session = sessionId,
|
||||
password = password
|
||||
)))
|
||||
viewModel.handle(CrossSigningAction.PasswordEntered(password))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInitializeCrossSigningKeys() {
|
||||
viewModel.handle(CrossSigningAction.InitializeCrossSigning())
|
||||
viewModel.handle(CrossSigningAction.InitializeCrossSigning)
|
||||
}
|
||||
|
||||
override fun onResetCrossSigningKeys() {
|
||||
@ -108,7 +98,7 @@ class CrossSigningSettingsFragment @Inject constructor(
|
||||
.setTitle(R.string.dialog_title_confirmation)
|
||||
.setMessage(R.string.are_you_sure)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
viewModel.handle(CrossSigningAction.InitializeCrossSigning())
|
||||
viewModel.handle(CrossSigningAction.InitializeCrossSigning)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 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.settings.crosssigning
|
||||
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
|
||||
/**
|
||||
* Transient events for cross signing settings screen
|
||||
*/
|
||||
sealed class CrossSigningSettingsViewEvents : VectorViewEvents {
|
||||
data class Failure(val throwable: Throwable) : CrossSigningSettingsViewEvents()
|
||||
|
||||
object RequestPassword : CrossSigningSettingsViewEvents()
|
||||
}
|
@ -15,14 +15,9 @@
|
||||
*/
|
||||
package im.vector.riotx.features.settings.crosssigning
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
@ -36,11 +31,9 @@ import im.vector.matrix.android.internal.crypto.crosssigning.isVerified
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotx.core.platform.EmptyViewEvents
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
|
||||
data class CrossSigningSettingsViewState(
|
||||
val crossSigningInfo: MXCrossSigningInfo? = null,
|
||||
@ -51,19 +44,13 @@ data class CrossSigningSettingsViewState(
|
||||
) : MvRxState
|
||||
|
||||
sealed class CrossSigningAction : VectorViewModelAction {
|
||||
data class InitializeCrossSigning(val auth: UserPasswordAuth? = null) : CrossSigningAction()
|
||||
data class RequestPasswordAuth(val sessionId: String) : CrossSigningAction()
|
||||
object InitializeCrossSigning : CrossSigningAction()
|
||||
data class PasswordEntered(val password: String) : CrossSigningAction()
|
||||
}
|
||||
|
||||
class CrossSigningSettingsViewModel @AssistedInject constructor(@Assisted private val initialState: CrossSigningSettingsViewState,
|
||||
private val stringProvider: StringProvider,
|
||||
private val session: Session)
|
||||
: VectorViewModel<CrossSigningSettingsViewState, CrossSigningAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
// Can be used for several actions, for a one shot result
|
||||
private val _requestLiveData = MutableLiveData<LiveEvent<Async<CrossSigningAction>>>()
|
||||
val requestLiveData: LiveData<LiveEvent<Async<CrossSigningAction>>>
|
||||
get() = _requestLiveData
|
||||
: VectorViewModel<CrossSigningSettingsViewState, CrossSigningAction, CrossSigningSettingsViewEvents>(initialState) {
|
||||
|
||||
init {
|
||||
session.rx().liveCrossSigningInfo(session.myUserId)
|
||||
@ -81,6 +68,9 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(@Assisted privat
|
||||
}
|
||||
}
|
||||
|
||||
// Storage when password is required
|
||||
private var _pendingSession: String? = null
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(initialState: CrossSigningSettingsViewState): CrossSigningSettingsViewModel
|
||||
@ -89,26 +79,37 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(@Assisted privat
|
||||
override fun handle(action: CrossSigningAction) {
|
||||
when (action) {
|
||||
is CrossSigningAction.InitializeCrossSigning -> {
|
||||
initializeCrossSigning(action.auth?.copy(user = session.myUserId))
|
||||
initializeCrossSigning(null)
|
||||
}
|
||||
}
|
||||
is CrossSigningAction.PasswordEntered -> {
|
||||
initializeCrossSigning(UserPasswordAuth(
|
||||
session = _pendingSession,
|
||||
user = session.myUserId,
|
||||
password = action.password
|
||||
))
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun initializeCrossSigning(auth: UserPasswordAuth?) {
|
||||
_pendingSession = null
|
||||
|
||||
setState {
|
||||
copy(isUploadingKeys = true)
|
||||
}
|
||||
session.getCrossSigningService().initializeCrossSigning(auth, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
_pendingSession = null
|
||||
|
||||
setState {
|
||||
copy(isUploadingKeys = false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
if (failure is Failure.OtherServerError
|
||||
&& failure.httpCode == 401
|
||||
) {
|
||||
_pendingSession = null
|
||||
|
||||
if (failure is Failure.OtherServerError && failure.httpCode == 401) {
|
||||
try {
|
||||
MoshiProvider.providesMoshi()
|
||||
.adapter(RegistrationFlowResponse::class.java)
|
||||
@ -118,23 +119,23 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(@Assisted privat
|
||||
}?.let { flowResponse ->
|
||||
// Retry with authentication
|
||||
if (flowResponse.flows?.any { it.stages?.contains(LoginFlowTypes.PASSWORD) == true } == true) {
|
||||
_requestLiveData.postValue(LiveEvent(Success(CrossSigningAction.RequestPasswordAuth(flowResponse.session ?: ""))))
|
||||
_pendingSession = flowResponse.session ?: ""
|
||||
_viewEvents.post(CrossSigningSettingsViewEvents.RequestPassword)
|
||||
return
|
||||
} else {
|
||||
_requestLiveData.postValue(LiveEvent(Fail(Throwable("You cannot do that from mobile"))))
|
||||
// can't do this from here
|
||||
_viewEvents.post(CrossSigningSettingsViewEvents.Failure(Throwable("You cannot do that from mobile")))
|
||||
|
||||
setState {
|
||||
copy(isUploadingKeys = false)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
when (failure) {
|
||||
is Failure.ServerError -> {
|
||||
_requestLiveData.postValue(LiveEvent(Fail(Throwable(failure.error.message))))
|
||||
}
|
||||
else -> {
|
||||
_requestLiveData.postValue(LiveEvent(Fail(failure)))
|
||||
}
|
||||
}
|
||||
|
||||
_viewEvents.post(CrossSigningSettingsViewEvents.Failure(failure))
|
||||
|
||||
setState {
|
||||
copy(isUploadingKeys = false)
|
||||
}
|
||||
|
@ -16,14 +16,14 @@
|
||||
|
||||
package im.vector.riotx.features.settings.devices
|
||||
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class DevicesAction : VectorViewModelAction {
|
||||
object Retry : DevicesAction()
|
||||
data class Delete(val deviceId: String) : DevicesAction()
|
||||
data class Password(val password: String) : DevicesAction()
|
||||
data class Rename(val deviceInfo: DeviceInfo, val newName: String) : DevicesAction()
|
||||
data class PromptRename(val deviceId: String, val deviceInfo: DeviceInfo? = null) : DevicesAction()
|
||||
data class VerifyMyDevice(val deviceId: String, val userId: String? = null, val transactionId: String? = null) : DevicesAction()
|
||||
data class Rename(val deviceId: String, val newName: String) : DevicesAction()
|
||||
|
||||
data class PromptRename(val deviceId: String) : DevicesAction()
|
||||
data class VerifyMyDevice(val deviceId: String) : DevicesAction()
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
package im.vector.riotx.features.settings.devices
|
||||
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
|
||||
/**
|
||||
@ -25,4 +26,13 @@ import im.vector.riotx.core.platform.VectorViewEvents
|
||||
sealed class DevicesViewEvents : VectorViewEvents {
|
||||
data class Loading(val message: CharSequence? = null) : DevicesViewEvents()
|
||||
data class Failure(val throwable: Throwable) : DevicesViewEvents()
|
||||
|
||||
object RequestPassword : DevicesViewEvents()
|
||||
|
||||
data class PromptRenameDevice(val deviceInfo: DeviceInfo) : DevicesViewEvents()
|
||||
|
||||
data class ShowVerifyDevice(
|
||||
val userId: String,
|
||||
val transactionId: String?
|
||||
) : DevicesViewEvents()
|
||||
}
|
||||
|
@ -16,8 +16,6 @@
|
||||
|
||||
package im.vector.riotx.features.settings.devices
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
@ -41,9 +39,7 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotx.core.extensions.postLiveEvent
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
import im.vector.riotx.features.crypto.verification.supportedVerificationMethods
|
||||
|
||||
data class DevicesViewState(
|
||||
@ -76,15 +72,6 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
|
||||
private var _currentDeviceId: String? = null
|
||||
private var _currentSession: String? = null
|
||||
|
||||
private val _requestPasswordLiveData = MutableLiveData<LiveEvent<Unit>>()
|
||||
val requestPasswordLiveData: LiveData<LiveEvent<Unit>>
|
||||
get() = _requestPasswordLiveData
|
||||
|
||||
// Used to communicate back from model to fragment
|
||||
private val _requestLiveData = MutableLiveData<LiveEvent<Async<DevicesAction>>>()
|
||||
val fragmentActionLiveData: LiveData<LiveEvent<Async<DevicesAction>>>
|
||||
get() = _requestLiveData
|
||||
|
||||
init {
|
||||
refreshDevicesList()
|
||||
session.getVerificationService().addListener(this)
|
||||
@ -187,25 +174,21 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
|
||||
|
||||
private fun handleVerify(action: DevicesAction.VerifyMyDevice) {
|
||||
val txID = session.getVerificationService().requestKeyVerification(supportedVerificationMethods, session.myUserId, listOf(action.deviceId))
|
||||
_requestLiveData.postValue(LiveEvent(Success(
|
||||
action.copy(
|
||||
userId = session.myUserId,
|
||||
transactionId = txID.transactionId
|
||||
)
|
||||
)))
|
||||
_viewEvents.post(DevicesViewEvents.ShowVerifyDevice(
|
||||
session.myUserId,
|
||||
txID.transactionId
|
||||
))
|
||||
}
|
||||
|
||||
private fun handlePromptRename(action: DevicesAction.PromptRename) = withState { state ->
|
||||
val info = state.devices.invoke()?.firstOrNull { it.deviceId == action.deviceId }
|
||||
if (info == null) {
|
||||
_requestLiveData.postValue(LiveEvent(Uninitialized))
|
||||
} else {
|
||||
_requestLiveData.postValue(LiveEvent(Success(action.copy(deviceInfo = info))))
|
||||
if (info != null) {
|
||||
_viewEvents.post(DevicesViewEvents.PromptRenameDevice(info))
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRename(action: DevicesAction.Rename) {
|
||||
session.setDeviceName(action.deviceInfo.deviceId!!, action.newName, object : MatrixCallback<Unit> {
|
||||
session.setDeviceName(action.deviceId, action.newName, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
setState {
|
||||
copy(
|
||||
@ -261,7 +244,7 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
|
||||
)
|
||||
}
|
||||
|
||||
_requestPasswordLiveData.postLiveEvent(Unit)
|
||||
_viewEvents.post(DevicesViewEvents.RequestPassword)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,6 @@ import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
@ -32,7 +31,6 @@ import im.vector.riotx.core.dialogs.PromptPasswordDialog
|
||||
import im.vector.riotx.core.extensions.cleanup
|
||||
import im.vector.riotx.core.extensions.configureWith
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.extensions.observeEvent
|
||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
|
||||
@ -64,35 +62,18 @@ class VectorSettingsDevicesFragment @Inject constructor(
|
||||
recyclerView.configureWith(devicesController, showDivider = true)
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is DevicesViewEvents.Loading -> showLoading(it.message)
|
||||
is DevicesViewEvents.Failure -> showFailure(it.throwable)
|
||||
}.exhaustive
|
||||
}
|
||||
viewModel.requestPasswordLiveData.observeEvent(this) {
|
||||
maybeShowDeleteDeviceWithPasswordDialog()
|
||||
}
|
||||
|
||||
viewModel.fragmentActionLiveData.observeEvent(this) { async ->
|
||||
when (async) {
|
||||
is Success -> {
|
||||
when (val action = async.invoke()) {
|
||||
is DevicesAction.PromptRename -> {
|
||||
action.deviceInfo?.let { deviceInfo ->
|
||||
displayDeviceRenameDialog(deviceInfo)
|
||||
}
|
||||
}
|
||||
is DevicesAction.VerifyMyDevice -> {
|
||||
if (context is VectorBaseActivity) {
|
||||
VerificationBottomSheet.withArgs(
|
||||
roomId = null,
|
||||
otherUserId = action.userId!!,
|
||||
transactionId = action.transactionId!!
|
||||
).show(childFragmentManager, "REQPOP")
|
||||
}
|
||||
}
|
||||
}
|
||||
is DevicesViewEvents.Loading -> showLoading(it.message)
|
||||
is DevicesViewEvents.Failure -> showFailure(it.throwable)
|
||||
is DevicesViewEvents.RequestPassword -> maybeShowDeleteDeviceWithPasswordDialog()
|
||||
is DevicesViewEvents.PromptRenameDevice -> displayDeviceRenameDialog(it.deviceInfo)
|
||||
is DevicesViewEvents.ShowVerifyDevice -> {
|
||||
VerificationBottomSheet.withArgs(
|
||||
roomId = null,
|
||||
otherUserId = it.userId,
|
||||
transactionId = it.transactionId
|
||||
).show(childFragmentManager, "REQPOP")
|
||||
}
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,7 +133,7 @@ class VectorSettingsDevicesFragment @Inject constructor(
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
val newName = input.text.toString()
|
||||
|
||||
viewModel.handle(DevicesAction.Rename(deviceInfo, newName))
|
||||
viewModel.handle(DevicesAction.Rename(deviceInfo.deviceId!!, newName))
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
|
Loading…
Reference in New Issue
Block a user