diff --git a/CHANGES.md b/CHANGES.md index 093d2c0b86..f7b8847bbd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ Changes in RiotX 0.8.0 (2019-XX-XX) =================================================== Features ✨: - - + - Ignore/UnIgnore users, and display list of ignored users (#542, #617) Improvements 🙌: - Search reaction by name or keyword in emoji picker diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt index f19777b6f5..1572851d3a 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt @@ -54,6 +54,10 @@ class RxSession(private val session: Session) { return session.liveUsers().asObservable() } + fun liveIgnoredUsers(): Observable<List<User>> { + return session.liveIgnoredUsers().asObservable() + } + fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> { return session.livePagedUsers(filter).asObservable() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/cache/CacheService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/cache/CacheService.kt index f1e4ca6c7b..a84e5af48c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/cache/CacheService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/cache/CacheService.kt @@ -19,7 +19,7 @@ package im.vector.matrix.android.api.session.cache import im.vector.matrix.android.api.MatrixCallback /** - * This interface defines a method to sign out. It's implemented at the session level. + * This interface defines a method to clear the cache. It's implemented at the session level. */ interface CacheService { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt index d3de777e34..2a93a876f6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt @@ -64,4 +64,19 @@ interface UserService { * @return a Livedata of users */ fun livePagedUsers(filter: String? = null): LiveData<PagedList<User>> + + /** + * Get list of ignored users + */ + fun liveIgnoredUsers(): LiveData<List<User>> + + /** + * Ignore users + */ + fun ignoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable + + /** + * Un-ignore some users + */ + fun unIgnoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/Types.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/Types.kt index bfb9a59956..f83166512e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/Types.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/Types.kt @@ -21,4 +21,6 @@ import java.lang.reflect.ParameterizedType typealias JsonDict = Map<String, @JvmSuppressWildcards Any> +val emptyJsonDict = emptyMap<String, Any>() + internal val JSON_DICT_PARAMETERIZED_TYPE: ParameterizedType = Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/IgnoredUserEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/IgnoredUserEntity.kt new file mode 100644 index 0000000000..bd31046f82 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/IgnoredUserEntity.kt @@ -0,0 +1,24 @@ +/* + * 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.database.model + +import io.realm.RealmObject + +internal open class IgnoredUserEntity(var userId: String = "") : RealmObject() { + + companion object +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt index 21b2fdce5a..76b355b064 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt @@ -35,6 +35,7 @@ import io.realm.annotations.RealmModule RoomTagEntity::class, SyncEntity::class, UserEntity::class, + IgnoredUserEntity::class, EventAnnotationsSummaryEntity::class, ReactionAggregatedSummaryEntity::class, EditAggregatedSummaryEntity::class, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt index d8db462f7c..96cdf29226 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt @@ -20,10 +20,11 @@ import com.squareup.moshi.Moshi import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.internal.network.parsing.RuntimeJsonAdapterFactory import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter -import im.vector.matrix.android.internal.session.sync.model.UserAccountData -import im.vector.matrix.android.internal.session.sync.model.UserAccountDataDirectMessages -import im.vector.matrix.android.internal.session.sync.model.UserAccountDataFallback -import im.vector.matrix.android.internal.session.sync.model.UserAccountDataPushRules +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataDirectMessages +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataFallback +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataIgnoredUsers +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataPushRules object MoshiProvider { @@ -31,6 +32,7 @@ object MoshiProvider { .add(UriMoshiAdapter()) .add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataFallback::class.java) .registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES) + .registerSubtype(UserAccountDataIgnoredUsers::class.java, UserAccountData.TYPE_IGNORED_USER_LIST) .registerSubtype(UserAccountDataPushRules::class.java, UserAccountData.TYPE_PUSH_RULES) ) .add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt index 56b96b428d..56bc005805 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt @@ -27,10 +27,9 @@ import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.session.pushers.SavePushRulesTask import im.vector.matrix.android.internal.session.room.membership.RoomMembers import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync -import im.vector.matrix.android.internal.session.sync.model.UserAccountDataDirectMessages -import im.vector.matrix.android.internal.session.sync.model.UserAccountDataPushRules -import im.vector.matrix.android.internal.session.sync.model.UserAccountDataSync +import im.vector.matrix.android.internal.session.sync.model.accountdata.* import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper +import im.vector.matrix.android.internal.session.user.accountdata.SaveIgnoredUsersTask import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith @@ -44,6 +43,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc private val directChatsHelper: DirectChatsHelper, private val updateUserAccountDataTask: UpdateUserAccountDataTask, private val savePushRulesTask: SavePushRulesTask, + private val saveIgnoredUsersTask: SaveIgnoredUsersTask, private val taskExecutor: TaskExecutor) { suspend fun handle(accountData: UserAccountDataSync?, invites: Map<String, InvitedRoomSync>?) { @@ -51,9 +51,18 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc when (it) { is UserAccountDataDirectMessages -> handleDirectChatRooms(it) is UserAccountDataPushRules -> handlePushRules(it) - else -> return@forEach + is UserAccountDataIgnoredUsers -> handleIgnoredUsers(it) + is UserAccountDataFallback -> Timber.d("Receive account data of unhandled type ${it.type}") + else -> error("Missing code here!") } } + + // TODO Store all account data, app can be interested of it + // accountData?.list?.forEach { + // it.toString() + // MoshiProvider.providesMoshi() + // } + monarchy.doWithRealm { realm -> synchronizeWithServerIfNeeded(realm, invites) } @@ -114,4 +123,11 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc updateUserAccountDataTask.configureWith(updateUserAccountParams).executeBy(taskExecutor) } } + + private fun handleIgnoredUsers(userAccountDataIgnoredUsers: UserAccountDataIgnoredUsers) { + saveIgnoredUsersTask + .configureWith(SaveIgnoredUsersTask.Params(userAccountDataIgnoredUsers.content.ignoredUsers.keys.toList())) + .executeBy(taskExecutor) + // TODO If not initial sync, we should execute a init sync + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/SyncResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/SyncResponse.kt index d084dcdadd..9e5cc2cfa4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/SyncResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/SyncResponse.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.sync.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataSync // SyncResponse represents the request response for server sync v2. @JsonClass(generateAdapter = true) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/IgnoredUsersContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/IgnoredUsersContent.kt new file mode 100644 index 0000000000..ea591d79b0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/IgnoredUsersContent.kt @@ -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.sync.model.accountdata + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.util.JsonDict +import im.vector.matrix.android.api.util.emptyJsonDict + +@JsonClass(generateAdapter = true) +internal data class IgnoredUsersContent( + /** + * Required. The map of users to ignore. UserId -> empty object for future enhancement + */ + @Json(name = "ignored_users") val ignoredUsers: Map<String, JsonDict> +) { + + companion object { + fun createWithUserIds(userIds: List<String>): IgnoredUsersContent { + return IgnoredUsersContent( + ignoredUsers = userIds.associateWith { emptyJsonDict } + ) + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/UserAccountData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountData.kt similarity index 81% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/UserAccountData.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountData.kt index 2173d2f4df..55dbad6099 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/UserAccountData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountData.kt @@ -14,9 +14,13 @@ * limitations under the License. */ -package im.vector.matrix.android.internal.session.sync.model +package im.vector.matrix.android.internal.session.sync.model.accountdata -internal interface UserAccountData { +import com.squareup.moshi.Json + +internal abstract class UserAccountData { + + @Json(name = "type") abstract val type: String companion object { const val TYPE_IGNORED_USER_LIST = "m.ignored_user_list" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/UserAccountDataDirectMessages.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataDirectMessages.kt similarity index 82% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/UserAccountDataDirectMessages.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataDirectMessages.kt index 825a16cb1e..e5c6135bd1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/UserAccountDataDirectMessages.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataDirectMessages.kt @@ -14,12 +14,13 @@ * limitations under the License. */ -package im.vector.matrix.android.internal.session.sync.model +package im.vector.matrix.android.internal.session.sync.model.accountdata import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) internal data class UserAccountDataDirectMessages( + @Json(name = "type") override val type: String = TYPE_DIRECT_MESSAGES, @Json(name = "content") val content: Map<String, List<String>> -) : UserAccountData +) : UserAccountData() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/UserAccountDataFallback.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataFallback.kt similarity index 84% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/UserAccountDataFallback.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataFallback.kt index 70d28c084f..a8b8235d37 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/UserAccountDataFallback.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataFallback.kt @@ -14,12 +14,13 @@ * limitations under the License. */ -package im.vector.matrix.android.internal.session.sync.model +package im.vector.matrix.android.internal.session.sync.model.accountdata import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) internal data class UserAccountDataFallback( + @Json(name = "type") override val type: String, @Json(name = "content") val content: Map<String, Any> -) : UserAccountData +) : UserAccountData() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataIgnoredUsers.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataIgnoredUsers.kt new file mode 100644 index 0000000000..63a7604305 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataIgnoredUsers.kt @@ -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.matrix.android.internal.session.sync.model.accountdata + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class UserAccountDataIgnoredUsers( + @Json(name = "type") override val type: String = TYPE_IGNORED_USER_LIST, + @Json(name = "content") val content: IgnoredUsersContent +) : UserAccountData() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/UserAccountDataPushRules.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataPushRules.kt similarity index 83% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/UserAccountDataPushRules.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataPushRules.kt index 7f357c876b..0d549d1667 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/UserAccountDataPushRules.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataPushRules.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.matrix.android.internal.session.sync.model +package im.vector.matrix.android.internal.session.sync.model.accountdata import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @@ -22,5 +22,6 @@ import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse @JsonClass(generateAdapter = true) internal data class UserAccountDataPushRules( + @Json(name = "type") override val type: String = TYPE_PUSH_RULES, @Json(name = "content") val content: GetPushRulesResponse -) : UserAccountData +) : UserAccountData() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/UserAccountDataSync.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataSync.kt similarity index 91% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/UserAccountDataSync.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataSync.kt index 4b9e9d652d..c7f8bfa4c2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/UserAccountDataSync.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataSync.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.matrix.android.internal.session.sync.model +package im.vector.matrix.android.internal.session.sync.model.accountdata import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/DefaultUserService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/DefaultUserService.kt index be330bfc36..d314c8d108 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/DefaultUserService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/DefaultUserService.kt @@ -29,9 +29,12 @@ import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.toOptional import im.vector.matrix.android.internal.database.mapper.asDomain +import im.vector.matrix.android.internal.database.model.IgnoredUserEntity +import im.vector.matrix.android.internal.database.model.IgnoredUserEntityFields import im.vector.matrix.android.internal.database.model.UserEntity import im.vector.matrix.android.internal.database.model.UserEntityFields import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.session.user.accountdata.UpdateIgnoredUserIdsTask import im.vector.matrix.android.internal.session.user.model.SearchUserTask import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith @@ -40,8 +43,8 @@ import javax.inject.Inject internal class DefaultUserService @Inject constructor(private val monarchy: Monarchy, private val searchUserTask: SearchUserTask, + private val updateIgnoredUserIdsTask: UpdateIgnoredUserIdsTask, private val taskExecutor: TaskExecutor) : UserService { - private val realmDataSourceFactory: Monarchy.RealmDataSourceFactory<UserEntity> by lazy { monarchy.createDataSourceFactory { realm -> realm.where(UserEntity::class.java) @@ -62,7 +65,7 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona override fun getUser(userId: String): User? { val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() } - ?: return null + ?: return null return userEntity.asDomain() } @@ -117,4 +120,33 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona } .executeBy(taskExecutor) } + + override fun liveIgnoredUsers(): LiveData<List<User>> { + return monarchy.findAllMappedWithChanges( + { realm -> + realm.where(IgnoredUserEntity::class.java) + .isNotEmpty(IgnoredUserEntityFields.USER_ID) + .sort(IgnoredUserEntityFields.USER_ID) + }, + { getUser(it.userId) ?: User(userId = it.userId) } + ) + } + + override fun ignoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable { + val params = UpdateIgnoredUserIdsTask.Params(userIdsToIgnore = userIds.toList()) + return updateIgnoredUserIdsTask + .configureWith(params) { + this.callback = callback + } + .executeBy(taskExecutor) + } + + override fun unIgnoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable { + val params = UpdateIgnoredUserIdsTask.Params(userIdsToUnIgnore = userIds.toList()) + return updateIgnoredUserIdsTask + .configureWith(params) { + this.callback = callback + } + .executeBy(taskExecutor) + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserModule.kt index a997c616f3..51c296ba6e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserModule.kt @@ -21,6 +21,10 @@ import dagger.Module import dagger.Provides import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.internal.session.SessionScope +import im.vector.matrix.android.internal.session.user.accountdata.DefaultSaveIgnoredUsersTask +import im.vector.matrix.android.internal.session.user.accountdata.DefaultUpdateIgnoredUserIdsTask +import im.vector.matrix.android.internal.session.user.accountdata.SaveIgnoredUsersTask +import im.vector.matrix.android.internal.session.user.accountdata.UpdateIgnoredUserIdsTask import im.vector.matrix.android.internal.session.user.model.DefaultSearchUserTask import im.vector.matrix.android.internal.session.user.model.SearchUserTask import retrofit2.Retrofit @@ -43,4 +47,10 @@ internal abstract class UserModule { @Binds abstract fun bindSearchUserTask(searchUserTask: DefaultSearchUserTask): SearchUserTask + + @Binds + abstract fun bindSaveIgnoredUsersTask(task: DefaultSaveIgnoredUsersTask): SaveIgnoredUsersTask + + @Binds + abstract fun bindUpdateIgnoredUserIdsTask(task: DefaultUpdateIgnoredUserIdsTask): UpdateIgnoredUserIdsTask } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveIgnoredUsersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveIgnoredUsersTask.kt new file mode 100644 index 0000000000..c9a3eef440 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveIgnoredUsersTask.kt @@ -0,0 +1,46 @@ +/* + * 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.user.accountdata + +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.internal.database.model.IgnoredUserEntity +import im.vector.matrix.android.internal.task.Task +import im.vector.matrix.android.internal.util.awaitTransaction +import javax.inject.Inject + +/** + * Save the ignored users list in DB + */ +internal interface SaveIgnoredUsersTask : Task<SaveIgnoredUsersTask.Params, Unit> { + data class Params( + val userIds: List<String> + ) +} + +internal class DefaultSaveIgnoredUsersTask @Inject constructor(private val monarchy: Monarchy) : SaveIgnoredUsersTask { + + override suspend fun execute(params: SaveIgnoredUsersTask.Params) { + monarchy.awaitTransaction { realm -> + // clear current ignored users + realm.where(IgnoredUserEntity::class.java) + .findAll() + .deleteAllFromRealm() + + // And save the new received list + params.userIds.forEach { realm.createObject(IgnoredUserEntity::class.java).apply { userId = it } } + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt new file mode 100644 index 0000000000..075eeb23d1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt @@ -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.matrix.android.internal.session.user.accountdata + +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.internal.database.model.IgnoredUserEntity +import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.session.sync.model.accountdata.IgnoredUsersContent +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData +import im.vector.matrix.android.internal.task.Task +import javax.inject.Inject + +internal interface UpdateIgnoredUserIdsTask : Task<UpdateIgnoredUserIdsTask.Params, Unit> { + + data class Params( + val userIdsToIgnore: List<String> = emptyList(), + val userIdsToUnIgnore: List<String> = emptyList() + ) +} + +internal class DefaultUpdateIgnoredUserIdsTask @Inject constructor(private val accountDataApi: AccountDataAPI, + private val monarchy: Monarchy, + private val saveIgnoredUsersTask: SaveIgnoredUsersTask, + @UserId private val userId: String) : UpdateIgnoredUserIdsTask { + + override suspend fun execute(params: UpdateIgnoredUserIdsTask.Params) { + // Get current list + val ignoredUserIds = monarchy.fetchAllMappedSync( + { realm -> realm.where(IgnoredUserEntity::class.java) }, + { it.userId } + ).toMutableSet() + + val original = ignoredUserIds.toList() + + ignoredUserIds.removeAll { it in params.userIdsToUnIgnore } + ignoredUserIds.addAll(params.userIdsToIgnore) + + if (original == ignoredUserIds) { + // No change + return + } + + val list = ignoredUserIds.toList() + val body = IgnoredUsersContent.createWithUserIds(list) + + executeRequest<Unit> { + apiCall = accountDataApi.setAccountData(userId, UserAccountData.TYPE_IGNORED_USER_LIST, body) + } + + // Update the DB right now (do not wait for the sync to come back with updated data, for a faster UI update) + saveIgnoredUsersTask.execute(SaveIgnoredUsersTask.Params(list)) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt index 5c0dac1125..9fa71005ff 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt @@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.session.user.accountdata import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.network.executeRequest -import im.vector.matrix.android.internal.session.sync.model.UserAccountData +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData import im.vector.matrix.android.internal.task.Task import javax.inject.Inject @@ -29,6 +29,7 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa fun getData(): Any } + // TODO Use [UserAccountDataDirectMessages] class? data class DirectChatParams(override val type: String = UserAccountData.TYPE_DIRECT_MESSAGES, private val directMessages: Map<String, List<String>> ) : Params { diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorViewModel.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorViewModel.kt index 9679c20efb..ae601bb0f3 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorViewModel.kt @@ -16,13 +16,21 @@ package im.vector.riotx.core.platform +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import com.airbnb.mvrx.* +import im.vector.riotx.core.utils.LiveEvent import io.reactivex.Observable import io.reactivex.Single abstract class VectorViewModel<S : MvRxState>(initialState: S) : BaseMvRxViewModel<S>(initialState, false) { + // Generic handling of any request error + protected val _requestErrorLiveData = MutableLiveData<LiveEvent<Throwable>>() + val requestErrorLiveData: LiveData<LiveEvent<Throwable>> + get() = _requestErrorLiveData + /** * This method does the same thing as the execute function, but it doesn't subscribe to the stream * so you can use this in a switchMap or a flatMap diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt index a219d25c09..9169183da1 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt @@ -50,7 +50,14 @@ sealed class RoomDetailActions { data class ResendMessage(val eventId: String) : RoomDetailActions() data class RemoveFailedEcho(val eventId: String) : RoomDetailActions() - data class ReportContent(val eventId: String, val reason: String, val spam: Boolean = false, val inappropriate: Boolean = false) : RoomDetailActions() + data class ReportContent( + val eventId: String, + val senderId: String?, + val reason: String, + val spam: Boolean = false, + val inappropriate: Boolean = false) : RoomDetailActions() + + data class IgnoreUser(val userId: String?) : RoomDetailActions() object ClearSendQueue : RoomDetailActions() object ResendAll : RoomDetailActions() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 667fa9784b..6d39a0b790 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -757,7 +757,7 @@ class RoomDetailFragment @Inject constructor( .setView(layout) .setPositiveButton(R.string.report_content_custom_submit) { _, _ -> val reason = input.text.toString() - roomDetailViewModel.process(RoomDetailActions.ReportContent(action.eventId, reason)) + roomDetailViewModel.process(RoomDetailActions.ReportContent(action.eventId, action.senderId, reason)) } .setNegativeButton(R.string.cancel, null) .show() @@ -781,7 +781,9 @@ class RoomDetailFragment @Inject constructor( .setTitle(R.string.content_reported_as_spam_title) .setMessage(R.string.content_reported_as_spam_content) .setPositiveButton(R.string.ok, null) - .setNegativeButton(R.string.block_user) { _, _ -> vectorBaseActivity.notImplemented("block user") } + .setNegativeButton(R.string.block_user) { _, _ -> + roomDetailViewModel.process(RoomDetailActions.IgnoreUser(data.senderId)) + } .show() .withColoredButton(DialogInterface.BUTTON_NEGATIVE) } @@ -790,7 +792,9 @@ class RoomDetailFragment @Inject constructor( .setTitle(R.string.content_reported_as_inappropriate_title) .setMessage(R.string.content_reported_as_inappropriate_content) .setPositiveButton(R.string.ok, null) - .setNegativeButton(R.string.block_user) { _, _ -> vectorBaseActivity.notImplemented("block user") } + .setNegativeButton(R.string.block_user) { _, _ -> + roomDetailViewModel.process(RoomDetailActions.IgnoreUser(data.senderId)) + } .show() .withColoredButton(DialogInterface.BUTTON_NEGATIVE) } @@ -799,7 +803,9 @@ class RoomDetailFragment @Inject constructor( .setTitle(R.string.content_reported_title) .setMessage(R.string.content_reported_content) .setPositiveButton(R.string.ok, null) - .setNegativeButton(R.string.block_user) { _, _ -> vectorBaseActivity.notImplemented("block user") } + .setNegativeButton(R.string.block_user) { _, _ -> + roomDetailViewModel.process(RoomDetailActions.IgnoreUser(data.senderId)) + } .show() .withColoredButton(DialogInterface.BUTTON_NEGATIVE) } @@ -1124,10 +1130,12 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.process(RoomDetailActions.RemoveFailedEcho(action.eventId)) } is SimpleAction.ReportContentSpam -> { - roomDetailViewModel.process(RoomDetailActions.ReportContent(action.eventId, "This message is spam", spam = true)) + roomDetailViewModel.process(RoomDetailActions.ReportContent( + action.eventId, action.senderId, "This message is spam", spam = true)) } is SimpleAction.ReportContentInappropriate -> { - roomDetailViewModel.process(RoomDetailActions.ReportContent(action.eventId, "This message is inappropriate", inappropriate = true)) + roomDetailViewModel.process(RoomDetailActions.ReportContent( + action.eventId, action.senderId, "This message is inappropriate", inappropriate = true)) } is SimpleAction.ReportContentCustom -> { promptReasonToReportContent(action) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 2436b7a8b4..4965de4438 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -157,6 +157,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro is RoomDetailActions.SetReadMarkerAction -> handleSetReadMarkerAction(action) is RoomDetailActions.MarkAllAsRead -> handleMarkAllAsRead() is RoomDetailActions.ReportContent -> handleReportContent(action) + is RoomDetailActions.IgnoreUser -> handleIgnoreUser(action) } } @@ -710,6 +711,22 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro }) } + private fun handleIgnoreUser(action: RoomDetailActions.IgnoreUser) { + if (action.userId.isNullOrEmpty()) { + return + } + + session.ignoreUserIds(listOf(action.userId), object : MatrixCallback<Unit> { + override fun onSuccess(data: Unit) { + _requestLiveData.postValue(LiveEvent(Success(action))) + } + + override fun onFailure(failure: Throwable) { + _requestLiveData.postValue(LiveEvent(Fail(failure))) + } + }) + } + private fun observeSyncState() { session.rx() .liveSyncState() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt index d9119f08b3..cea693c172 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt @@ -102,9 +102,9 @@ class MessageActionsEpoxyController @Inject constructor(private val stringProvid if (action is SimpleAction.ReportContent && state.expendedReportContentMenu) { // Special case for report content menu: add the submenu listOf( - SimpleAction.ReportContentSpam(action.eventId), - SimpleAction.ReportContentInappropriate(action.eventId), - SimpleAction.ReportContentCustom(action.eventId) + SimpleAction.ReportContentSpam(action.eventId, action.senderId), + SimpleAction.ReportContentInappropriate(action.eventId, action.senderId), + SimpleAction.ReportContentCustom(action.eventId, action.senderId) ).forEachIndexed { indexReport, actionReport -> bottomSheetItemAction { id("actionReport_$indexReport") diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 63a4919763..63429e5def 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -263,7 +263,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted if (session.myUserId != event.root.senderId && event.root.getClearType() == EventType.MESSAGE) { // not sent by me - add(SimpleAction.ReportContent(eventId)) + add(SimpleAction.ReportContent(eventId, event.root.senderId)) } } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/SimpleAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/SimpleAction.kt index 5da589d862..ab2fb8e41e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/SimpleAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/SimpleAction.kt @@ -22,25 +22,63 @@ import im.vector.riotx.R import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData sealed class SimpleAction(@StringRes val titleRes: Int, @DrawableRes val iconResId: Int) { - data class AddReaction(val eventId: String) : SimpleAction(R.string.message_add_reaction, R.drawable.ic_add_reaction) - data class Copy(val content: String) : SimpleAction(R.string.copy, R.drawable.ic_copy) - data class Edit(val eventId: String) : SimpleAction(R.string.edit, R.drawable.ic_edit) - data class Quote(val eventId: String) : SimpleAction(R.string.quote, R.drawable.ic_quote) - data class Reply(val eventId: String) : SimpleAction(R.string.reply, R.drawable.ic_reply) - data class Share(val imageUrl: String) : SimpleAction(R.string.share, R.drawable.ic_share) - data class Resend(val eventId: String) : SimpleAction(R.string.global_retry, R.drawable.ic_refresh_cw) - data class Remove(val eventId: String) : SimpleAction(R.string.remove, R.drawable.ic_trash) - data class Delete(val eventId: String) : SimpleAction(R.string.delete, R.drawable.ic_delete) - data class Cancel(val eventId: String) : SimpleAction(R.string.cancel, R.drawable.ic_close_round) - data class ViewSource(val content: String) : SimpleAction(R.string.view_source, R.drawable.ic_view_source) - data class ViewDecryptedSource(val content: String) : SimpleAction(R.string.view_decrypted_source, R.drawable.ic_view_source) - data class CopyPermalink(val eventId: String) : SimpleAction(R.string.permalink, R.drawable.ic_permalink) - data class ReportContent(val eventId: String) : SimpleAction(R.string.report_content, R.drawable.ic_flag) - data class ReportContentSpam(val eventId: String) : SimpleAction(R.string.report_content_spam, R.drawable.ic_report_spam) - data class ReportContentInappropriate(val eventId: String) : SimpleAction(R.string.report_content_inappropriate, R.drawable.ic_report_inappropriate) - data class ReportContentCustom(val eventId: String) : SimpleAction(R.string.report_content_custom, R.drawable.ic_report_custom) - data class QuickReact(val eventId: String, val clickedOn: String, val add: Boolean) : SimpleAction(0, 0) - data class ViewReactions(val messageInformationData: MessageInformationData) : SimpleAction(R.string.message_view_reaction, R.drawable.ic_view_reactions) + data class AddReaction(val eventId: String) : + SimpleAction(R.string.message_add_reaction, R.drawable.ic_add_reaction) + + data class Copy(val content: String) : + SimpleAction(R.string.copy, R.drawable.ic_copy) + + data class Edit(val eventId: String) : + SimpleAction(R.string.edit, R.drawable.ic_edit) + + data class Quote(val eventId: String) : + SimpleAction(R.string.quote, R.drawable.ic_quote) + + data class Reply(val eventId: String) : + SimpleAction(R.string.reply, R.drawable.ic_reply) + + data class Share(val imageUrl: String) : + SimpleAction(R.string.share, R.drawable.ic_share) + + data class Resend(val eventId: String) : + SimpleAction(R.string.global_retry, R.drawable.ic_refresh_cw) + + data class Remove(val eventId: String) : + SimpleAction(R.string.remove, R.drawable.ic_trash) + + data class Delete(val eventId: String) : + SimpleAction(R.string.delete, R.drawable.ic_delete) + + data class Cancel(val eventId: String) : + SimpleAction(R.string.cancel, R.drawable.ic_close_round) + + data class ViewSource(val content: String) : + SimpleAction(R.string.view_source, R.drawable.ic_view_source) + + data class ViewDecryptedSource(val content: String) : + SimpleAction(R.string.view_decrypted_source, R.drawable.ic_view_source) + + data class CopyPermalink(val eventId: String) : + SimpleAction(R.string.permalink, R.drawable.ic_permalink) + + data class ReportContent(val eventId: String, val senderId: String?) : + SimpleAction(R.string.report_content, R.drawable.ic_flag) + + data class ReportContentSpam(val eventId: String, val senderId: String?) : + SimpleAction(R.string.report_content_spam, R.drawable.ic_report_spam) + + data class ReportContentInappropriate(val eventId: String, val senderId: String?) : + SimpleAction(R.string.report_content_inappropriate, R.drawable.ic_report_inappropriate) + + data class ReportContentCustom(val eventId: String, val senderId: String?) : + SimpleAction(R.string.report_content_custom, R.drawable.ic_report_custom) + + data class QuickReact(val eventId: String, val clickedOn: String, val add: Boolean) : + SimpleAction(0, 0) + + data class ViewReactions(val messageInformationData: MessageInformationData) : + SimpleAction(R.string.message_view_reaction, R.drawable.ic_view_reactions) + data class ViewEditHistory(val messageInformationData: MessageInformationData) : SimpleAction(R.string.message_view_edit_history, R.drawable.ic_view_edit_history) } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt index a593bb6e96..78f57dd548 100755 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt @@ -55,8 +55,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { const val SETTINGS_CONTACT_PREFERENCE_KEYS = "SETTINGS_CONTACT_PREFERENCE_KEYS" const val SETTINGS_NOTIFICATIONS_TARGETS_PREFERENCE_KEY = "SETTINGS_NOTIFICATIONS_TARGETS_PREFERENCE_KEY" const val SETTINGS_NOTIFICATIONS_TARGET_DIVIDER_PREFERENCE_KEY = "SETTINGS_NOTIFICATIONS_TARGET_DIVIDER_PREFERENCE_KEY" - const val SETTINGS_IGNORED_USERS_PREFERENCE_KEY = "SETTINGS_IGNORED_USERS_PREFERENCE_KEY" - const val SETTINGS_IGNORE_USERS_DIVIDER_PREFERENCE_KEY = "SETTINGS_IGNORE_USERS_DIVIDER_PREFERENCE_KEY" const val SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY = "SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY" const val SETTINGS_BACKGROUND_SYNC_DIVIDER_PREFERENCE_KEY = "SETTINGS_BACKGROUND_SYNC_DIVIDER_PREFERENCE_KEY" const val SETTINGS_LABS_PREFERENCE_KEY = "SETTINGS_LABS_PREFERENCE_KEY" @@ -544,7 +542,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { MEDIA_SAVING_1_WEEK -> System.currentTimeMillis() / 1000 - 7 * 24 * 60 * 60 MEDIA_SAVING_1_MONTH -> System.currentTimeMillis() / 1000 - 30 * 24 * 60 * 60 MEDIA_SAVING_FOREVER -> 0 - else -> 0 + else -> 0 } } @@ -559,7 +557,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { MEDIA_SAVING_1_WEEK -> context.getString(R.string.media_saving_period_1_week) MEDIA_SAVING_1_MONTH -> context.getString(R.string.media_saving_period_1_month) MEDIA_SAVING_FOREVER -> context.getString(R.string.media_saving_period_forever) - else -> "?" + else -> "?" } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsIgnoredUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsIgnoredUsersFragment.kt deleted file mode 100644 index 32a6fe8f41..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsIgnoredUsersFragment.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.riotx.features.settings - -import androidx.appcompat.app.AlertDialog -import androidx.preference.Preference -import androidx.preference.PreferenceCategory -import im.vector.riotx.R -import im.vector.riotx.core.preference.VectorPreference -import java.util.ArrayList -import kotlin.Comparator - -class VectorSettingsIgnoredUsersFragment : VectorSettingsBaseFragment() { - - override var titleRes = R.string.settings_ignored_users - override val preferenceXmlRes = R.xml.vector_settings_ignored_users - - // displayed the ignored users list - private val mIgnoredUserSettingsCategoryDivider by lazy { - findPreference<VectorPreference>(VectorPreferences.SETTINGS_IGNORE_USERS_DIVIDER_PREFERENCE_KEY)!! - } - private val mIgnoredUserSettingsCategory by lazy { - findPreference<PreferenceCategory>(VectorPreferences.SETTINGS_IGNORED_USERS_PREFERENCE_KEY)!! - } - - override fun bindPref() { - // Ignore users - refreshIgnoredUsersList() - } - - // ============================================================================================================== - // ignored users list management - // ============================================================================================================== - - /** - * Refresh the ignored users list - */ - private fun refreshIgnoredUsersList() { - val ignoredUsersList = mutableListOf<String>() // TODO session.dataHandler.ignoredUserIds - - ignoredUsersList.sortWith(Comparator { u1, u2 -> - u1.toLowerCase(VectorLocale.applicationLocale).compareTo(u2.toLowerCase(VectorLocale.applicationLocale)) - }) - - val preferenceScreen = preferenceScreen - - preferenceScreen.removePreference(mIgnoredUserSettingsCategory) - preferenceScreen.removePreference(mIgnoredUserSettingsCategoryDivider) - mIgnoredUserSettingsCategory.removeAll() - - if (ignoredUsersList.size > 0) { - preferenceScreen.addPreference(mIgnoredUserSettingsCategoryDivider) - preferenceScreen.addPreference(mIgnoredUserSettingsCategory) - - for (userId in ignoredUsersList) { - val preference = Preference(activity) - - preference.title = userId - preference.key = IGNORED_USER_KEY_BASE + userId - - preference.onPreferenceClickListener = Preference.OnPreferenceClickListener { - activity?.let { - AlertDialog.Builder(it) - .setMessage(getString(R.string.settings_unignore_user, userId)) - .setPositiveButton(R.string.yes) { _, _ -> - displayLoadingView() - - val idsList = ArrayList<String>() - idsList.add(userId) - - notImplemented() - /* TODO - session.unIgnoreUsers(idsList, object : MatrixCallback<Unit> { - override fun onSuccess(info: Void?) { - onCommonDone(null) - } - - override fun onNetworkError(e: Exception) { - onCommonDone(e.localizedMessage) - } - - override fun onMatrixError(e: MatrixError) { - onCommonDone(e.localizedMessage) - } - - override fun onUnexpectedError(e: Exception) { - onCommonDone(e.localizedMessage) - } - }) - */ - } - .setNegativeButton(R.string.no, null) - .show() - } - - false - } - - mIgnoredUserSettingsCategory.addPreference(preference) - } - } - } - - companion object { - private const val IGNORED_USER_KEY_BASE = "IGNORED_USER_KEY_BASE" - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/ignored/IgnoredUsersController.kt b/vector/src/main/java/im/vector/riotx/features/settings/ignored/IgnoredUsersController.kt new file mode 100644 index 0000000000..120781874d --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/ignored/IgnoredUsersController.kt @@ -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.settings.ignored + +import com.airbnb.epoxy.EpoxyController +import im.vector.matrix.android.api.session.user.model.User +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.noResultItem +import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.features.home.AvatarRenderer +import javax.inject.Inject + +class IgnoredUsersController @Inject constructor(private val stringProvider: StringProvider, + private val avatarRenderer: AvatarRenderer) : EpoxyController() { + + var callback: Callback? = null + private var viewState: IgnoredUsersViewState? = null + + init { + requestModelBuild() + } + + fun update(viewState: IgnoredUsersViewState) { + this.viewState = viewState + requestModelBuild() + } + + override fun buildModels() { + val nonNullViewState = viewState ?: return + buildIgnoredUserModels(nonNullViewState.ignoredUsers) + } + + private fun buildIgnoredUserModels(userIds: List<User>) { + if (userIds.isEmpty()) { + noResultItem { + id("empty") + text(stringProvider.getString(R.string.no_ignored_users)) + } + } else { + userIds.forEach { userId -> + userItem { + id(userId.userId) + avatarRenderer(avatarRenderer) + user(userId) + itemClickAction { callback?.onUserIdClicked(userId.userId) } + } + } + } + } + + interface Callback { + fun onUserIdClicked(userId: String) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/ignored/IgnoredUsersViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/ignored/IgnoredUsersViewModel.kt new file mode 100644 index 0000000000..a9f142058d --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/ignored/IgnoredUsersViewModel.kt @@ -0,0 +1,102 @@ +/* + * 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.settings.ignored + +import com.airbnb.mvrx.* +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.user.model.User +import im.vector.matrix.rx.rx +import im.vector.riotx.core.extensions.postLiveEvent +import im.vector.riotx.core.platform.VectorViewModel + +data class IgnoredUsersViewState( + val ignoredUsers: List<User> = emptyList(), + val unIgnoreRequest: Async<Unit> = Uninitialized +) : MvRxState + +sealed class IgnoredUsersAction { + data class UnIgnore(val userId: String) : IgnoredUsersAction() +} + +class IgnoredUsersViewModel @AssistedInject constructor(@Assisted initialState: IgnoredUsersViewState, + private val session: Session) : VectorViewModel<IgnoredUsersViewState>(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: IgnoredUsersViewState): IgnoredUsersViewModel + } + + companion object : MvRxViewModelFactory<IgnoredUsersViewModel, IgnoredUsersViewState> { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: IgnoredUsersViewState): IgnoredUsersViewModel? { + val ignoredUsersFragment: VectorSettingsIgnoredUsersFragment = (viewModelContext as FragmentViewModelContext).fragment() + return ignoredUsersFragment.ignoredUsersViewModelFactory.create(state) + } + } + + init { + observeIgnoredUsers() + } + + private fun observeIgnoredUsers() { + session.rx() + .liveIgnoredUsers() + .execute { async -> + copy( + ignoredUsers = async.invoke().orEmpty() + ) + } + } + + fun handle(action: IgnoredUsersAction) { + when (action) { + is IgnoredUsersAction.UnIgnore -> handleUnIgnore(action) + } + } + + private fun handleUnIgnore(action: IgnoredUsersAction.UnIgnore) { + setState { + copy( + unIgnoreRequest = Loading() + ) + } + + session.unIgnoreUserIds(listOf(action.userId), object : MatrixCallback<Unit> { + override fun onFailure(failure: Throwable) { + setState { + copy( + unIgnoreRequest = Fail(failure) + ) + } + + _requestErrorLiveData.postLiveEvent(failure) + } + + override fun onSuccess(data: Unit) { + setState { + copy( + unIgnoreRequest = Success(data) + ) + } + } + }) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/ignored/UserItem.kt b/vector/src/main/java/im/vector/riotx/features/settings/ignored/UserItem.kt new file mode 100644 index 0000000000..a9c1b98915 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/ignored/UserItem.kt @@ -0,0 +1,59 @@ +/* + * 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.settings.ignored + +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.matrix.android.api.session.user.model.User +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 list item for User. + */ +@EpoxyModelClass(layout = R.layout.item_user) +abstract class UserItem : VectorEpoxyModel<UserItem.Holder>() { + + @EpoxyAttribute + lateinit var avatarRenderer: AvatarRenderer + + @EpoxyAttribute + lateinit var user: User + + @EpoxyAttribute + var itemClickAction: (() -> Unit)? = null + + override fun bind(holder: Holder) { + holder.root.setOnClickListener { itemClickAction?.invoke() } + + avatarRenderer.render(user, holder.avatarImage) + holder.userIdText.setTextOrHide(user.userId) + holder.displayNameText.setTextOrHide(user.displayName) + } + + class Holder : VectorEpoxyHolder() { + val root by bind<View>(R.id.itemUserRoot) + val avatarImage by bind<ImageView>(R.id.itemUserAvatar) + val userIdText by bind<TextView>(R.id.itemUserId) + val displayNameText by bind<TextView>(R.id.itemUserName) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt new file mode 100644 index 0000000000..11e473ae24 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt @@ -0,0 +1,98 @@ +/* + * 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.settings.ignored + +import android.os.Bundle +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.core.view.isVisible +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.riotx.R +import im.vector.riotx.core.error.ErrorFormatter +import im.vector.riotx.core.extensions.observeEvent +import im.vector.riotx.core.platform.VectorBaseActivity +import im.vector.riotx.core.platform.VectorBaseFragment +import kotlinx.android.synthetic.main.fragment_generic_recycler_epoxy.* +import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* +import javax.inject.Inject + +class VectorSettingsIgnoredUsersFragment @Inject constructor( + val ignoredUsersViewModelFactory: IgnoredUsersViewModel.Factory, + private val ignoredUsersController: IgnoredUsersController, + private val errorFormatter: ErrorFormatter +) : VectorBaseFragment(), IgnoredUsersController.Callback { + + override fun getLayoutResId() = R.layout.fragment_generic_recycler_epoxy + + private val ignoredUsersViewModel: IgnoredUsersViewModel by fragmentViewModel() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + waiting_view_status_text.setText(R.string.please_wait) + waiting_view_status_text.isVisible = true + ignoredUsersController.callback = this + epoxyRecyclerView.setController(ignoredUsersController) + ignoredUsersViewModel.requestErrorLiveData.observeEvent(this) { + displayErrorDialog(it) + } + } + + override fun onResume() { + super.onResume() + + (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_ignored_users) + } + + override fun onUserIdClicked(userId: String) { + AlertDialog.Builder(requireActivity()) + .setMessage(getString(R.string.settings_unignore_user, userId)) + .setPositiveButton(R.string.yes) { _, _ -> + ignoredUsersViewModel.handle(IgnoredUsersAction.UnIgnore(userId)) + } + .setNegativeButton(R.string.no, null) + .show() + } + + private fun displayErrorDialog(throwable: Throwable) { + AlertDialog.Builder(requireActivity()) + .setTitle(R.string.dialog_title_error) + .setMessage(errorFormatter.toHumanReadable(throwable)) + .setPositiveButton(R.string.ok, null) + .show() + } + + // ============================================================================================================== + // ignored users list management + // ============================================================================================================== + + override fun invalidate() = withState(ignoredUsersViewModel) { state -> + ignoredUsersController.update(state) + + handleUnIgnoreRequestStatus(state.unIgnoreRequest) + } + + private fun handleUnIgnoreRequestStatus(unIgnoreRequest: Async<Unit>) { + when (unIgnoreRequest) { + is Loading -> waiting_view.isVisible = true + else -> waiting_view.isVisible = false + } + } +} diff --git a/vector/src/main/res/layout/fragment_generic_recycler_epoxy.xml b/vector/src/main/res/layout/fragment_generic_recycler_epoxy.xml index c794e8a5fa..3f02081b16 100644 --- a/vector/src/main/res/layout/fragment_generic_recycler_epoxy.xml +++ b/vector/src/main/res/layout/fragment_generic_recycler_epoxy.xml @@ -1,9 +1,17 @@ <?xml version="1.0" encoding="utf-8"?> -<com.airbnb.epoxy.EpoxyRecyclerView xmlns:android="http://schemas.android.com/apk/res/android" +<FrameLayout 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/epoxyRecyclerView" android:layout_width="match_parent" - android:layout_height="match_parent" - app:itemSpacing="1dp" - tools:listitem="@layout/item_pushgateway" /> + android:layout_height="match_parent"> + + <com.airbnb.epoxy.EpoxyRecyclerView + android:id="@+id/epoxyRecyclerView" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:itemSpacing="1dp" + tools:listitem="@layout/item_pushgateway" /> + + <include layout="@layout/merge_overlay_waiting_view" /> + +</FrameLayout> diff --git a/vector/src/main/res/layout/item_user.xml b/vector/src/main/res/layout/item_user.xml new file mode 100644 index 0000000000..20e339528a --- /dev/null +++ b/vector/src/main/res/layout/item_user.xml @@ -0,0 +1,58 @@ +<?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/itemUserRoot" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?riotx_background" + android:foreground="?attr/selectableItemBackground" + android:gravity="center_vertical" + android:orientation="horizontal" + android:padding="8dp"> + + <ImageView + android:id="@+id/itemUserAvatar" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_marginStart="8dp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:src="@tools:sample/avatars" /> + + <TextView + android:id="@+id/itemUserId" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginStart="12dp" + android:ellipsize="end" + android:maxLines="1" + android:textColor="?riotx_text_primary" + android:textSize="15sp" + android:textStyle="bold" + app:layout_constraintBottom_toTopOf="@+id/itemUserName" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/itemUserAvatar" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" + tools:text="@sample/matrix.json/data/mxid" /> + + <TextView + android:id="@+id/itemUserName" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="12dp" + android:ellipsize="end" + android:maxLines="1" + android:textColor="?riotx_text_secondary" + android:textSize="15sp" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/itemUserAvatar" + app:layout_constraintTop_toBottomOf="@+id/itemUserId" + tools:text="@sample/matrix.json/data/displayName" + tools:visibility="visible" /> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/vector/src/main/res/layout/merge_overlay_waiting_view.xml b/vector/src/main/res/layout/merge_overlay_waiting_view.xml index 8f09ed0988..b7e7bf41c7 100644 --- a/vector/src/main/res/layout/merge_overlay_waiting_view.xml +++ b/vector/src/main/res/layout/merge_overlay_waiting_view.xml @@ -3,8 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> + android:layout_height="match_parent"> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/waiting_view" diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 20fc234fc8..0a2af875ab 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -4,4 +4,6 @@ <!-- Strings not defined in Riot --> <string name="notice_member_no_changes">"%1$s made no changes"</string> + <string name="no_ignored_users">You are not ignoring any users</string> + </resources> diff --git a/vector/src/main/res/xml/vector_settings_ignored_users.xml b/vector/src/main/res/xml/vector_settings_ignored_users.xml deleted file mode 100644 index 4ad8aed5b6..0000000000 --- a/vector/src/main/res/xml/vector_settings_ignored_users.xml +++ /dev/null @@ -1,13 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<androidx.preference.PreferenceScreen 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"> - - <im.vector.riotx.core.preference.VectorPreferenceCategory - android:key="SETTINGS_IGNORED_USERS_PREFERENCE_KEY" - android:title="@string/settings_ignored_users" /> - - <im.vector.riotx.core.preference.VectorPreferenceDivider android:key="SETTINGS_IGNORE_USERS_DIVIDER_PREFERENCE_KEY" /> - - -</androidx.preference.PreferenceScreen> \ No newline at end of file diff --git a/vector/src/main/res/xml/vector_settings_root.xml b/vector/src/main/res/xml/vector_settings_root.xml index e0cb4f778b..894784767a 100644 --- a/vector/src/main/res/xml/vector_settings_root.xml +++ b/vector/src/main/res/xml/vector_settings_root.xml @@ -37,10 +37,9 @@ <im.vector.riotx.core.preference.VectorPreference android:layout_width="match_parent" - android:enabled="@bool/false_not_implemented" android:icon="@drawable/ic_settings_root_ignored_users" android:title="@string/settings_ignored_users" - app:fragment="im.vector.riotx.features.settings.VectorSettingsIgnoredUsersFragment" /> + app:fragment="im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment" /> <im.vector.riotx.core.preference.VectorPreference android:layout_width="match_parent"