From d8317f74392df65b1fb534ea7a97ee8a187d9de2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 10 Jun 2020 00:21:02 +0200 Subject: [PATCH] Add capability to add and remove a room from the favorites (#1217) --- CHANGES.md | 1 + .../matrix/android/api/session/room/Room.kt | 2 + .../api/session/room/model/RoomSummary.kt | 3 + .../api/session/room/model/tag/RoomTag.kt | 7 +-- .../api/session/room/tags/TagsService.kt | 35 ++++++++++++ .../internal/session/room/DefaultRoom.kt | 3 + .../android/internal/session/room/RoomAPI.kt | 23 ++++++++ .../internal/session/room/RoomFactory.kt | 3 + .../internal/session/room/RoomModule.kt | 10 ++++ .../session/room/tags/AddTagToRoomTask.kt | 53 ++++++++++++++++++ .../session/room/tags/DefaultTagsService.kt | 56 +++++++++++++++++++ .../room/tags/DeleteTagFromRoomTask.kt | 49 ++++++++++++++++ .../internal/session/room/tags/TagBody.kt | 29 ++++++++++ .../bottomsheet/BottomSheetRoomPreviewItem.kt | 37 ++++++++++-- .../riotx/features/home/ShortcutsHandler.kt | 2 +- .../features/home/room/list/RoomListAction.kt | 1 + .../home/room/list/RoomListFragment.kt | 6 +- .../home/room/list/RoomListViewModel.kt | 18 ++++++ .../RoomListQuickActionsBottomSheet.kt | 6 +- .../RoomListQuickActionsEpoxyController.kt | 15 +++-- .../RoomListQuickActionsSharedAction.kt | 4 ++ vector/src/main/res/drawable/ic_star_24dp.xml | 14 +++++ .../main/res/drawable/ic_star_green_24dp.xml | 14 +++++ .../layout/item_bottom_sheet_room_preview.xml | 22 ++++++-- vector/src/main/res/values/strings.xml | 2 + 25 files changed, 395 insertions(+), 20 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/tags/TagsService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tags/AddTagToRoomTask.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tags/DefaultTagsService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tags/DeleteTagFromRoomTask.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tags/TagBody.kt create mode 100644 vector/src/main/res/drawable/ic_star_24dp.xml create mode 100644 vector/src/main/res/drawable/ic_star_green_24dp.xml diff --git a/CHANGES.md b/CHANGES.md index 95a3b88df3..163db6a1d0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ Improvements 🙌: - Correctly handle SSO login redirection - SSO login is now performed in the default browser, or in Chrome Custom tab if available (#1400) - Improve checking of homeserver version support (#1442) + - Add capability to add and remove a room from the favorites (#1217) Bugfix 🐛: - Switch theme is not fully taken into account without restarting the app diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt index 2fd7d84f04..4ae61f46e1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt @@ -27,6 +27,7 @@ import im.vector.matrix.android.api.session.room.reporting.ReportingService import im.vector.matrix.android.api.session.room.send.DraftService import im.vector.matrix.android.api.session.room.send.SendService import im.vector.matrix.android.api.session.room.state.StateService +import im.vector.matrix.android.api.session.room.tags.TagsService import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.session.room.typing.TypingService import im.vector.matrix.android.api.session.room.uploads.UploadsService @@ -41,6 +42,7 @@ interface Room : DraftService, ReadService, TypingService, + TagsService, MembershipService, StateService, UploadsService, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt index 1ad6112f2c..3c95fd47fc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt @@ -59,6 +59,9 @@ data class RoomSummary constructor( val hasNewMessages: Boolean get() = notificationCount != 0 + val isFavorite: Boolean + get() = tags.any { it.name == RoomTag.ROOM_TAG_FAVOURITE } + companion object { const val NOT_IN_BREADCRUMBS = -1 } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/tag/RoomTag.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/tag/RoomTag.kt index 9e85ba3255..6826824278 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/tag/RoomTag.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/tag/RoomTag.kt @@ -22,9 +22,8 @@ data class RoomTag( ) { companion object { - val ROOM_TAG_FAVOURITE = "m.favourite" - val ROOM_TAG_LOW_PRIORITY = "m.lowpriority" - val ROOM_TAG_NO_TAG = "m.recent" - val ROOM_TAG_SERVER_NOTICE = "m.server_notice" + const val ROOM_TAG_FAVOURITE = "m.favourite" + const val ROOM_TAG_LOW_PRIORITY = "m.lowpriority" + const val ROOM_TAG_SERVER_NOTICE = "m.server_notice" } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/tags/TagsService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/tags/TagsService.kt new file mode 100644 index 0000000000..b2509d575f --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/tags/TagsService.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.api.session.room.tags + +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.util.Cancelable + +/** + * This interface defines methods to handle tags of a room. It's implemented at the room level. + */ +interface TagsService { + /** + * Add a tag to a room + */ + fun addTag(tag: String, order: Double?, callback: MatrixCallback): Cancelable + + /** + * Remove tag form a room + */ + fun deleteTag(tag: String, callback: MatrixCallback): Cancelable +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt index 575b040ffe..04d495211e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt @@ -32,6 +32,7 @@ import im.vector.matrix.android.api.session.room.reporting.ReportingService import im.vector.matrix.android.api.session.room.send.DraftService import im.vector.matrix.android.api.session.room.send.SendService import im.vector.matrix.android.api.session.room.state.StateService +import im.vector.matrix.android.api.session.room.tags.TagsService import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.session.room.typing.TypingService import im.vector.matrix.android.api.session.room.uploads.UploadsService @@ -59,6 +60,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, private val reportingService: ReportingService, private val readService: ReadService, private val typingService: TypingService, + private val tagsService: TagsService, private val cryptoService: CryptoService, private val relationService: RelationService, private val roomMembersService: MembershipService, @@ -74,6 +76,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, ReportingService by reportingService, ReadService by readService, TypingService by typingService, + TagsService by tagsService, RelationService by relationService, MembershipService by roomMembersService, RoomPushRuleService by roomPushRuleService { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt index 3184416d23..48b97c4d50 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt @@ -32,11 +32,13 @@ import im.vector.matrix.android.internal.session.room.membership.joining.InviteB import im.vector.matrix.android.internal.session.room.relation.RelationsResponse import im.vector.matrix.android.internal.session.room.reporting.ReportContentBody import im.vector.matrix.android.internal.session.room.send.SendResponse +import im.vector.matrix.android.internal.session.room.tags.TagBody import im.vector.matrix.android.internal.session.room.timeline.EventContextResponse import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse import im.vector.matrix.android.internal.session.room.typing.TypingBody import retrofit2.Call import retrofit2.http.Body +import retrofit2.http.DELETE import retrofit2.http.GET import retrofit2.http.Headers import retrofit2.http.POST @@ -288,4 +290,25 @@ internal interface RoomAPI { fun sendTypingState(@Path("roomId") roomId: String, @Path("userId") userId: String, @Body body: TypingBody): Call + + /** + * Room tagging + */ + + /** + * Add a tag to a room. + */ + @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}") + fun putTag(@Path("userId") userId: String, + @Path("roomId") roomId: String, + @Path("tag") tag: String, + @Body body: TagBody): Call + + /** + * Delete a tag from a room. + */ + @DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}") + fun deleteTag(@Path("userId") userId: String, + @Path("roomId") roomId: String, + @Path("tag") tag: String): Call } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt index 974c30dba9..0560aa80c2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt @@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.session.room.reporting.DefaultReporting import im.vector.matrix.android.internal.session.room.send.DefaultSendService import im.vector.matrix.android.internal.session.room.state.DefaultStateService import im.vector.matrix.android.internal.session.room.state.SendStateTask +import im.vector.matrix.android.internal.session.room.tags.DefaultTagsService import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService import im.vector.matrix.android.internal.session.room.typing.DefaultTypingService import im.vector.matrix.android.internal.session.room.uploads.DefaultUploadsService @@ -52,6 +53,7 @@ internal class DefaultRoomFactory @Inject constructor(private val monarchy: Mona private val reportingServiceFactory: DefaultReportingService.Factory, private val readServiceFactory: DefaultReadService.Factory, private val typingServiceFactory: DefaultTypingService.Factory, + private val tagsServiceFactory: DefaultTagsService.Factory, private val relationServiceFactory: DefaultRelationService.Factory, private val membershipServiceFactory: DefaultMembershipService.Factory, private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory, @@ -72,6 +74,7 @@ internal class DefaultRoomFactory @Inject constructor(private val monarchy: Mona reportingService = reportingServiceFactory.create(roomId), readService = readServiceFactory.create(roomId), typingService = typingServiceFactory.create(roomId), + tagsService = tagsServiceFactory.create(roomId), cryptoService = cryptoService, relationService = relationServiceFactory.create(roomId), roomMembersService = membershipServiceFactory.create(roomId), diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index 77a89a355f..e0c84e590f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -56,6 +56,10 @@ import im.vector.matrix.android.internal.session.room.reporting.DefaultReportCon import im.vector.matrix.android.internal.session.room.reporting.ReportContentTask import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask import im.vector.matrix.android.internal.session.room.state.SendStateTask +import im.vector.matrix.android.internal.session.room.tags.AddTagToRoomTask +import im.vector.matrix.android.internal.session.room.tags.DefaultAddTagToRoomTask +import im.vector.matrix.android.internal.session.room.tags.DefaultDeleteTagFromRoomTask +import im.vector.matrix.android.internal.session.room.tags.DeleteTagFromRoomTask import im.vector.matrix.android.internal.session.room.timeline.DefaultFetchNextTokenAndPaginateTask import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask @@ -186,4 +190,10 @@ internal abstract class RoomModule { @Binds abstract fun bindGetUploadsTask(task: DefaultGetUploadsTask): GetUploadsTask + + @Binds + abstract fun bindAddTagToRoomTask(task: DefaultAddTagToRoomTask): AddTagToRoomTask + + @Binds + abstract fun bindDeleteTagFromRoomTask(task: DefaultDeleteTagFromRoomTask): DeleteTagFromRoomTask } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tags/AddTagToRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tags/AddTagToRoomTask.kt new file mode 100644 index 0000000000..0d1832050c --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tags/AddTagToRoomTask.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.session.room.tags + +import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus +import javax.inject.Inject + +internal interface AddTagToRoomTask : Task { + + data class Params( + val roomId: String, + val tag: String, + val order: Double? + ) +} + +internal class DefaultAddTagToRoomTask @Inject constructor( + private val roomAPI: RoomAPI, + @UserId private val userId: String, + private val eventBus: EventBus +) : AddTagToRoomTask { + + override suspend fun execute(params: AddTagToRoomTask.Params) { + executeRequest(eventBus) { + apiCall = roomAPI.putTag( + userId = userId, + roomId = params.roomId, + tag = params.tag, + body = TagBody( + order = params.order + ) + ) + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tags/DefaultTagsService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tags/DefaultTagsService.kt new file mode 100644 index 0000000000..9df2f0593b --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tags/DefaultTagsService.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.session.room.tags + +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.room.tags.TagsService +import im.vector.matrix.android.api.util.Cancelable +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith + +internal class DefaultTagsService @AssistedInject constructor( + @Assisted private val roomId: String, + private val taskExecutor: TaskExecutor, + private val addTagToRoomTask: AddTagToRoomTask, + private val deleteTagFromRoomTask: DeleteTagFromRoomTask +) : TagsService { + + @AssistedInject.Factory + interface Factory { + fun create(roomId: String): TagsService + } + + override fun addTag(tag: String, order: Double?, callback: MatrixCallback): Cancelable { + val params = AddTagToRoomTask.Params(roomId, tag, order) + return addTagToRoomTask + .configureWith(params) { + this.callback = callback + } + .executeBy(taskExecutor) + } + + override fun deleteTag(tag: String, callback: MatrixCallback): Cancelable { + val params = DeleteTagFromRoomTask.Params(roomId, tag) + return deleteTagFromRoomTask + .configureWith(params) { + this.callback = callback + } + .executeBy(taskExecutor) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tags/DeleteTagFromRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tags/DeleteTagFromRoomTask.kt new file mode 100644 index 0000000000..2907df4953 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tags/DeleteTagFromRoomTask.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.session.room.tags + +import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus +import javax.inject.Inject + +internal interface DeleteTagFromRoomTask : Task { + + data class Params( + val roomId: String, + val tag: String + ) +} + +internal class DefaultDeleteTagFromRoomTask @Inject constructor( + private val roomAPI: RoomAPI, + @UserId private val userId: String, + private val eventBus: EventBus +) : DeleteTagFromRoomTask { + + override suspend fun execute(params: DeleteTagFromRoomTask.Params) { + executeRequest(eventBus) { + apiCall = roomAPI.deleteTag( + userId = userId, + roomId = params.roomId, + tag = params.tag + ) + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tags/TagBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tags/TagBody.kt new file mode 100644 index 0000000000..d82d37757f --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tags/TagBody.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.session.room.tags + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class TagBody( + /** + * A number in a range [0,1] describing a relative position of the room under the given tag. + */ + @Json(name = "order") + val order: Double? +) diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetRoomPreviewItem.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetRoomPreviewItem.kt index b9c0fbdc35..1c1d613f3e 100644 --- a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetRoomPreviewItem.kt +++ b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetRoomPreviewItem.kt @@ -16,9 +16,12 @@ */ 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.core.content.ContextCompat +import androidx.core.widget.ImageViewCompat import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.matrix.android.api.util.MatrixItem @@ -28,7 +31,9 @@ import im.vector.riotx.core.epoxy.VectorEpoxyHolder import im.vector.riotx.core.epoxy.VectorEpoxyModel import im.vector.riotx.core.epoxy.onClick import im.vector.riotx.core.extensions.setTextOrHide +import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.features.home.AvatarRenderer +import im.vector.riotx.features.themes.ThemeUtils /** * A room preview for bottom sheet. @@ -36,22 +41,46 @@ import im.vector.riotx.features.home.AvatarRenderer @EpoxyModelClass(layout = R.layout.item_bottom_sheet_room_preview) abstract class BottomSheetRoomPreviewItem : VectorEpoxyModel() { - @EpoxyAttribute - lateinit var avatarRenderer: AvatarRenderer - @EpoxyAttribute - lateinit var matrixItem: MatrixItem + @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer + @EpoxyAttribute lateinit var matrixItem: MatrixItem + @EpoxyAttribute lateinit var stringProvider: StringProvider + @EpoxyAttribute var izFavorite: Boolean = false @EpoxyAttribute var settingsClickListener: ClickListener? = null + @EpoxyAttribute var favoriteClickListener: ClickListener? = null override fun bind(holder: Holder) { avatarRenderer.render(matrixItem, holder.avatar) holder.avatar.onClick(settingsClickListener) holder.roomName.setTextOrHide(matrixItem.displayName) + setFavoriteState(holder, izFavorite) + + holder.roomFavorite.setOnClickListener { + // Immediate echo + setFavoriteState(holder, !izFavorite) + // And do the action + favoriteClickListener?.invoke() + } holder.roomSettings.onClick(settingsClickListener) } + private fun setFavoriteState(holder: Holder, isFavorite: Boolean) { + val tintColor: Int + if (isFavorite) { + holder.roomFavorite.contentDescription = stringProvider.getString(R.string.room_list_quick_actions_favorite_remove) + holder.roomFavorite.setImageResource(R.drawable.ic_star_green_24dp) + tintColor = ContextCompat.getColor(holder.view.context, R.color.riotx_accent) + } else { + holder.roomFavorite.contentDescription = stringProvider.getString(R.string.room_list_quick_actions_favorite_add) + holder.roomFavorite.setImageResource(R.drawable.ic_star_24dp) + tintColor = ThemeUtils.getColor(holder.view.context, R.attr.riotx_text_secondary) + } + ImageViewCompat.setImageTintList(holder.roomFavorite, ColorStateList.valueOf(tintColor)) + } + class Holder : VectorEpoxyHolder() { val avatar by bind(R.id.bottomSheetRoomPreviewAvatar) val roomName by bind(R.id.bottomSheetRoomPreviewName) + val roomFavorite by bind(R.id.bottomSheetRoomPreviewFavorite) val roomSettings by bind(R.id.bottomSheetRoomPreviewSettings) } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/riotx/features/home/ShortcutsHandler.kt index 657942457e..e951939c38 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/ShortcutsHandler.kt @@ -58,7 +58,7 @@ class ShortcutsHandler @Inject constructor( .observeOn(Schedulers.computation()) .subscribe { rooms -> val shortcuts = rooms - .filter { room -> room.tags.any { it.name == RoomTag.ROOM_TAG_FAVOURITE } } + .filter { room -> room.isFavorite } .take(n = 4) // Android only allows us to create 4 shortcuts .map { room -> val intent = RoomDetailActivity.shortcutIntent(context, room.roomId) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListAction.kt index 9db7374169..44ae10c85d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListAction.kt @@ -27,6 +27,7 @@ sealed class RoomListAction : VectorViewModelAction { data class RejectInvitation(val roomSummary: RoomSummary) : RoomListAction() data class FilterWith(val filter: String) : RoomListAction() data class ChangeRoomNotificationState(val roomId: String, val notificationState: RoomNotificationState) : RoomListAction() + data class ToggleFavorite(val roomId: String) : RoomListAction() data class LeaveRoom(val roomId: String) : RoomListAction() object MarkAllRoomsRead : RoomListAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt index f4db464e6a..ed7c09d578 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt @@ -230,6 +230,9 @@ class RoomListFragment @Inject constructor( is RoomListQuickActionsSharedAction.Settings -> { navigator.openRoomProfile(requireActivity(), quickAction.roomId) } + is RoomListQuickActionsSharedAction.Favorite -> { + roomListViewModel.handle(RoomListAction.ToggleFavorite(quickAction.roomId)) + } is RoomListQuickActionsSharedAction.Leave -> { AlertDialog.Builder(requireContext()) .setTitle(R.string.room_participants_leave_prompt_title) @@ -239,8 +242,9 @@ class RoomListFragment @Inject constructor( } .setNegativeButton(R.string.cancel, null) .show() + Unit } - } + }.exhaustive } override fun invalidate() = withState(roomListViewModel) { state -> diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt index 81c75ed1d7..4f4a6af260 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt @@ -21,6 +21,7 @@ import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.NoOpMatrixCallback +import im.vector.matrix.android.api.extensions.orFalse 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 @@ -67,6 +68,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, is RoomListAction.MarkAllRoomsRead -> handleMarkAllRoomsRead() is RoomListAction.LeaveRoom -> handleLeaveRoom(action) is RoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action) + is RoomListAction.ToggleFavorite -> handleToggleFavorite(action) }.exhaustive } @@ -205,6 +207,22 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, }) } + private fun handleToggleFavorite(action: RoomListAction.ToggleFavorite) { + session.getRoom(action.roomId)?.let { + val callback = object : MatrixCallback { + override fun onFailure(failure: Throwable) { + _viewEvents.post(RoomListViewEvents.Failure(failure)) + } + } + if (it.roomSummary()?.isFavorite == false) { + // Set favorite tag. We do not handle the order for the moment + it.addTag(RoomTag.ROOM_TAG_FAVOURITE, 0.5, callback) + } else { + it.deleteTag(RoomTag.ROOM_TAG_FAVOURITE, callback) + } + } + } + private fun handleLeaveRoom(action: RoomListAction.LeaveRoom) { _viewEvents.post(RoomListViewEvents.Loading(null)) session.getRoom(action.roomId)?.leave(null, object : MatrixCallback { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt index 6148b3c725..32dc038222 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt @@ -86,7 +86,11 @@ class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), R override fun didSelectMenuAction(quickAction: RoomListQuickActionsSharedAction) { sharedActionViewModel.post(quickAction) - dismiss() + // Do not dismiss for all the actions + when (quickAction) { + is RoomListQuickActionsSharedAction.Favorite -> Unit + else -> dismiss() + } } companion object { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsEpoxyController.kt index 132da9341b..74475406ed 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsEpoxyController.kt @@ -22,14 +22,17 @@ import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetActionItem import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetRoomPreviewItem import im.vector.riotx.core.epoxy.dividerItem +import im.vector.riotx.core.resources.StringProvider 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() { +class RoomListQuickActionsEpoxyController @Inject constructor( + private val avatarRenderer: AvatarRenderer, + private val stringProvider: StringProvider +) : TypedEpoxyController() { var listener: Listener? = null @@ -38,12 +41,15 @@ class RoomListQuickActionsEpoxyController @Inject constructor(private val avatar val showAll = state.mode == RoomListActionsArgs.Mode.FULL if (showAll) { - // Preview + // Preview, favorite, settings bottomSheetRoomPreviewItem { id("room_preview") avatarRenderer(avatarRenderer) matrixItem(roomSummary.toMatrixItem()) + stringProvider(stringProvider) + izFavorite(roomSummary.isFavorite) settingsClickListener { listener?.didSelectMenuAction(RoomListQuickActionsSharedAction.Settings(roomSummary.roomId)) } + favoriteClickListener { listener?.didSelectMenuAction(RoomListQuickActionsSharedAction.Favorite(roomSummary.roomId)) } } // Notifications @@ -73,8 +79,7 @@ class RoomListQuickActionsEpoxyController @Inject constructor(private val avatar is RoomListQuickActionsSharedAction.NotificationsAll -> roomNotificationState == RoomNotificationState.ALL_MESSAGES is RoomListQuickActionsSharedAction.NotificationsMentionsOnly -> roomNotificationState == RoomNotificationState.MENTIONS_ONLY is RoomListQuickActionsSharedAction.NotificationsMute -> roomNotificationState == RoomNotificationState.MUTE - is RoomListQuickActionsSharedAction.Settings, - is RoomListQuickActionsSharedAction.Leave -> false + else -> false } return bottomSheetActionItem { id("action_$index") diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsSharedAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsSharedAction.kt index ca006ddd7d..5da15bf6a3 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsSharedAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsSharedAction.kt @@ -52,6 +52,10 @@ sealed class RoomListQuickActionsSharedAction( R.drawable.ic_room_actions_settings ) + data class Favorite(val roomId: String) : RoomListQuickActionsSharedAction( + R.string.room_list_quick_actions_favorite_add, + R.drawable.ic_star_24dp) + data class Leave(val roomId: String) : RoomListQuickActionsSharedAction( R.string.room_list_quick_actions_leave, R.drawable.ic_room_actions_leave, diff --git a/vector/src/main/res/drawable/ic_star_24dp.xml b/vector/src/main/res/drawable/ic_star_24dp.xml new file mode 100644 index 0000000000..3113cf8f47 --- /dev/null +++ b/vector/src/main/res/drawable/ic_star_24dp.xml @@ -0,0 +1,14 @@ + + + diff --git a/vector/src/main/res/drawable/ic_star_green_24dp.xml b/vector/src/main/res/drawable/ic_star_green_24dp.xml new file mode 100644 index 0000000000..1fd974386f --- /dev/null +++ b/vector/src/main/res/drawable/ic_star_green_24dp.xml @@ -0,0 +1,14 @@ + + + diff --git a/vector/src/main/res/layout/item_bottom_sheet_room_preview.xml b/vector/src/main/res/layout/item_bottom_sheet_room_preview.xml index 964875bd08..06df565d8b 100644 --- a/vector/src/main/res/layout/item_bottom_sheet_room_preview.xml +++ b/vector/src/main/res/layout/item_bottom_sheet_room_preview.xml @@ -11,13 +11,13 @@ 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_marginEnd="@dimen/layout_horizontal_margin" android:layout_marginBottom="8dp" android:adjustViewBounds="true" android:background="@drawable/circle" - android:importantForAccessibility="no" android:contentDescription="@string/avatar" + android:importantForAccessibility="no" android:scaleType="centerCrop" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -39,19 +39,33 @@ android:textSize="14sp" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="@+id/bottomSheetRoomPreviewAvatar" - app:layout_constraintEnd_toStartOf="@+id/bottomSheetRoomPreviewSettings" + app:layout_constraintEnd_toStartOf="@+id/bottomSheetRoomPreviewFavorite" app:layout_constraintStart_toEndOf="@id/bottomSheetRoomPreviewAvatar" app:layout_constraintTop_toTopOf="@id/bottomSheetRoomPreviewAvatar" tools:text="@tools:sample/full_names" /> + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index de54771187..9d021ece79 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1821,6 +1821,8 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming "Mentions only" "Mute" "Settings" + "Add to favorites" + "Remove from favorites" "Leave the room" "%1$s made no changes" "You made no changes"