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