Merge pull request #664 from vector-im/feature/room_list_actions

Feature/room list actions
This commit is contained in:
ganfra 2019-11-07 15:45:45 +01:00 committed by GitHub
commit cbdbe5033f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
84 changed files with 1690 additions and 494 deletions

View file

@ -2,6 +2,7 @@ Changes in RiotX 0.8.0 (2019-XX-XX)
===================================================
Features ✨:
- Handle long click on room in the room list (#395)
- Ignore/UnIgnore users, and display list of ignored users (#542, #617)
Improvements 🙌:
@ -38,6 +39,7 @@ Improvements:
- Attachments: start using system pickers (#52)
- Mark all messages as read (#396)
Other changes:
- Accessibility improvements to read receipts in the room timeline and reactions emoji chooser

View file

@ -20,6 +20,7 @@ import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.Optional
@ -67,6 +68,10 @@ class RxRoom(private val room: Room) {
fun liveDrafts(): Observable<List<UserDraft>> {
return room.getDraftsLive().asObservable()
}
fun liveNotificationState(): Observable<RoomNotificationState> {
return room.getLiveRoomNotificationState().asObservable()
}
}
fun Room.rx(): RxRoom {

View file

@ -21,7 +21,7 @@ import timber.log.Timber
sealed class Action {
object Notify : Action()
object DoNotNotify : Action()
data class Sound(val sound: String) : Action()
data class Sound(val sound: String = ACTION_OBJECT_VALUE_VALUE_DEFAULT) : Action()
data class Highlight(val highlight: Boolean) : Action()
}
@ -63,6 +63,29 @@ private const val ACTION_OBJECT_VALUE_VALUE_DEFAULT = "default"
*
* </pre>
*/
@Suppress("IMPLICIT_CAST_TO_ANY")
fun List<Action>.toJson(): List<Any> {
return map { action ->
when (action) {
is Action.Notify -> ACTION_NOTIFY
is Action.DoNotNotify -> ACTION_DONT_NOTIFY
is Action.Sound -> {
mapOf(
ACTION_OBJECT_SET_TWEAK_KEY to ACTION_OBJECT_SET_TWEAK_VALUE_SOUND,
ACTION_OBJECT_VALUE_KEY to action.sound
)
}
is Action.Highlight -> {
mapOf(
ACTION_OBJECT_SET_TWEAK_KEY to ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT,
ACTION_OBJECT_VALUE_KEY to action.highlight
)
}
}
}
}
fun PushRule.getActions(): List<Action> {
val result = ArrayList<Action>()

View file

@ -34,6 +34,10 @@ interface PushRuleService {
fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable
fun addPushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
fun removePushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
fun addPushRuleListener(listener: PushRuleListener)
fun removePushRuleListener(listener: PushRuleListener)

View file

@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.room.crypto.RoomCryptoService
import im.vector.matrix.android.api.session.room.members.MembershipService
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.relation.RelationService
import im.vector.matrix.android.api.session.room.notification.RoomPushRuleService
import im.vector.matrix.android.api.session.room.reporting.ReportingService
import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.api.session.room.send.DraftService
@ -41,7 +42,8 @@ interface Room :
StateService,
ReportingService,
RelationService,
RoomCryptoService {
RoomCryptoService,
RoomPushRuleService {
/**
* The roomId of this room

View file

@ -0,0 +1,42 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.notification
/**
* Defines the room notification state
*/
enum class RoomNotificationState {
/**
* All the messages will trigger a noisy notification
*/
ALL_MESSAGES_NOISY,
/**
* All the messages will trigger a notification
*/
ALL_MESSAGES,
/**
* Only the messages with user display name / user name will trigger notifications
*/
MENTIONS_ONLY,
/**
* No notifications
*/
MUTE
}

View file

@ -0,0 +1,28 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.notification
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
interface RoomPushRuleService {
fun getLiveRoomNotificationState(): LiveData<RoomNotificationState>
fun setRoomNotificationState(roomNotificationState: RoomNotificationState, matrixCallback: MatrixCallback<Unit>): Cancelable
}

View file

@ -17,6 +17,8 @@ package im.vector.matrix.android.internal.database.model
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.LinkingObjects
internal open class PushRuleEntity(
// Required. The actions to perform when this rule is matched.
@ -33,5 +35,8 @@ internal open class PushRuleEntity(
var pattern: String? = null
) : RealmObject() {
@LinkingObjects("pushRules")
val parent: RealmResults<PushRulesEntity>? = null
companion object
}

View file

@ -16,10 +16,10 @@
package im.vector.matrix.android.internal.database.query
import im.vector.matrix.android.api.pushrules.RuleKind
import im.vector.matrix.android.internal.database.model.*
import im.vector.matrix.android.internal.database.model.PushRuleEntity
import im.vector.matrix.android.internal.database.model.PushRulesEntity
import im.vector.matrix.android.internal.database.model.PushRulesEntityFields
import im.vector.matrix.android.internal.database.model.PusherEntity
import im.vector.matrix.android.internal.database.model.PusherEntityFields
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.kotlin.where
@ -41,3 +41,11 @@ internal fun PushRulesEntity.Companion.where(realm: Realm,
.equalTo(PushRulesEntityFields.SCOPE, scope)
.equalTo(PushRulesEntityFields.KIND_STR, kind.name)
}
internal fun PushRuleEntity.Companion.where(realm: Realm,
scope: String,
ruleId: String): RealmQuery<PushRuleEntity> {
return realm.where<PushRuleEntity>()
.equalTo("${PushRuleEntityFields.PARENT}.${PushRulesEntityFields.SCOPE}", scope)
.equalTo(PushRuleEntityFields.RULE_ID, ruleId)
}

View file

@ -28,7 +28,9 @@ import im.vector.matrix.android.internal.database.mapper.PushRulesMapper
import im.vector.matrix.android.internal.database.model.PushRulesEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.pushers.AddPushRuleTask
import im.vector.matrix.android.internal.session.pushers.GetPushRulesTask
import im.vector.matrix.android.internal.session.pushers.RemovePushRuleTask
import im.vector.matrix.android.internal.session.pushers.UpdatePushRuleEnableStatusTask
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
@ -38,6 +40,8 @@ import javax.inject.Inject
@SessionScope
internal class DefaultPushRuleService @Inject constructor(private val getPushRulesTask: GetPushRulesTask,
private val updatePushRuleEnableStatusTask: UpdatePushRuleEnableStatusTask,
private val addPushRuleTask: AddPushRuleTask,
private val removePushRuleTask: RemovePushRuleTask,
private val taskExecutor: TaskExecutor,
private val monarchy: Monarchy
) : PushRuleService {
@ -98,6 +102,22 @@ internal class DefaultPushRuleService @Inject constructor(private val getPushRul
.executeBy(taskExecutor)
}
override fun addPushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable {
return addPushRuleTask
.configureWith(AddPushRuleTask.Params(kind, pushRule)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
override fun removePushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable {
return removePushRuleTask
.configureWith(RemovePushRuleTask.Params(kind, pushRule)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
override fun removePushRuleListener(listener: PushRuleService.PushRuleListener) {
synchronized(listeners) {
listeners.remove(listener)

View file

@ -0,0 +1,39 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.pushers
import im.vector.matrix.android.api.pushrules.RuleKind
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject
internal interface AddPushRuleTask : Task<AddPushRuleTask.Params, Unit> {
data class Params(
val kind: RuleKind,
val pushRule: PushRule
)
}
internal class DefaultAddPushRuleTask @Inject constructor(private val pushRulesApi: PushRulesApi)
: AddPushRuleTask {
override suspend fun execute(params: AddPushRuleTask.Params) {
return executeRequest {
apiCall = pushRulesApi.addRule(params.kind.value, params.pushRule.ruleId, params.pushRule)
}
}
}

View file

@ -25,6 +25,8 @@ import im.vector.matrix.android.api.session.pushers.PushersService
import im.vector.matrix.android.internal.session.notification.DefaultProcessEventForPushTask
import im.vector.matrix.android.internal.session.notification.DefaultPushRuleService
import im.vector.matrix.android.internal.session.notification.ProcessEventForPushTask
import im.vector.matrix.android.internal.session.room.notification.DefaultSetRoomNotificationStateTask
import im.vector.matrix.android.internal.session.room.notification.SetRoomNotificationStateTask
import retrofit2.Retrofit
@Module
@ -67,6 +69,15 @@ internal abstract class PushersModule {
@Binds
abstract fun bindUpdatePushRuleEnableStatusTask(updatePushRuleEnableStatusTask: DefaultUpdatePushRuleEnableStatusTask): UpdatePushRuleEnableStatusTask
@Binds
abstract fun bindAddPushRuleTask(addPushRuleTask: DefaultAddPushRuleTask): AddPushRuleTask
@Binds
abstract fun bindRemovePushRuleTask(removePushRuleTask: DefaultRemovePushRuleTask): RemovePushRuleTask
@Binds
abstract fun bindSetRoomNotificationStateTask(setRoomNotificationStateTask: DefaultSetRoomNotificationStateTask): SetRoomNotificationStateTask
@Binds
abstract fun bindPushRuleService(pushRuleService: DefaultPushRuleService): PushRuleService

View file

@ -0,0 +1,39 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.pushers
import im.vector.matrix.android.api.pushrules.RuleKind
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject
internal interface RemovePushRuleTask : Task<RemovePushRuleTask.Params, Unit> {
data class Params(
val kind: RuleKind,
val pushRule: PushRule
)
}
internal class DefaultRemovePushRuleTask @Inject constructor(private val pushRulesApi: PushRulesApi)
: RemovePushRuleTask {
override suspend fun execute(params: RemovePushRuleTask.Params) {
return executeRequest {
apiCall = pushRulesApi.deleteRule(params.kind.value, params.pushRule.ruleId)
}
}
}

View file

@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.members.MembershipService
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.relation.RelationService
import im.vector.matrix.android.api.session.room.notification.RoomPushRuleService
import im.vector.matrix.android.api.session.room.reporting.ReportingService
import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.api.session.room.send.DraftService
@ -49,7 +50,8 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
private val readService: ReadService,
private val cryptoService: CryptoService,
private val relationService: RelationService,
private val roomMembersService: MembershipService) :
private val roomMembersService: MembershipService,
private val roomPushRuleService: RoomPushRuleService) :
Room,
TimelineService by timelineService,
SendService by sendService,
@ -58,7 +60,8 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
ReportingService by reportingService,
ReadService by readService,
RelationService by relationService,
MembershipService by roomMembersService {
MembershipService by roomMembersService,
RoomPushRuleService by roomPushRuleService {
override fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>> {
val liveData = monarchy.findAllMappedWithChanges(

View file

@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
import im.vector.matrix.android.internal.session.room.draft.DefaultDraftService
import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
import im.vector.matrix.android.internal.session.room.notification.DefaultRoomPushRuleService
import im.vector.matrix.android.internal.session.room.read.DefaultReadService
import im.vector.matrix.android.internal.session.room.relation.DefaultRelationService
import im.vector.matrix.android.internal.session.room.reporting.DefaultReportingService
@ -44,7 +45,8 @@ internal class DefaultRoomFactory @Inject constructor(private val monarchy: Mona
private val reportingServiceFactory: DefaultReportingService.Factory,
private val readServiceFactory: DefaultReadService.Factory,
private val relationServiceFactory: DefaultRelationService.Factory,
private val membershipServiceFactory: DefaultMembershipService.Factory) :
private val membershipServiceFactory: DefaultMembershipService.Factory,
private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory) :
RoomFactory {
override fun create(roomId: String): Room {
@ -60,7 +62,8 @@ internal class DefaultRoomFactory @Inject constructor(private val monarchy: Mona
readServiceFactory.create(roomId),
cryptoService,
relationServiceFactory.create(roomId),
membershipServiceFactory.create(roomId)
membershipServiceFactory.create(roomId),
roomPushRuleServiceFactory.create(roomId)
)
}
}

View file

@ -0,0 +1,72 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.room.notification
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.pushrules.RuleScope
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
import im.vector.matrix.android.api.session.room.notification.RoomPushRuleService
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.model.PushRuleEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
internal class DefaultRoomPushRuleService @AssistedInject constructor(@Assisted private val roomId: String,
private val setRoomNotificationStateTask: SetRoomNotificationStateTask,
private val monarchy: Monarchy,
private val taskExecutor: TaskExecutor)
: RoomPushRuleService {
@AssistedInject.Factory
interface Factory {
fun create(roomId: String): RoomPushRuleService
}
override fun getLiveRoomNotificationState(): LiveData<RoomNotificationState> {
return Transformations.map(getPushRuleForRoom()) {
it?.toRoomNotificationState() ?: RoomNotificationState.ALL_MESSAGES
}
}
override fun setRoomNotificationState(roomNotificationState: RoomNotificationState, matrixCallback: MatrixCallback<Unit>): Cancelable {
return setRoomNotificationStateTask
.configureWith(SetRoomNotificationStateTask.Params(roomId, roomNotificationState)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
private fun getPushRuleForRoom(): LiveData<RoomPushRule?> {
val liveData = monarchy.findAllMappedWithChanges(
{ realm ->
PushRuleEntity.where(realm, scope = RuleScope.GLOBAL, ruleId = roomId)
},
{ result ->
result.toRoomPushRule()
}
)
return Transformations.map(liveData) { results ->
results.firstOrNull()
}
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.room.notification
import im.vector.matrix.android.api.pushrules.RuleKind
import im.vector.matrix.android.api.pushrules.rest.PushRule
internal data class RoomPushRule(
val kind: RuleKind,
val rule: PushRule
)

View file

@ -0,0 +1,101 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.room.notification
import im.vector.matrix.android.api.pushrules.*
import im.vector.matrix.android.api.pushrules.rest.PushCondition
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
import im.vector.matrix.android.internal.database.mapper.PushRulesMapper
import im.vector.matrix.android.internal.database.model.PushRuleEntity
internal fun PushRuleEntity.toRoomPushRule(): RoomPushRule? {
val kind = parent?.firstOrNull()?.kind
val pushRule = when (kind) {
RuleSetKey.OVERRIDE -> {
PushRulesMapper.map(this)
}
RuleSetKey.ROOM -> {
PushRulesMapper.mapRoomRule(this)
}
else -> null
}
return if (pushRule == null || kind == null) {
null
} else {
RoomPushRule(kind, pushRule)
}
}
internal fun RoomNotificationState.toRoomPushRule(roomId: String): RoomPushRule? {
return when {
this == RoomNotificationState.ALL_MESSAGES -> null
this == RoomNotificationState.ALL_MESSAGES_NOISY -> {
val rule = PushRule(
actions = listOf(Action.Notify, Action.Sound()).toJson(),
enabled = true,
ruleId = roomId
)
return RoomPushRule(RuleSetKey.ROOM, rule)
}
else -> {
val condition = PushCondition(
kind = Condition.Kind.event_match.value,
key = "room_id",
pattern = roomId
)
val rule = PushRule(
actions = listOf(Action.DoNotNotify).toJson(),
enabled = true,
ruleId = roomId,
conditions = listOf(condition)
)
val kind = if (this == RoomNotificationState.MUTE) {
RuleSetKey.OVERRIDE
} else {
RuleSetKey.ROOM
}
return RoomPushRule(kind, rule)
}
}
}
internal fun RoomPushRule.toRoomNotificationState(): RoomNotificationState {
return if (rule.enabled) {
val actions = rule.getActions()
if (actions.contains(Action.DoNotNotify)) {
if (kind == RuleSetKey.OVERRIDE) {
RoomNotificationState.MUTE
} else {
RoomNotificationState.MENTIONS_ONLY
}
} else if (actions.contains(Action.Notify)) {
val hasSoundAction = actions.find {
it is Action.Sound
} != null
if (hasSoundAction) {
RoomNotificationState.ALL_MESSAGES_NOISY
} else {
RoomNotificationState.ALL_MESSAGES
}
} else {
RoomNotificationState.ALL_MESSAGES
}
} else {
RoomNotificationState.ALL_MESSAGES
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.room.notification
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.pushrules.RuleScope
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
import im.vector.matrix.android.internal.database.model.PushRuleEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.pushers.AddPushRuleTask
import im.vector.matrix.android.internal.session.pushers.RemovePushRuleTask
import im.vector.matrix.android.internal.task.Task
import io.realm.Realm
import javax.inject.Inject
internal interface SetRoomNotificationStateTask : Task<SetRoomNotificationStateTask.Params, Unit> {
data class Params(
val roomId: String,
val roomNotificationState: RoomNotificationState
)
}
internal class DefaultSetRoomNotificationStateTask @Inject constructor(private val monarchy: Monarchy,
private val removePushRuleTask: RemovePushRuleTask,
private val addPushRuleTask: AddPushRuleTask)
: SetRoomNotificationStateTask {
override suspend fun execute(params: SetRoomNotificationStateTask.Params) {
val currentRoomPushRule = Realm.getInstance(monarchy.realmConfiguration).use {
PushRuleEntity.where(it, scope = RuleScope.GLOBAL, ruleId = params.roomId).findFirst()?.toRoomPushRule()
}
if (currentRoomPushRule != null) {
removePushRuleTask.execute(RemovePushRuleTask.Params(currentRoomPushRule.kind, currentRoomPushRule.rule))
}
val newRoomPushRule = params.roomNotificationState.toRoomPushRule(params.roomId)
if (newRoomPushRule != null) {
addPushRuleTask.execute(AddPushRuleTask.Params(newRoomPushRule.kind, newRoomPushRule.rule))
}
}
}

View file

@ -16,23 +16,15 @@
package im.vector.matrix.android.api.pushrules
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.session.room.model.*
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.Optional
import io.mockk.every
import io.mockk.mockk
import org.junit.Assert
import org.junit.Test
@ -133,17 +125,20 @@ class PushrulesConditionTest {
val conditionEqual3Bis = RoomMemberCountCondition("==3")
val conditionLessThan3 = RoomMemberCountCondition("<3")
val session = MockRoomService()
val room2JoinedId = "2joined"
val room3JoinedId = "3joined"
Event(
type = "m.room.message",
eventId = "mx0",
content = MessageTextContent("m.text", "A").toContent(),
originServerTs = 0,
roomId = "2joined").also {
Assert.assertFalse("This room does not have 3 members", conditionEqual3.isSatisfied(it, session))
Assert.assertFalse("This room does not have 3 members", conditionEqual3Bis.isSatisfied(it, session))
Assert.assertTrue("This room has less than 3 members", conditionLessThan3.isSatisfied(it, session))
val roomStub2Joined = mockk<Room> {
every { getNumberOfJoinedMembers() } returns 2
}
val roomStub3Joined = mockk<Room> {
every { getNumberOfJoinedMembers() } returns 3
}
val sessionStub = mockk<RoomService> {
every { getRoom(room2JoinedId) } returns roomStub2Joined
every { getRoom(room3JoinedId) } returns roomStub3Joined
}
Event(
@ -151,10 +146,21 @@ class PushrulesConditionTest {
eventId = "mx0",
content = MessageTextContent("m.text", "A").toContent(),
originServerTs = 0,
roomId = "3joined").also {
Assert.assertTrue("This room has 3 members", conditionEqual3.isSatisfied(it, session))
Assert.assertTrue("This room has 3 members", conditionEqual3Bis.isSatisfied(it, session))
Assert.assertFalse("This room has more than 3 members", conditionLessThan3.isSatisfied(it, session))
roomId = room2JoinedId).also {
Assert.assertFalse("This room does not have 3 members", conditionEqual3.isSatisfied(it, sessionStub))
Assert.assertFalse("This room does not have 3 members", conditionEqual3Bis.isSatisfied(it, sessionStub))
Assert.assertTrue("This room has less than 3 members", conditionLessThan3.isSatisfied(it, sessionStub))
}
Event(
type = "m.room.message",
eventId = "mx0",
content = MessageTextContent("m.text", "A").toContent(),
originServerTs = 0,
roomId = room3JoinedId).also {
Assert.assertTrue("This room has 3 members", conditionEqual3.isSatisfied(it, sessionStub))
Assert.assertTrue("This room has 3 members", conditionEqual3Bis.isSatisfied(it, sessionStub))
Assert.assertFalse("This room has more than 3 members", conditionLessThan3.isSatisfied(it, sessionStub))
}
}
@ -171,212 +177,4 @@ class PushrulesConditionTest {
Assert.assertTrue("Notice", conditionEqual.isSatisfied(it))
}
}
class MockRoomService() : RoomService {
override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun joinRoom(roomId: String, viaServers: List<String>, callback: MatrixCallback<Unit>): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getRoom(roomId: String): Room? {
return when (roomId) {
"2joined" -> MockRoom(roomId, 2)
"3joined" -> MockRoom(roomId, 3)
else -> null
}
}
override fun liveRoomSummaries(): LiveData<List<RoomSummary>> {
return MutableLiveData()
}
override fun markAllAsRead(roomIds: List<String>, callback: MatrixCallback<Unit>): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
}
class MockRoom(override val roomId: String, val _numberOfJoinedMembers: Int) : Room {
override fun reportContent(eventId: String, score: Int, reason: String, callback: MatrixCallback<Unit>): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getReadMarkerLive(): LiveData<Optional<String>> {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getMyReadReceiptLive(): LiveData<Optional<String>> {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun resendTextMessage(localEcho: TimelineEvent): Cancelable? {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun resendMediaMessage(localEcho: TimelineEvent): Cancelable? {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun deleteFailedEcho(localEcho: TimelineEvent) {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun clearSendingQueue() {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun resendAllFailedMessages() {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun saveDraft(draft: UserDraft) {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun deleteDraft() {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getDraftsLive(): LiveData<List<UserDraft>> {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>> {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getStateEvent(eventType: String): Event? {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun editReply(replyToEdit: TimelineEvent, originalTimelineEvent: TimelineEvent, newBodyText: String, compatibilityBodyText: String): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>) {
}
override fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>> {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getNumberOfJoinedMembers(): Int {
return _numberOfJoinedMembers
}
override fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>> {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun roomSummary(): RoomSummary? {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun createTimeline(eventId: String?, settings: TimelineSettings): Timeline {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getTimeLineEvent(eventId: String): TimelineEvent? {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun sendTextMessage(text: String, msgType: String, autoMarkdown: Boolean): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun sendFormattedTextMessage(text: String, formattedText: String): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun sendMedia(attachment: ContentAttachmentData): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun sendMedias(attachments: List<ContentAttachmentData>): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun redactEvent(event: Event, reason: String?): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun markAllAsRead(callback: MatrixCallback<Unit>) {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun setReadReceipt(eventId: String, callback: MatrixCallback<Unit>) {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>) {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun isEventRead(eventId: String): Boolean {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getRoomMember(userId: String): RoomMember? {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getRoomMemberIdsLive(): LiveData<List<String>> {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun invite(userId: String, callback: MatrixCallback<Unit>): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun join(viaServers: List<String>, callback: MatrixCallback<Unit>): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun leave(callback: MatrixCallback<Unit>): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun updateTopic(topic: String, callback: MatrixCallback<Unit>) {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun sendReaction(targetEventId: String, reaction: String): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun undoReaction(targetEventId: String, reaction: String): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun editTextMessage(targetEventId: String, msgType: String, newBodyText: String,
newBodyAutoMarkdown: Boolean, compatibilityBodyText: String): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun replyToMessage(eventReplied: TimelineEvent, replyText: String, autoMarkdown: Boolean): Cancelable? {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getEventSummaryLive(eventId: String): LiveData<Optional<EventAnnotationsSummary>> {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun isEncrypted(): Boolean {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun encryptionAlgorithm(): String? {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun shouldEncryptForInvitedMembers(): Boolean {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
}
}

View file

@ -19,9 +19,9 @@ package im.vector.riotx
import arrow.core.Option
import im.vector.matrix.android.api.session.Session
import im.vector.riotx.core.utils.RxStore
import im.vector.riotx.core.utils.BehaviorDataSource
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ActiveSessionObservableStore @Inject constructor() : RxStore<Option<Session>>()
class ActiveSessionDataSource @Inject constructor() : BehaviorDataSource<Option<Session>>()

View file

@ -23,9 +23,9 @@ import arrow.core.Option
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.rx.rx
import im.vector.riotx.features.home.HomeRoomListObservableStore
import im.vector.riotx.features.home.HomeRoomListDataSource
import im.vector.riotx.features.home.group.ALL_COMMUNITIES_GROUP_ID
import im.vector.riotx.features.home.group.SelectedGroupStore
import im.vector.riotx.features.home.group.SelectedGroupDataSource
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
@ -41,9 +41,9 @@ import javax.inject.Singleton
*/
@Singleton
class AppStateHandler @Inject constructor(
private val sessionObservableStore: ActiveSessionObservableStore,
private val homeRoomListObservableStore: HomeRoomListObservableStore,
private val selectedGroupStore: SelectedGroupStore) : LifecycleObserver {
private val sessionDataSource: ActiveSessionDataSource,
private val homeRoomListDataSource: HomeRoomListDataSource,
private val selectedGroupDataSource: SelectedGroupDataSource) : LifecycleObserver {
private val compositeDisposable = CompositeDisposable()
@ -60,14 +60,14 @@ class AppStateHandler @Inject constructor(
private fun observeRoomsAndGroup() {
Observable
.combineLatest<List<RoomSummary>, Option<GroupSummary>, List<RoomSummary>>(
sessionObservableStore.observe()
sessionDataSource.observe()
.observeOn(AndroidSchedulers.mainThread())
.switchMap {
it.orNull()?.rx()?.liveRoomSummaries()
?: Observable.just(emptyList())
}
.throttleLast(300, TimeUnit.MILLISECONDS),
selectedGroupStore.observe(),
selectedGroupDataSource.observe(),
BiFunction { rooms, selectedGroupOption ->
val selectedGroup = selectedGroupOption.orNull()
val filteredDirectRooms = rooms
@ -92,7 +92,7 @@ class AppStateHandler @Inject constructor(
}
)
.subscribe {
homeRoomListObservableStore.post(it)
homeRoomListDataSource.post(it)
}
.addTo(compositeDisposable)
}

View file

@ -19,7 +19,7 @@ package im.vector.riotx.core.di
import arrow.core.Option
import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.session.Session
import im.vector.riotx.ActiveSessionObservableStore
import im.vector.riotx.ActiveSessionDataSource
import im.vector.riotx.features.crypto.keysrequest.KeyRequestHandler
import im.vector.riotx.features.crypto.verification.IncomingVerificationRequestHandler
import java.util.concurrent.atomic.AtomicReference
@ -28,7 +28,7 @@ import javax.inject.Singleton
@Singleton
class ActiveSessionHolder @Inject constructor(private val authenticator: Authenticator,
private val sessionObservableStore: ActiveSessionObservableStore,
private val sessionObservableStore: ActiveSessionDataSource,
private val keyRequestHandler: KeyRequestHandler,
private val incomingVerificationRequestHandler: IncomingVerificationRequestHandler
) {

View file

@ -32,6 +32,7 @@ import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsB
import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import im.vector.riotx.features.home.room.list.RoomListModule
import im.vector.riotx.features.invite.VectorInviteView
import im.vector.riotx.features.link.LinkHandlerActivity
@ -123,6 +124,8 @@ interface ScreenComponent {
fun inject(incomingShareActivity: IncomingShareActivity)
fun inject(roomListActionsBottomSheet: RoomListQuickActionsBottomSheet)
@Component.Factory
interface Factory {
fun create(vectorComponent: VectorComponent,

View file

@ -23,7 +23,7 @@ import dagger.Component
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.session.Session
import im.vector.riotx.ActiveSessionObservableStore
import im.vector.riotx.ActiveSessionDataSource
import im.vector.riotx.EmojiCompatFontProvider
import im.vector.riotx.EmojiCompatWrapper
import im.vector.riotx.VectorApplication
@ -33,8 +33,8 @@ import im.vector.riotx.features.configuration.VectorConfiguration
import im.vector.riotx.features.crypto.keysrequest.KeyRequestHandler
import im.vector.riotx.features.crypto.verification.IncomingVerificationRequestHandler
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.HomeRoomListObservableStore
import im.vector.riotx.features.home.group.SelectedGroupStore
import im.vector.riotx.features.home.HomeRoomListDataSource
import im.vector.riotx.features.home.group.SelectedGroupDataSource
import im.vector.riotx.features.html.EventHtmlRenderer
import im.vector.riotx.features.navigation.Navigator
import im.vector.riotx.features.notifications.*
@ -43,7 +43,7 @@ import im.vector.riotx.features.rageshake.VectorFileLogger
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotx.features.session.SessionListener
import im.vector.riotx.features.settings.VectorPreferences
import im.vector.riotx.features.share.ShareRoomListObservableStore
import im.vector.riotx.features.share.ShareRoomListDataSource
import im.vector.riotx.features.ui.UiStateRepository
import javax.inject.Singleton
@ -85,13 +85,13 @@ interface VectorComponent {
fun navigator(): Navigator
fun homeRoomListObservableStore(): HomeRoomListObservableStore
fun homeRoomListObservableStore(): HomeRoomListDataSource
fun shareRoomListObservableStore(): ShareRoomListObservableStore
fun shareRoomListObservableStore(): ShareRoomListDataSource
fun selectedGroupStore(): SelectedGroupStore
fun selectedGroupStore(): SelectedGroupDataSource
fun activeSessionObservableStore(): ActiveSessionObservableStore
fun activeSessionObservableStore(): ActiveSessionDataSource
fun incomingVerificationRequestHandler(): IncomingVerificationRequestHandler

View file

@ -27,10 +27,7 @@ import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromP
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedViewModel
import im.vector.riotx.features.crypto.verification.SasVerificationViewModel
import im.vector.riotx.features.home.HomeNavigationViewModel
import im.vector.riotx.features.home.createdirect.CreateDirectRoomNavigationViewModel
import im.vector.riotx.features.reactions.EmojiChooserViewModel
import im.vector.riotx.features.roomdirectory.RoomDirectoryNavigationViewModel
import im.vector.riotx.features.workers.signout.SignOutViewModel
@Module
@ -76,16 +73,6 @@ interface ViewModelModule {
@ViewModelKey(KeysBackupRestoreFromPassphraseViewModel::class)
fun bindKeysBackupRestoreFromPassphraseViewModel(viewModel: KeysBackupRestoreFromPassphraseViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(RoomDirectoryNavigationViewModel::class)
fun bindRoomDirectoryNavigationViewModel(viewModel: RoomDirectoryNavigationViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(HomeNavigationViewModel::class)
fun bindHomeNavigationViewModel(viewModel: HomeNavigationViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(KeysBackupSetupSharedViewModel::class)
@ -95,9 +82,4 @@ interface ViewModelModule {
@IntoMap
@ViewModelKey(ConfigurationViewModel::class)
fun bindConfigurationViewModel(viewModel: ConfigurationViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(CreateDirectRoomNavigationViewModel::class)
fun bindCreateDirectRoomNavigationViewModel(viewModel: CreateDirectRoomNavigationViewModel): ViewModel
}

View file

@ -5,26 +5,33 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* 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.timeline.action
package im.vector.riotx.core.epoxy.bottomsheet
import android.content.res.ColorStateList
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.widget.ImageViewCompat
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.features.themes.ThemeUtils
/**
* A action for bottom sheet.
@ -42,28 +49,48 @@ abstract class BottomSheetItemAction : VectorEpoxyModel<BottomSheetItemAction.Ho
@EpoxyAttribute
var expanded = false
@EpoxyAttribute
var selected = false
@EpoxyAttribute
var subMenuItem = false
@EpoxyAttribute
var destructive = false
@EpoxyAttribute
lateinit var listener: View.OnClickListener
override fun bind(holder: Holder) {
holder.view.setOnClickListener {
listener.onClick(it)
}
holder.startSpace.isVisible = subMenuItem
val tintColor = if (destructive) {
ContextCompat.getColor(holder.view.context, R.color.riotx_notice)
} else {
ThemeUtils.getColor(holder.view.context, R.attr.riotx_text_secondary)
}
holder.icon.setImageResource(iconRes)
ImageViewCompat.setImageTintList(holder.icon, ColorStateList.valueOf(tintColor))
holder.text.setText(textRes)
holder.expand.isVisible = showExpand
holder.text.setTextColor(tintColor)
holder.selected.isInvisible = !selected
if (showExpand) {
holder.expand.setImageResource(if (expanded) R.drawable.ic_material_expand_less_black else R.drawable.ic_material_expand_more_black)
val expandDrawable = if (expanded) {
ContextCompat.getDrawable(holder.view.context, R.drawable.ic_material_expand_less_black)
} else {
ContextCompat.getDrawable(holder.view.context, R.drawable.ic_material_expand_more_black)
}
expandDrawable?.also {
DrawableCompat.setTint(it, tintColor)
}
holder.text.setCompoundDrawablesWithIntrinsicBounds(null, null, expandDrawable, null)
} else {
holder.text.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null)
}
}
class Holder : VectorEpoxyHolder() {
val startSpace by bind<View>(R.id.action_start_space)
val icon by bind<ImageView>(R.id.action_icon)
val text by bind<TextView>(R.id.action_title)
val expand by bind<ImageView>(R.id.action_expand)
val startSpace by bind<View>(R.id.actionStartSpace)
val icon by bind<ImageView>(R.id.actionIcon)
val text by bind<TextView>(R.id.actionTitle)
val selected by bind<ImageView>(R.id.actionSelected)
}
}

View file

@ -5,15 +5,16 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* 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.timeline.action
package im.vector.riotx.core.epoxy.bottomsheet
import android.widget.ImageView
import android.widget.TextView
@ -24,7 +25,6 @@ import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.extensions.setTextOrHide
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
/**
* A message preview for bottom sheet.
@ -35,7 +35,9 @@ abstract class BottomSheetItemMessagePreview : VectorEpoxyModel<BottomSheetItemM
@EpoxyAttribute
lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
lateinit var informationData: MessageInformationData
lateinit var avatarUrl: String
@EpoxyAttribute
lateinit var senderId: String
@EpoxyAttribute
var senderName: String? = null
@EpoxyAttribute
@ -44,7 +46,7 @@ abstract class BottomSheetItemMessagePreview : VectorEpoxyModel<BottomSheetItemM
var time: CharSequence? = null
override fun bind(holder: Holder) {
avatarRenderer.render(informationData.avatarUrl, informationData.senderId, senderName, holder.avatar)
avatarRenderer.render(avatarUrl, senderId, senderName, holder.avatar)
holder.sender.setTextOrHide(senderName)
holder.body.text = body
holder.timestamp.setTextOrHide(time)

View file

@ -5,15 +5,16 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* 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.timeline.action
package im.vector.riotx.core.epoxy.bottomsheet
import android.graphics.Typeface
import android.widget.TextView

View file

@ -0,0 +1,57 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.riotx.core.epoxy.bottomsheet
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.extensions.setTextOrHide
import im.vector.riotx.features.home.AvatarRenderer
/**
* A room preview for bottom sheet.
*/
@EpoxyModelClass(layout = R.layout.item_bottom_sheet_room_preview)
abstract class BottomSheetItemRoomPreview : VectorEpoxyModel<BottomSheetItemRoomPreview.Holder>() {
@EpoxyAttribute
lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
lateinit var avatarUrl: String
@EpoxyAttribute
lateinit var roomId: String
@EpoxyAttribute
var roomName: String? = null
@EpoxyAttribute var settingsClickListener: View.OnClickListener? = null
override fun bind(holder: Holder) {
avatarRenderer.render(avatarUrl, roomId, roomName, holder.avatar)
holder.roomName.setTextOrHide(roomName)
holder.roomSettings.setOnClickListener(settingsClickListener)
}
class Holder : VectorEpoxyHolder() {
val avatar by bind<ImageView>(R.id.bottomSheetRoomPreviewAvatar)
val roomName by bind<TextView>(R.id.bottomSheetRoomPreviewName)
val roomSettings by bind<View>(R.id.bottomSheetRoomPreviewSettings)
}
}

View file

@ -5,15 +5,16 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* 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.timeline.action
package im.vector.riotx.core.epoxy.bottomsheet
import android.view.View
import android.widget.TextView

View file

@ -5,15 +5,16 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* 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.timeline.action
package im.vector.riotx.core.epoxy.bottomsheet
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R

View file

@ -1,34 +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.core.mvrx
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.utils.LiveEvent
abstract class NavigationViewModel<NavigationClass> : ViewModel() {
private val _navigateTo = MutableLiveData<LiveEvent<NavigationClass>>()
val navigateTo: LiveData<LiveEvent<NavigationClass>>
get() = _navigateTo
fun goTo(navigation: NavigationClass) {
_navigateTo.postLiveEvent(navigation)
}
}

View file

@ -17,18 +17,30 @@
package im.vector.riotx.core.utils
import com.jakewharton.rxrelay2.BehaviorRelay
import com.jakewharton.rxrelay2.PublishRelay
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
open class RxStore<T>(private val defaultValue: T? = null) {
interface DataSource<T> {
fun observe(): Observable<T>
}
interface MutableDataSource<T> : DataSource<T> {
fun post(value: T)
}
/**
* This datasource emits the most recent value it has observed and all subsequent observed values to each subscriber.
*/
open class BehaviorDataSource<T>(private val defaultValue: T? = null) : MutableDataSource<T> {
private val storeRelay = createRelay()
fun observe(): Observable<T> {
override fun observe(): Observable<T> {
return storeRelay.hide().observeOn(Schedulers.computation())
}
fun post(value: T) {
override fun post(value: T) {
storeRelay.accept(value)
}
@ -40,3 +52,19 @@ open class RxStore<T>(private val defaultValue: T? = null) {
}
}
}
/**
* This datasource only emits all subsequent observed values to each subscriber.
*/
open class PublishDataSource<T> : MutableDataSource<T> {
private val storeRelay = PublishRelay.create<T>()
override fun observe(): Observable<T> {
return storeRelay.hide()
}
override fun post(value: T) {
storeRelay.accept(value)
}
}

View file

@ -31,7 +31,6 @@ import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.extensions.hideKeyboard
import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.extensions.replaceFragment
import im.vector.riotx.core.platform.ToolbarConfigurable
import im.vector.riotx.core.platform.VectorBaseActivity
@ -83,15 +82,17 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
replaceFragment(R.id.homeDrawerFragmentContainer, HomeDrawerFragment::class.java)
}
navigationViewModel.navigateTo.observeEvent(this) { navigation ->
when (navigation) {
is Navigation.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START)
is Navigation.OpenGroup -> {
drawerLayout.closeDrawer(GravityCompat.START)
replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java)
navigationViewModel.observe()
.subscribe { navigation ->
when (navigation) {
is Navigation.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START)
is Navigation.OpenGroup -> {
drawerLayout.closeDrawer(GravityCompat.START)
replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java)
}
}
}
}
}
.disposeOnDestroy()
if (intent.getBooleanExtra(EXTRA_CLEAR_EXISTING_NOTIFICATION, false)) {
notificationDrawerManager.clearAllEvents()

View file

@ -129,7 +129,7 @@ class HomeDetailFragment @Inject constructor(
}
groupToolbar.title = ""
groupToolbarAvatarImageView.setOnClickListener {
navigationViewModel.goTo(HomeActivity.Navigation.OpenDrawer)
navigationViewModel.post(HomeActivity.Navigation.OpenDrawer)
}
}

View file

@ -26,7 +26,7 @@ import im.vector.matrix.rx.rx
import im.vector.riotx.core.di.HasScreenInjector
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.group.SelectedGroupStore
import im.vector.riotx.features.home.group.SelectedGroupDataSource
import im.vector.riotx.features.home.room.list.RoomListFragment
import im.vector.riotx.features.ui.UiStateRepository
import io.reactivex.schedulers.Schedulers
@ -38,8 +38,8 @@ import io.reactivex.schedulers.Schedulers
class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: HomeDetailViewState,
private val session: Session,
private val uiStateRepository: UiStateRepository,
private val selectedGroupStore: SelectedGroupStore,
private val homeRoomListStore: HomeRoomListObservableStore,
private val selectedGroupStore: SelectedGroupDataSource,
private val homeRoomListStore: HomeRoomListDataSource,
private val stringProvider: StringProvider)
: VectorViewModel<HomeDetailViewState>(initialState) {

View file

@ -16,7 +16,9 @@
package im.vector.riotx.features.home
import im.vector.riotx.core.mvrx.NavigationViewModel
import javax.inject.Inject
import androidx.lifecycle.ViewModel
import im.vector.riotx.core.utils.PublishDataSource
import im.vector.riotx.core.utils.MutableDataSource
class HomeNavigationViewModel @Inject constructor() : NavigationViewModel<HomeActivity.Navigation>()
class HomeNavigationViewModel(private val source: MutableDataSource<HomeActivity.Navigation> = PublishDataSource())
: ViewModel(), MutableDataSource<HomeActivity.Navigation> by source

View file

@ -17,9 +17,9 @@
package im.vector.riotx.features.home
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotx.core.utils.RxStore
import im.vector.riotx.core.utils.BehaviorDataSource
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class HomeRoomListObservableStore @Inject constructor() : RxStore<List<RoomSummary>>()
class HomeRoomListDataSource @Inject constructor() : BehaviorDataSource<List<RoomSummary>>()

View file

@ -31,7 +31,6 @@ import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.extensions.addFragment
import im.vector.riotx.core.extensions.addFragmentToBackstack
import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.platform.SimpleFragmentActivity
import im.vector.riotx.core.platform.WaitingViewData
import kotlinx.android.synthetic.main.activity.*
@ -59,13 +58,15 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
super.onCreate(savedInstanceState)
toolbar.visibility = View.GONE
navigationViewModel = ViewModelProviders.of(this, viewModelFactory).get(CreateDirectRoomNavigationViewModel::class.java)
navigationViewModel.navigateTo.observeEvent(this) { navigation ->
when (navigation) {
is Navigation.UsersDirectory -> addFragmentToBackstack(R.id.container, CreateDirectRoomDirectoryUsersFragment::class.java)
Navigation.Close -> finish()
Navigation.Previous -> onBackPressed()
}
}
navigationViewModel.observe()
.subscribe { navigation ->
when (navigation) {
is Navigation.UsersDirectory -> addFragmentToBackstack(R.id.container, CreateDirectRoomDirectoryUsersFragment::class.java)
Navigation.Close -> finish()
Navigation.Previous -> onBackPressed()
}
}
.disposeOnDestroy()
if (isFirstCreation()) {
addFragment(R.id.container, CreateDirectRoomKnownUsersFragment::class.java)
}

View file

@ -70,7 +70,7 @@ class CreateDirectRoomDirectoryUsersFragment @Inject constructor(
private fun setupCloseView() {
createDirectRoomClose.setOnClickListener {
navigationViewModel.goTo(CreateDirectRoomActivity.Navigation.Previous)
navigationViewModel.post(CreateDirectRoomActivity.Navigation.Previous)
}
}
@ -81,7 +81,7 @@ class CreateDirectRoomDirectoryUsersFragment @Inject constructor(
override fun onItemClick(user: User) {
view?.hideKeyboard()
viewModel.handle(CreateDirectRoomActions.SelectUser(user))
navigationViewModel.goTo(CreateDirectRoomActivity.Navigation.Previous)
navigationViewModel.post(CreateDirectRoomActivity.Navigation.Previous)
}
override fun retryDirectoryUsersRequest() {

View file

@ -89,7 +89,7 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor(
private fun setupAddByMatrixIdView() {
addByMatrixId.setOnClickListener {
navigationViewModel.goTo(CreateDirectRoomActivity.Navigation.UsersDirectory)
navigationViewModel.post(CreateDirectRoomActivity.Navigation.UsersDirectory)
}
}

View file

@ -16,7 +16,9 @@
package im.vector.riotx.features.home.createdirect
import im.vector.riotx.core.mvrx.NavigationViewModel
import javax.inject.Inject
import androidx.lifecycle.ViewModel
import im.vector.riotx.core.utils.PublishDataSource
import im.vector.riotx.core.utils.MutableDataSource
class CreateDirectRoomNavigationViewModel @Inject constructor(): NavigationViewModel<CreateDirectRoomActivity.Navigation>()
class CreateDirectRoomNavigationViewModel(private val dataSource: MutableDataSource<CreateDirectRoomActivity.Navigation> = PublishDataSource())
: ViewModel(), MutableDataSource<CreateDirectRoomActivity.Navigation> by dataSource

View file

@ -49,7 +49,7 @@ class GroupListFragment @Inject constructor(
groupListEpoxyRecyclerView.setController(groupController)
viewModel.subscribe { renderState(it) }
viewModel.openGroupLiveData.observeEvent(this) {
navigationViewModel.goTo(HomeActivity.Navigation.OpenGroup)
navigationViewModel.post(HomeActivity.Navigation.OpenGroup)
}
}

View file

@ -39,7 +39,7 @@ import io.reactivex.functions.BiFunction
const val ALL_COMMUNITIES_GROUP_ID = "ALL_COMMUNITIES_GROUP_ID"
class GroupListViewModel @AssistedInject constructor(@Assisted initialState: GroupListViewState,
private val selectedGroupStore: SelectedGroupStore,
private val selectedGroupStore: SelectedGroupDataSource,
private val session: Session,
private val stringProvider: StringProvider
) : VectorViewModel<GroupListViewState>(initialState) {

View file

@ -18,9 +18,9 @@ package im.vector.riotx.features.home.group
import arrow.core.Option
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.riotx.core.utils.RxStore
import im.vector.riotx.core.utils.BehaviorDataSource
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SelectedGroupStore @Inject constructor() : RxStore<Option<GroupSummary>>(Option.empty())
class SelectedGroupDataSource @Inject constructor() : BehaviorDataSource<Option<GroupSummary>>(Option.empty())

View file

@ -97,8 +97,8 @@ import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewState
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotx.features.home.room.detail.timeline.action.ActionsHandler
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsDispatcher
import im.vector.riotx.features.home.room.detail.timeline.action.SimpleAction
import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.item.*
@ -193,7 +193,7 @@ class RoomDetailFragment @Inject constructor(
override fun getMenuRes() = R.menu.menu_timeline
private lateinit var actionViewModel: ActionsHandler
private lateinit var messageActionsDispatcher: MessageActionsDispatcher
private lateinit var layoutManager: LinearLayoutManager
private lateinit var attachmentsHelper: AttachmentsHelper
private lateinit var keyboardStateUtils: KeyboardStateUtils
@ -206,7 +206,7 @@ class RoomDetailFragment @Inject constructor(
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
actionViewModel = ViewModelProviders.of(requireActivity()).get(ActionsHandler::class.java)
messageActionsDispatcher = ViewModelProviders.of(requireActivity()).get(MessageActionsDispatcher::class.java)
attachmentsHelper = AttachmentsHelper.create(this, this).register()
keyboardStateUtils = KeyboardStateUtils(requireActivity())
setupToolbar(roomToolbar)
@ -225,9 +225,12 @@ class RoomDetailFragment @Inject constructor(
val message = requireContext().getString(pair.first, *pair.second.toTypedArray())
showSnackWithMessage(message, Snackbar.LENGTH_LONG)
}
actionViewModel.actionCommandEvent.observeEvent(this) {
handleActions(it)
}
messageActionsDispatcher
.observe()
.subscribe {
handleActions(it)
}
.disposeOnDestroy()
roomDetailViewModel.navigateToEvent.observeEvent(this) {
val scrollPosition = timelineEventController.searchPositionOfEvent(it)

View file

@ -47,7 +47,7 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message
override val showExpanded = true
private lateinit var actionHandlerModel: ActionsHandler
private lateinit var messageActionsStore: MessageActionsDispatcher
override fun injectWith(screenComponent: ScreenComponent) {
screenComponent.inject(this)
@ -61,7 +61,7 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
actionHandlerModel = ViewModelProviders.of(requireActivity()).get(ActionsHandler::class.java)
messageActionsStore = ViewModelProviders.of(requireActivity()).get(MessageActionsDispatcher::class.java)
recyclerView.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
recyclerView.adapter = messageActionsEpoxyController.adapter
// Disable item animation
@ -74,7 +74,7 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message
// Toggle report menu
viewModel.toggleReportMenu()
} else {
actionHandlerModel.fireAction(simpleAction)
messageActionsStore.post(simpleAction)
dismiss()
}
}

View file

@ -15,20 +15,13 @@
*/
package im.vector.riotx.features.home.room.detail.timeline.action
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.utils.LiveEvent
import javax.inject.Inject
import im.vector.riotx.core.utils.PublishDataSource
import im.vector.riotx.core.utils.MutableDataSource
/**
* Activity shared view model to handle message actions
*/
class ActionsHandler @Inject constructor() : ViewModel() {
val actionCommandEvent = MutableLiveData<LiveEvent<SimpleAction>>()
fun fireAction(action: SimpleAction) {
actionCommandEvent.postLiveEvent(action)
}
}
class MessageActionsDispatcher constructor(
private val dataSource: MutableDataSource<SimpleAction> = PublishDataSource()
) : ViewModel(), MutableDataSource<SimpleAction> by dataSource

View file

@ -20,6 +20,12 @@ import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Success
import im.vector.riotx.EmojiCompatFontProvider
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.bottomsheet.BottomSheetItemQuickReactions
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetItemAction
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetItemMessagePreview
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetItemQuickReactions
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetItemSendState
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetItemSeparator
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.AvatarRenderer
import javax.inject.Inject
@ -40,7 +46,8 @@ class MessageActionsEpoxyController @Inject constructor(private val stringProvid
bottomSheetItemMessagePreview {
id("preview")
avatarRenderer(avatarRenderer)
informationData(state.informationData)
avatarUrl(state.informationData.avatarUrl ?: "")
senderId(state.informationData.senderId)
senderName(state.senderName())
body(body)
time(state.time())

View file

@ -17,6 +17,7 @@
package im.vector.riotx.features.home.room.list
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
sealed class RoomListActions {
data class SelectRoom(val roomSummary: RoomSummary) : RoomListActions()
@ -24,5 +25,7 @@ sealed class RoomListActions {
data class AcceptInvitation(val roomSummary: RoomSummary) : RoomListActions()
data class RejectInvitation(val roomSummary: RoomSummary) : RoomListActions()
data class FilterWith(val filter: String) : RoomListActions()
data class ChangeRoomNotificationState(val roomId: String, val notificationState: RoomNotificationState) : RoomListActions()
data class LeaveRoom(val roomId: String) : RoomListActions()
object MarkAllRoomsRead : RoomListActions()
}

View file

@ -21,8 +21,10 @@ import android.os.Parcelable
import android.view.Menu
import android.view.MenuItem
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.airbnb.mvrx.*
@ -30,17 +32,20 @@ import com.google.android.material.snackbar.Snackbar
import im.vector.matrix.android.api.failure.Failure
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.notification.RoomNotificationState
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.extensions.observeEventFirstThrottle
import im.vector.riotx.core.platform.OnBackPressed
import im.vector.riotx.core.platform.StateView
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActions
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsStore
import im.vector.riotx.features.home.room.list.widget.FabMenuView
import im.vector.riotx.features.notifications.NotificationDrawerManager
import im.vector.riotx.features.share.SharedData
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_list.*
import javax.inject.Inject
@ -67,6 +72,7 @@ class RoomListFragment @Inject constructor(
SHARE(/* Not used */ 0)
}
private lateinit var quickActionsDispatcher: RoomListQuickActionsStore
private val roomListParams: RoomListParams by args()
private val roomListViewModel: RoomListViewModel by fragmentViewModel()
@ -94,25 +100,43 @@ class RoomListFragment @Inject constructor(
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
quickActionsDispatcher = ViewModelProviders.of(requireActivity()).get(RoomListQuickActionsStore::class.java)
setupCreateRoomButton()
setupRecyclerView()
roomListViewModel.subscribe { renderState(it) }
roomListViewModel.openRoomLiveData.observeEventFirstThrottle(this, 800L) {
if (roomListParams.displayMode == DisplayMode.SHARE) {
val sharedData = roomListParams.sharedData ?: return@observeEventFirstThrottle
navigator.openRoomForSharing(requireActivity(), it, sharedData)
} else {
navigator.openRoom(requireActivity(), it)
}
}
roomListViewModel.viewEvents
.observe()
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
when (it) {
is RoomListViewEvents.SelectRoom -> openSelectedRoom(it)
is RoomListViewEvents.Failure -> showError(it)
}
}
.disposeOnDestroy()
createChatFabMenu.listener = this
roomListViewModel.invitationAnswerErrorLiveData.observeEvent(this) { throwable ->
vectorBaseActivity.coordinatorLayout?.let {
Snackbar.make(it, errorFormatter.toHumanReadable(throwable), Snackbar.LENGTH_SHORT)
.show()
}
quickActionsDispatcher
.observe()
.subscribe { handleQuickActions(it) }
.disposeOnDestroy()
}
private fun openSelectedRoom(event: RoomListViewEvents.SelectRoom) {
if (roomListParams.displayMode == DisplayMode.SHARE) {
val sharedData = roomListParams.sharedData ?: return
navigator.openRoomForSharing(requireActivity(), event.roomId, sharedData)
} else {
navigator.openRoom(requireActivity(), event.roomId)
}
}
private fun showError(event: RoomListViewEvents.Failure) {
vectorBaseActivity.coordinatorLayout?.let {
Snackbar.make(it, errorFormatter.toHumanReadable(event.throwable), Snackbar.LENGTH_SHORT)
.show()
}
}
@ -192,6 +216,36 @@ class RoomListFragment @Inject constructor(
}
}
private fun handleQuickActions(quickActions: RoomListQuickActions) {
when (quickActions) {
is RoomListQuickActions.NotificationsAllNoisy -> {
roomListViewModel.accept(RoomListActions.ChangeRoomNotificationState(quickActions.roomId, RoomNotificationState.ALL_MESSAGES_NOISY))
}
is RoomListQuickActions.NotificationsAll -> {
roomListViewModel.accept(RoomListActions.ChangeRoomNotificationState(quickActions.roomId, RoomNotificationState.ALL_MESSAGES))
}
is RoomListQuickActions.NotificationsMentionsOnly -> {
roomListViewModel.accept(RoomListActions.ChangeRoomNotificationState(quickActions.roomId, RoomNotificationState.MENTIONS_ONLY))
}
is RoomListQuickActions.NotificationsMute -> {
roomListViewModel.accept(RoomListActions.ChangeRoomNotificationState(quickActions.roomId, RoomNotificationState.MUTE))
}
is RoomListQuickActions.Settings -> {
vectorBaseActivity.notImplemented("Opening room settings")
}
is RoomListQuickActions.Leave -> {
AlertDialog.Builder(requireContext())
.setTitle(R.string.room_participants_leave_prompt_title)
.setMessage(R.string.room_participants_leave_prompt_msg)
.setPositiveButton(R.string.leave) { _, _ ->
roomListViewModel.accept(RoomListActions.LeaveRoom(quickActions.roomId))
}
.setNegativeButton(R.string.cancel, null)
.show()
}
}
}
private fun renderState(state: RoomListViewState) {
when (state.asyncFilteredRooms) {
is Incomplete -> renderLoading()
@ -287,10 +341,17 @@ class RoomListFragment @Inject constructor(
// RoomSummaryController.Callback **************************************************************
override fun onRoomSelected(room: RoomSummary) {
override fun onRoomClicked(room: RoomSummary) {
roomListViewModel.accept(RoomListActions.SelectRoom(room))
}
override fun onRoomLongClicked(room: RoomSummary): Boolean {
RoomListQuickActionsBottomSheet
.newInstance(room.roomId)
.show(requireActivity().supportFragmentManager, "ROOM_LIST_QUICK_ACTIONS")
return true
}
override fun onAcceptRoomInvitation(room: RoomSummary) {
notificationDrawerManager.clearMemberShipNotificationForRoom(room.roomId)
roomListViewModel.accept(RoomListActions.AcceptInvitation(room))

View file

@ -0,0 +1,26 @@
/*
* 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.list
/**
* Transient events for RoomList
*/
sealed class RoomListViewEvents {
data class Failure(val throwable: Throwable) : RoomListViewEvents()
data class SelectRoom(val roomId: String) : RoomListViewEvents()
}

View file

@ -16,8 +16,6 @@
package im.vector.riotx.features.home.room.list
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
@ -26,17 +24,16 @@ import im.vector.matrix.android.api.session.Session
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.tag.RoomTag
import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.utils.LiveEvent
import im.vector.riotx.core.utils.RxStore
import im.vector.riotx.core.utils.DataSource
import im.vector.riotx.core.utils.PublishDataSource
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
private val session: Session,
private val roomSummariesStore: RxStore<List<RoomSummary>>,
private val roomSummariesSource: DataSource<List<RoomSummary>>,
private val alphabeticalRoomComparator: AlphabeticalRoomComparator,
private val chronologicalRoomComparator: ChronologicalRoomComparator)
: VectorViewModel<RoomListViewState>(initialState) {
@ -57,13 +54,8 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
private val displayMode = initialState.displayMode
private val roomListDisplayModeFilter = RoomListDisplayModeFilter(displayMode)
private val _openRoomLiveData = MutableLiveData<LiveEvent<String>>()
val openRoomLiveData: LiveData<LiveEvent<String>>
get() = _openRoomLiveData
private val _invitationAnswerErrorLiveData = MutableLiveData<LiveEvent<Throwable>>()
val invitationAnswerErrorLiveData: LiveData<LiveEvent<Throwable>>
get() = _invitationAnswerErrorLiveData
private val _viewEvents = PublishDataSource<RoomListViewEvents>()
val viewEvents: DataSource<RoomListViewEvents> = _viewEvents
init {
observeRoomSummaries()
@ -71,19 +63,21 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
fun accept(action: RoomListActions) {
when (action) {
is RoomListActions.SelectRoom -> handleSelectRoom(action)
is RoomListActions.ToggleCategory -> handleToggleCategory(action)
is RoomListActions.AcceptInvitation -> handleAcceptInvitation(action)
is RoomListActions.RejectInvitation -> handleRejectInvitation(action)
is RoomListActions.FilterWith -> handleFilter(action)
is RoomListActions.MarkAllRoomsRead -> handleMarkAllRoomsRead()
is RoomListActions.SelectRoom -> handleSelectRoom(action)
is RoomListActions.ToggleCategory -> handleToggleCategory(action)
is RoomListActions.AcceptInvitation -> handleAcceptInvitation(action)
is RoomListActions.RejectInvitation -> handleRejectInvitation(action)
is RoomListActions.FilterWith -> handleFilter(action)
is RoomListActions.MarkAllRoomsRead -> handleMarkAllRoomsRead()
is RoomListActions.LeaveRoom -> handleLeaveRoom(action)
is RoomListActions.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
}
}
// PRIVATE METHODS *****************************************************************************
private fun handleSelectRoom(action: RoomListActions.SelectRoom) {
_openRoomLiveData.postLiveEvent(action.roomSummary.roomId)
_viewEvents.post(RoomListViewEvents.SelectRoom(action.roomSummary.roomId))
}
private fun handleToggleCategory(action: RoomListActions.ToggleCategory) = setState {
@ -99,7 +93,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
}
private fun observeRoomSummaries() {
roomSummariesStore
roomSummariesSource
.observe()
.observeOn(Schedulers.computation())
.map {
@ -109,7 +103,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
copy(asyncRooms = asyncRooms)
}
roomSummariesStore
roomSummariesSource
.observe()
.observeOn(Schedulers.computation())
.map { buildRoomSummaries(it) }
@ -142,8 +136,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
override fun onFailure(failure: Throwable) {
// Notify the user
_invitationAnswerErrorLiveData.postLiveEvent(failure)
_viewEvents.post(RoomListViewEvents.Failure(failure))
setState {
copy(
joiningRoomsIds = joiningRoomsIds - roomId,
@ -180,8 +173,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
override fun onFailure(failure: Throwable) {
// Notify the user
_invitationAnswerErrorLiveData.postLiveEvent(failure)
_viewEvents.post(RoomListViewEvents.Failure(failure))
setState {
copy(
rejectingRoomsIds = rejectingRoomsIds - roomId,
@ -201,6 +193,22 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
?.let { session.markAllAsRead(it, object : MatrixCallback<Unit> {}) }
}
private fun handleChangeNotificationMode(action: RoomListActions.ChangeRoomNotificationState) {
session.getRoom(action.roomId)?.setRoomNotificationState(action.notificationState, object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) {
_viewEvents.post(RoomListViewEvents.Failure(failure))
}
})
}
private fun handleLeaveRoom(action: RoomListActions.LeaveRoom) {
session.getRoom(action.roomId)?.leave(object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) {
_viewEvents.post(RoomListViewEvents.Failure(failure))
}
})
}
private fun buildRoomSummaries(rooms: List<RoomSummary>): RoomSummaries {
val invites = ArrayList<RoomSummary>()
val favourites = ArrayList<RoomSummary>()

View file

@ -17,14 +17,14 @@
package im.vector.riotx.features.home.room.list
import im.vector.matrix.android.api.session.Session
import im.vector.riotx.features.home.HomeRoomListObservableStore
import im.vector.riotx.features.share.ShareRoomListObservableStore
import im.vector.riotx.features.home.HomeRoomListDataSource
import im.vector.riotx.features.share.ShareRoomListDataSource
import javax.inject.Inject
import javax.inject.Provider
class RoomListViewModelFactory @Inject constructor(private val session: Provider<Session>,
private val homeRoomListObservableStore: Provider<HomeRoomListObservableStore>,
private val shareRoomListObservableStore: Provider<ShareRoomListObservableStore>,
private val homeRoomListDataSource: Provider<HomeRoomListDataSource>,
private val shareRoomListDataSource: Provider<ShareRoomListDataSource>,
private val alphabeticalRoomComparator: Provider<AlphabeticalRoomComparator>,
private val chronologicalRoomComparator: Provider<ChronologicalRoomComparator>) : RoomListViewModel.Factory {
@ -32,7 +32,7 @@ class RoomListViewModelFactory @Inject constructor(private val session: Provider
return RoomListViewModel(
initialState,
session.get(),
if (initialState.displayMode == RoomListFragment.DisplayMode.SHARE) shareRoomListObservableStore.get() else homeRoomListObservableStore.get(),
if (initialState.displayMode == RoomListFragment.DisplayMode.SHARE) shareRoomListDataSource.get() else homeRoomListDataSource.get(),
alphabeticalRoomComparator.get(),
chronologicalRoomComparator.get())
}

View file

@ -154,7 +154,8 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
interface Listener : FilteredRoomFooterItem.FilteredRoomFooterItemListener {
fun onToggleRoomCategory(roomCategory: RoomCategory)
fun onRoomSelected(room: RoomSummary)
fun onRoomClicked(room: RoomSummary)
fun onRoomLongClicked(room: RoomSummary): Boolean
fun onRejectRoomInvitation(room: RoomSummary)
fun onAcceptRoomInvitation(room: RoomSummary)
}

View file

@ -41,11 +41,13 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
@EpoxyAttribute var hasUnreadMessage: Boolean = false
@EpoxyAttribute var hasDraft: Boolean = false
@EpoxyAttribute var showHighlighted: Boolean = false
@EpoxyAttribute var listener: (() -> Unit)? = null
@EpoxyAttribute var itemLongClickListener: View.OnLongClickListener? = null
@EpoxyAttribute var itemClickListener: View.OnClickListener? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.rootView.setOnClickListener { listener?.invoke() }
holder.rootView.setOnClickListener(itemClickListener)
holder.rootView.setOnLongClickListener(itemLongClickListener)
holder.titleView.text = roomName
holder.lastEventTimeView.text = lastEventTime
holder.lastEventView.text = lastFormattedEvent

View file

@ -16,6 +16,7 @@
package im.vector.riotx.features.home.room.list
import android.view.View
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.Membership
@ -28,6 +29,7 @@ import im.vector.riotx.core.extensions.localDateTime
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.resources.DateProvider
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.utils.DebouncedClickListener
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter
import me.gujun.android.span.span
@ -78,7 +80,7 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
.rejectListener { listener?.onRejectRoomInvitation(roomSummary) }
.roomName(roomSummary.displayName)
.avatarUrl(roomSummary.avatarUrl)
.listener { listener?.onRoomSelected(roomSummary) }
.listener { listener?.onRoomClicked(roomSummary) }
}
private fun createRoomItem(roomSummary: RoomSummary, listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
@ -133,6 +135,13 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
.unreadNotificationCount(unreadCount)
.hasUnreadMessage(roomSummary.hasUnreadMessages)
.hasDraft(roomSummary.userDrafts.isNotEmpty())
.listener { listener?.onRoomSelected(roomSummary) }
.itemLongClickListener { _ ->
listener?.onRoomLongClicked(roomSummary) ?: false
}
.itemClickListener(
DebouncedClickListener(View.OnClickListener { _ ->
listener?.onRoomClicked(roomSummary)
})
)
}
}

View file

@ -0,0 +1,55 @@
/*
* 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.list.actions
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import im.vector.riotx.R
sealed class RoomListQuickActions(@StringRes val titleRes: Int, @DrawableRes val iconResId: Int, val destructive: Boolean = false) {
data class NotificationsAllNoisy(val roomId: String) : RoomListQuickActions(
R.string.room_list_quick_actions_notifications_all_noisy,
R.drawable.ic_room_actions_notifications_all_noisy
)
data class NotificationsAll(val roomId: String) : RoomListQuickActions(
R.string.room_list_quick_actions_notifications_all,
R.drawable.ic_room_actions_notifications_all
)
data class NotificationsMentionsOnly(val roomId: String) : RoomListQuickActions(
R.string.room_list_quick_actions_notifications_mentions,
R.drawable.ic_room_actions_notifications_mentions
)
data class NotificationsMute(val roomId: String) : RoomListQuickActions(
R.string.room_list_quick_actions_notifications_mute,
R.drawable.ic_room_actions_notifications_mutes
)
data class Settings(val roomId: String) : RoomListQuickActions(
R.string.room_list_quick_actions_settings,
R.drawable.ic_room_actions_settings
)
data class Leave(val roomId: String) : RoomListQuickActions(
R.string.room_list_quick_actions_leave,
R.drawable.ic_room_actions_leave,
true
)
}

View file

@ -0,0 +1,97 @@
/*
* 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.list.actions
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import butterknife.ButterKnife
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.riotx.features.navigation.Navigator
import kotlinx.android.parcel.Parcelize
import javax.inject.Inject
@Parcelize
data class RoomListActionsArgs(
val roomId: String
) : Parcelable
/**
* Bottom sheet fragment that shows room information with list of contextual actions
*/
class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), RoomListQuickActionsEpoxyController.Listener {
private lateinit var actionsDispatcher: RoomListQuickActionsStore
@Inject lateinit var roomListActionsViewModelFactory: RoomListQuickActionsViewModel.Factory
@Inject lateinit var roomListActionsEpoxyController: RoomListQuickActionsEpoxyController
@Inject lateinit var navigator: Navigator
private val viewModel: RoomListQuickActionsViewModel by fragmentViewModel(RoomListQuickActionsViewModel::class)
@BindView(R.id.bottomSheetRecyclerView)
lateinit var recyclerView: RecyclerView
override val showExpanded = true
override fun injectWith(screenComponent: ScreenComponent) {
screenComponent.inject(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.bottom_sheet_generic_list, container, false)
ButterKnife.bind(this, view)
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
actionsDispatcher = ViewModelProviders.of(requireActivity()).get(RoomListQuickActionsStore::class.java)
recyclerView.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
recyclerView.adapter = roomListActionsEpoxyController.adapter
// Disable item animation
recyclerView.itemAnimator = null
roomListActionsEpoxyController.listener = this
}
override fun invalidate() = withState(viewModel) {
roomListActionsEpoxyController.setData(it)
super.invalidate()
}
override fun didSelectMenuAction(quickActions: RoomListQuickActions) {
actionsDispatcher.post(quickActions)
dismiss()
}
companion object {
fun newInstance(roomId: String): RoomListQuickActionsBottomSheet {
return RoomListQuickActionsBottomSheet().apply {
setArguments(RoomListActionsArgs(roomId))
}
}
}
}

View file

@ -0,0 +1,88 @@
/*
* 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.list.actions
import android.view.View
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetItemAction
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetItemRoomPreview
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetItemSeparator
import im.vector.riotx.features.home.AvatarRenderer
import javax.inject.Inject
/**
* Epoxy controller for room list actions
*/
class RoomListQuickActionsEpoxyController @Inject constructor(private val avatarRenderer: AvatarRenderer)
: TypedEpoxyController<RoomListQuickActionsState>() {
var listener: Listener? = null
override fun buildModels(state: RoomListQuickActionsState) {
val roomSummary = state.roomSummary() ?: return
// Preview
bottomSheetItemRoomPreview {
id("preview")
avatarRenderer(avatarRenderer)
roomName(roomSummary.displayName)
avatarUrl(roomSummary.avatarUrl)
roomId(roomSummary.roomId)
settingsClickListener(View.OnClickListener { listener?.didSelectMenuAction(RoomListQuickActions.Settings(roomSummary.roomId)) })
}
// Notifications
bottomSheetItemSeparator {
id("notifications_separator")
}
val selectedRoomState = state.roomNotificationState()
RoomListQuickActions.NotificationsAllNoisy(roomSummary.roomId).toBottomSheetItem(0, selectedRoomState)
RoomListQuickActions.NotificationsAll(roomSummary.roomId).toBottomSheetItem(1, selectedRoomState)
RoomListQuickActions.NotificationsMentionsOnly(roomSummary.roomId).toBottomSheetItem(2, selectedRoomState)
RoomListQuickActions.NotificationsMute(roomSummary.roomId).toBottomSheetItem(3, selectedRoomState)
// Leave
bottomSheetItemSeparator {
id("leave_separator")
}
RoomListQuickActions.Leave(roomSummary.roomId).toBottomSheetItem(5)
}
private fun RoomListQuickActions.toBottomSheetItem(index: Int, roomNotificationState: RoomNotificationState? = null) {
val selected = when (this) {
is RoomListQuickActions.NotificationsAllNoisy -> roomNotificationState == RoomNotificationState.ALL_MESSAGES_NOISY
is RoomListQuickActions.NotificationsAll -> roomNotificationState == RoomNotificationState.ALL_MESSAGES
is RoomListQuickActions.NotificationsMentionsOnly -> roomNotificationState == RoomNotificationState.MENTIONS_ONLY
is RoomListQuickActions.NotificationsMute -> roomNotificationState == RoomNotificationState.MUTE
is RoomListQuickActions.Settings,
is RoomListQuickActions.Leave -> false
}
return bottomSheetItemAction {
id("action_$index")
selected(selected)
iconRes(iconResId)
textRes(titleRes)
destructive(this@toBottomSheetItem.destructive)
listener(View.OnClickListener { listener?.didSelectMenuAction(this@toBottomSheetItem) })
}
}
interface Listener {
fun didSelectMenuAction(quickActions: RoomListQuickActions)
}
}

View file

@ -0,0 +1,32 @@
/*
* 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.list.actions
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
data class RoomListQuickActionsState(
val roomId: String,
val roomSummary: Async<RoomSummary> = Uninitialized,
val roomNotificationState: Async<RoomNotificationState> = Uninitialized
) : MvRxState {
constructor(args: RoomListActionsArgs) : this(roomId = args.roomId)
}

View file

@ -0,0 +1,28 @@
/*
* 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.list.actions
import androidx.lifecycle.ViewModel
import im.vector.riotx.core.utils.PublishDataSource
import im.vector.riotx.core.utils.MutableDataSource
/**
* Activity shared view model to handle room list quick actions
*/
class RoomListQuickActionsStore constructor(
private val store: MutableDataSource<RoomListQuickActions> = PublishDataSource()
) : ViewModel(), MutableDataSource<RoomListQuickActions> by store

View file

@ -0,0 +1,68 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.home.room.list.actions
import com.airbnb.mvrx.*
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.rx.rx
import im.vector.matrix.rx.unwrap
import im.vector.riotx.core.platform.VectorViewModel
class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initialState: RoomListQuickActionsState,
session: Session
) : VectorViewModel<RoomListQuickActionsState>(initialState) {
@AssistedInject.Factory
interface Factory {
fun create(initialState: RoomListQuickActionsState): RoomListQuickActionsViewModel
}
companion object : MvRxViewModelFactory<RoomListQuickActionsViewModel, RoomListQuickActionsState> {
override fun create(viewModelContext: ViewModelContext, state: RoomListQuickActionsState): RoomListQuickActionsViewModel? {
val fragment: RoomListQuickActionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.roomListActionsViewModelFactory.create(state)
}
}
private val room = session.getRoom(initialState.roomId)!!
init {
observeRoomSummary()
observeNotificationState()
}
private fun observeNotificationState() {
room
.rx()
.liveNotificationState()
.execute {
copy(roomNotificationState = it)
}
}
private fun observeRoomSummary() {
room
.rx()
.liveRoomSummary()
.unwrap()
.execute {
copy(roomSummary = it)
}
}
}

View file

@ -112,4 +112,8 @@ class DefaultNavigator @Inject constructor() : Navigator {
override fun openUserDetail(userId: String, context: Context) {
Timber.v("Open user detail $userId")
}
override fun openRoomSettings(context: Context, roomId: String) {
Timber.v("Open room settings$roomId")
}
}

View file

@ -50,4 +50,6 @@ interface Navigator {
fun openGroupDetail(groupId: String, context: Context)
fun openUserDetail(userId: String, context: Context)
fun openRoomSettings(context: Context, roomId: String)
}

View file

@ -71,7 +71,7 @@ class PublicRoomsFragment @Inject constructor(
.disposeOnDestroy()
publicRoomsCreateNewRoom.setOnClickListener {
navigationViewModel.goTo(RoomDirectoryActivity.Navigation.CreateRoom)
navigationViewModel.post(RoomDirectoryActivity.Navigation.CreateRoom)
}
viewModel.joinRoomErrorLiveData.observeEvent(this) { throwable ->
@ -83,7 +83,7 @@ class PublicRoomsFragment @Inject constructor(
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.menu_room_directory_change_protocol -> {
navigationViewModel.goTo(RoomDirectoryActivity.Navigation.ChangeProtocol)
navigationViewModel.post(RoomDirectoryActivity.Navigation.ChangeProtocol)
true
}
else ->

View file

@ -25,7 +25,6 @@ import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.extensions.addFragment
import im.vector.riotx.core.extensions.addFragmentToBackstack
import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomViewModel
@ -62,14 +61,16 @@ class RoomDirectoryActivity : VectorBaseActivity() {
roomDirectoryViewModel.filterWith(intent?.getStringExtra(INITIAL_FILTER) ?: "")
}
navigationViewModel.navigateTo.observeEvent(this) { navigation ->
when (navigation) {
is Navigation.Back -> onBackPressed()
is Navigation.CreateRoom -> addFragmentToBackstack(R.id.simpleFragmentContainer, CreateRoomFragment::class.java)
is Navigation.ChangeProtocol -> addFragmentToBackstack(R.id.simpleFragmentContainer, RoomDirectoryPickerFragment::class.java)
is Navigation.Close -> finish()
}
}
navigationViewModel.observe()
.subscribe { navigation ->
when (navigation) {
is Navigation.Back -> onBackPressed()
is Navigation.CreateRoom -> addFragmentToBackstack(R.id.simpleFragmentContainer, CreateRoomFragment::class.java)
is Navigation.ChangeProtocol -> addFragmentToBackstack(R.id.simpleFragmentContainer, RoomDirectoryPickerFragment::class.java)
is Navigation.Close -> finish()
}
}
.disposeOnDestroy()
roomDirectoryViewModel.selectSubscribe(this, PublicRoomsViewState::currentFilter) { currentFilter ->
// Transmit the filter to the createRoomViewModel

View file

@ -16,7 +16,9 @@
package im.vector.riotx.features.roomdirectory
import im.vector.riotx.core.mvrx.NavigationViewModel
import javax.inject.Inject
import androidx.lifecycle.ViewModel
import im.vector.riotx.core.utils.PublishDataSource
import im.vector.riotx.core.utils.MutableDataSource
class RoomDirectoryNavigationViewModel @Inject constructor(): NavigationViewModel<RoomDirectoryActivity.Navigation>()
class RoomDirectoryNavigationViewModel(private val source: MutableDataSource<RoomDirectoryActivity.Navigation> = PublishDataSource())
: ViewModel(), MutableDataSource<RoomDirectoryActivity.Navigation> by source

View file

@ -24,7 +24,6 @@ import androidx.lifecycle.ViewModelProviders
import com.airbnb.mvrx.viewModel
import im.vector.riotx.R
import im.vector.riotx.core.extensions.addFragment
import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.platform.ToolbarConfigurable
import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity
@ -57,12 +56,14 @@ class CreateRoomActivity : VectorBaseActivity(), ToolbarConfigurable {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
navigationViewModel = ViewModelProviders.of(this, viewModelFactory).get(RoomDirectoryNavigationViewModel::class.java)
navigationViewModel.navigateTo.observeEvent(this) { navigation ->
when (navigation) {
is RoomDirectoryActivity.Navigation.Back,
is RoomDirectoryActivity.Navigation.Close -> finish()
}
}
navigationViewModel.observe()
.subscribe { navigation ->
when (navigation) {
is RoomDirectoryActivity.Navigation.Back,
is RoomDirectoryActivity.Navigation.Close -> finish()
}
}
.disposeOnDestroy()
}
companion object {

View file

@ -46,7 +46,7 @@ class CreateRoomFragment @Inject constructor(private val createRoomController: C
navigationViewModel = ViewModelProviders.of(requireActivity()).get(RoomDirectoryNavigationViewModel::class.java)
setupRecyclerView()
createRoomClose.setOnClickListener {
navigationViewModel.goTo(RoomDirectoryActivity.Navigation.Back)
navigationViewModel.post(RoomDirectoryActivity.Navigation.Back)
}
}
@ -93,7 +93,7 @@ class CreateRoomFragment @Inject constructor(private val createRoomController: C
// Navigate to freshly created room
navigator.openRoom(requireActivity(), async())
navigationViewModel.goTo(RoomDirectoryActivity.Navigation.Close)
navigationViewModel.post(RoomDirectoryActivity.Navigation.Close)
} else {
// Populate list with Epoxy
createRoomController.setData(state)

View file

@ -88,7 +88,7 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie
Timber.v("onRoomDirectoryClicked: $roomDirectoryData")
viewModel.setRoomDirectoryData(roomDirectoryData)
navigationViewModel.goTo(RoomDirectoryActivity.Navigation.Back)
navigationViewModel.post(RoomDirectoryActivity.Navigation.Back)
}
override fun retry() {

View file

@ -23,7 +23,7 @@ import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.rx.rx
import im.vector.riotx.ActiveSessionObservableStore
import im.vector.riotx.ActiveSessionDataSource
import im.vector.riotx.core.platform.VectorViewModel
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
@ -35,8 +35,8 @@ data class IncomingShareState(private val dummy: Boolean = false) : MvRxState
* View model used to observe the room list and post update to the ShareRoomListObservableStore
*/
class IncomingShareViewModel @AssistedInject constructor(@Assisted initialState: IncomingShareState,
private val sessionObservableStore: ActiveSessionObservableStore,
private val shareRoomListObservableStore: ShareRoomListObservableStore)
private val sessionObservableStore: ActiveSessionDataSource,
private val shareRoomListObservableStore: ShareRoomListDataSource)
: VectorViewModel<IncomingShareState>(initialState) {
@AssistedInject.Factory

View file

@ -17,9 +17,9 @@
package im.vector.riotx.features.share
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotx.core.utils.RxStore
import im.vector.riotx.core.utils.BehaviorDataSource
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ShareRoomListObservableStore @Inject constructor() : RxStore<List<RoomSummary>>()
class ShareRoomListDataSource @Inject constructor() : BehaviorDataSource<List<RoomSummary>>()

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24.0" android:viewportWidth="24.0" android:width="24dp">
<path android:fillColor="#FFFFFF" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>

View file

@ -0,0 +1,47 @@
<!--
~ 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.
~
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="22dp"
android:viewportWidth="22"
android:viewportHeight="22">
<path
android:pathData="M11,11m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M14,8L8,14"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M8,8L14,14"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,39 @@
<!--
~ 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.
~
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="17dp"
android:height="16dp"
android:viewportWidth="17"
android:viewportHeight="16">
<path
android:pathData="M10,1l-5,4l-4,0l0,6l4,0l5,4z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M14.54,4.46C16.4919,6.4125 16.4919,9.5775 14.54,11.53"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,39 @@
<!--
~ 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.
~
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="18dp"
android:viewportWidth="22"
android:viewportHeight="18">
<path
android:pathData="M10,2l-5,4l-4,0l0,6l4,0l5,4z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M18.07,1.93C21.9738,5.835 21.9738,12.165 18.07,16.07M14.54,5.46C16.4919,7.4125 16.4919,10.5775 14.54,12.53"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,47 @@
<!--
~ 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.
~
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="16dp"
android:viewportWidth="24"
android:viewportHeight="16">
<path
android:pathData="M10,1l-5,4l-4,0l0,6l4,0l5,4z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M18.8097,7.8097m-1.5238,0a1.5238,1.5238 0,1 1,3.0476 0a1.5238,1.5238 0,1 1,-3.0476 0"
android:strokeLineJoin="round"
android:strokeWidth="1.3"
android:fillColor="#00000000"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M20.3336,6.2858L20.3336,8.1906C20.3336,8.8218 20.8452,9.3335 21.4764,9.3335C22.1076,9.3335 22.6193,8.8218 22.6193,8.1906L22.6193,7.8097C22.6192,6.0393 21.3995,4.5024 19.6755,4.1001C17.9515,3.6977 16.1776,4.5361 15.3938,6.1235C14.6101,7.7109 15.0233,9.629 16.3909,10.753C17.7586,11.877 19.7204,11.9108 21.1259,10.8344"
android:strokeLineJoin="round"
android:strokeWidth="1.3"
android:fillColor="#00000000"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,47 @@
<!--
~ 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.
~
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="23dp"
android:height="16dp"
android:viewportWidth="23"
android:viewportHeight="16">
<path
android:pathData="M10,1l-5,4l-4,0l0,6l4,0l5,4z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M22,5L16,11"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M16,5L22,11"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,39 @@
<!--
~ 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.
~
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,12m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M19.4,15C19.1277,15.6171 19.2583,16.3378 19.73,16.82L19.79,16.88C20.1656,17.2551 20.3766,17.7642 20.3766,18.295C20.3766,18.8258 20.1656,19.3349 19.79,19.71C19.4149,20.0856 18.9058,20.2966 18.375,20.2966C17.8442,20.2966 17.3351,20.0856 16.96,19.71L16.9,19.65C16.4178,19.1783 15.6971,19.0477 15.08,19.32C14.4755,19.5791 14.0826,20.1724 14.08,20.83L14.08,21C14.08,22.1046 13.1846,23 12.08,23C10.9754,23 10.08,22.1046 10.08,21L10.08,20.91C10.0642,20.2327 9.6359,19.6339 9,19.4C8.3829,19.1277 7.6622,19.2583 7.18,19.73L7.12,19.79C6.7449,20.1656 6.2358,20.3766 5.705,20.3766C5.1742,20.3766 4.6651,20.1656 4.29,19.79C3.9144,19.4149 3.7034,18.9058 3.7034,18.375C3.7034,17.8442 3.9144,17.3351 4.29,16.96L4.35,16.9C4.8217,16.4178 4.9523,15.6971 4.68,15.08C4.4209,14.4755 3.8276,14.0826 3.17,14.08L3,14.08C1.8954,14.08 1,13.1846 1,12.08C1,10.9754 1.8954,10.08 3,10.08L3.09,10.08C3.7673,10.0642 4.3661,9.6359 4.6,9C4.8723,8.3829 4.7417,7.6622 4.27,7.18L4.21,7.12C3.8344,6.7449 3.6234,6.2358 3.6234,5.705C3.6234,5.1742 3.8344,4.6651 4.21,4.29C4.5851,3.9144 5.0942,3.7034 5.625,3.7034C6.1558,3.7034 6.6649,3.9144 7.04,4.29L7.1,4.35C7.5822,4.8217 8.3029,4.9523 8.92,4.68L9,4.68C9.6045,4.4209 9.9974,3.8276 10,3.17L10,3C10,1.8954 10.8954,1 12,1C13.1046,1 14,1.8954 14,3L14,3.09C14.0026,3.7476 14.3955,4.3409 15,4.6C15.6171,4.8723 16.3378,4.7417 16.82,4.27L16.88,4.21C17.2551,3.8344 17.7642,3.6234 18.295,3.6234C18.8258,3.6234 19.3349,3.8344 19.71,4.21C20.0856,4.5851 20.2966,5.0942 20.2966,5.625C20.2966,6.1558 20.0856,6.6649 19.71,7.04L19.65,7.1C19.1783,7.5822 19.0477,8.3029 19.32,8.92L19.32,9C19.5791,9.6045 20.1724,9.9974 20.83,10L21,10C22.1046,10 23,10.8954 23,12C23,13.1046 22.1046,14 21,14L20.91,14C20.2524,14.0026 19.6591,14.3955 19.4,15Z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#9E9E9E"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
</vector>

View file

@ -1,52 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:minHeight="50dp"
android:orientation="horizontal"
android:paddingLeft="@dimen/layout_horizontal_margin"
android:paddingTop="8dp"
android:paddingRight="@dimen/layout_horizontal_margin"
android:paddingBottom="8dp">
<Space
android:id="@+id/action_start_space"
android:id="@+id/actionStartSpace"
android:layout_width="32dp"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<ImageView
android:id="@+id/action_icon"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:id="@+id/actionIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/actionStartSpace"
android:scaleType="center"
android:tint="?riotx_text_secondary"
tools:src="@drawable/ic_delete" />
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/actionStartSpace"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_room_actions_notifications_all" />
<TextView
android:id="@+id/action_title"
android:layout_width="wrap_content"
android:id="@+id/actionTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="40dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:drawablePadding="16dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="?riotx_text_secondary"
android:textSize="17sp"
tools:text="@string/delete" />
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/actionSelected"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/actionStartSpace"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="wrap"
tools:text="zbla azjazjaz s sdkqdskdsqk kqsdkdqsk kdqsksqdk" />
<ImageView
android:id="@+id/action_expand"
android:id="@+id/actionSelected"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="?riotx_text_secondary"
android:src="@drawable/ic_check_white_24dp"
android:tint="@color/riotx_accent"
android:visibility="gone"
tools:src="@drawable/ic_material_expand_more_black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/bottom_sheet_message_preview"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/bottomSheetRoomPreviewAvatar"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:adjustViewBounds="true"
android:background="@drawable/circle"
android:importantForAccessibility="no"
android:contentDescription="@string/avatar"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/bottomSheetRoomPreviewName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:ellipsize="end"
android:fontFamily="sans-serif-bold"
android:singleLine="true"
android:textColor="?riotx_text_primary"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/bottomSheetRoomPreviewAvatar"
app:layout_constraintEnd_toStartOf="@+id/bottomSheetRoomPreviewSettings"
app:layout_constraintStart_toEndOf="@id/bottomSheetRoomPreviewAvatar"
app:layout_constraintTop_toTopOf="@id/bottomSheetRoomPreviewAvatar"
tools:text="@tools:sample/full_names" />
<ImageView
android:id="@+id/bottomSheetRoomPreviewSettings"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackground"
android:scaleType="centerInside"
android:src="@drawable/ic_room_actions_settings"
android:contentDescription="@string/room_list_quick_actions_settings"
app:layout_constraintBottom_toBottomOf="@+id/bottomSheetRoomPreviewAvatar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/bottomSheetRoomPreviewAvatar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -2,6 +2,12 @@
<resources>
<!-- Strings not defined in Riot -->
<string name="room_list_quick_actions_notifications_all_noisy">"All messages (noisy)"</string>
<string name="room_list_quick_actions_notifications_all">"All messages"</string>
<string name="room_list_quick_actions_notifications_mentions">"Mentions only"</string>
<string name="room_list_quick_actions_notifications_mute">"Mute"</string>
<string name="room_list_quick_actions_settings">"Settings"</string>
<string name="room_list_quick_actions_leave">"Leave the room"</string>
<string name="notice_member_no_changes">"%1$s made no changes"</string>
<string name="no_ignored_users">You are not ignoring any users</string>