Add capability to add and remove a room from the favorites (#1217)

This commit is contained in:
Benoit Marty 2020-06-10 00:21:02 +02:00
parent 0a7f77ea16
commit d8317f7439
25 changed files with 395 additions and 20 deletions

View File

@ -12,6 +12,7 @@ Improvements 🙌:
- Correctly handle SSO login redirection - Correctly handle SSO login redirection
- SSO login is now performed in the default browser, or in Chrome Custom tab if available (#1400) - SSO login is now performed in the default browser, or in Chrome Custom tab if available (#1400)
- Improve checking of homeserver version support (#1442) - Improve checking of homeserver version support (#1442)
- Add capability to add and remove a room from the favorites (#1217)
Bugfix 🐛: Bugfix 🐛:
- Switch theme is not fully taken into account without restarting the app - Switch theme is not fully taken into account without restarting the app

View File

@ -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.DraftService
import im.vector.matrix.android.api.session.room.send.SendService 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.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.timeline.TimelineService
import im.vector.matrix.android.api.session.room.typing.TypingService import im.vector.matrix.android.api.session.room.typing.TypingService
import im.vector.matrix.android.api.session.room.uploads.UploadsService import im.vector.matrix.android.api.session.room.uploads.UploadsService
@ -41,6 +42,7 @@ interface Room :
DraftService, DraftService,
ReadService, ReadService,
TypingService, TypingService,
TagsService,
MembershipService, MembershipService,
StateService, StateService,
UploadsService, UploadsService,

View File

@ -59,6 +59,9 @@ data class RoomSummary constructor(
val hasNewMessages: Boolean val hasNewMessages: Boolean
get() = notificationCount != 0 get() = notificationCount != 0
val isFavorite: Boolean
get() = tags.any { it.name == RoomTag.ROOM_TAG_FAVOURITE }
companion object { companion object {
const val NOT_IN_BREADCRUMBS = -1 const val NOT_IN_BREADCRUMBS = -1
} }

View File

@ -22,9 +22,8 @@ data class RoomTag(
) { ) {
companion object { companion object {
val ROOM_TAG_FAVOURITE = "m.favourite" const val ROOM_TAG_FAVOURITE = "m.favourite"
val ROOM_TAG_LOW_PRIORITY = "m.lowpriority" const val ROOM_TAG_LOW_PRIORITY = "m.lowpriority"
val ROOM_TAG_NO_TAG = "m.recent" const val ROOM_TAG_SERVER_NOTICE = "m.server_notice"
val ROOM_TAG_SERVER_NOTICE = "m.server_notice"
} }
} }

View File

@ -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<Unit>): Cancelable
/**
* Remove tag form a room
*/
fun deleteTag(tag: String, callback: MatrixCallback<Unit>): Cancelable
}

View File

@ -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.DraftService
import im.vector.matrix.android.api.session.room.send.SendService 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.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.timeline.TimelineService
import im.vector.matrix.android.api.session.room.typing.TypingService import im.vector.matrix.android.api.session.room.typing.TypingService
import im.vector.matrix.android.api.session.room.uploads.UploadsService 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 reportingService: ReportingService,
private val readService: ReadService, private val readService: ReadService,
private val typingService: TypingService, private val typingService: TypingService,
private val tagsService: TagsService,
private val cryptoService: CryptoService, private val cryptoService: CryptoService,
private val relationService: RelationService, private val relationService: RelationService,
private val roomMembersService: MembershipService, private val roomMembersService: MembershipService,
@ -74,6 +76,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
ReportingService by reportingService, ReportingService by reportingService,
ReadService by readService, ReadService by readService,
TypingService by typingService, TypingService by typingService,
TagsService by tagsService,
RelationService by relationService, RelationService by relationService,
MembershipService by roomMembersService, MembershipService by roomMembersService,
RoomPushRuleService by roomPushRuleService { RoomPushRuleService by roomPushRuleService {

View File

@ -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.relation.RelationsResponse
import im.vector.matrix.android.internal.session.room.reporting.ReportContentBody 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.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.EventContextResponse
import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse
import im.vector.matrix.android.internal.session.room.typing.TypingBody import im.vector.matrix.android.internal.session.room.typing.TypingBody
import retrofit2.Call import retrofit2.Call
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Headers import retrofit2.http.Headers
import retrofit2.http.POST import retrofit2.http.POST
@ -288,4 +290,25 @@ internal interface RoomAPI {
fun sendTypingState(@Path("roomId") roomId: String, fun sendTypingState(@Path("roomId") roomId: String,
@Path("userId") userId: String, @Path("userId") userId: String,
@Body body: TypingBody): Call<Unit> @Body body: TypingBody): Call<Unit>
/**
* 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<Unit>
/**
* 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<Unit>
} }

View File

@ -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.send.DefaultSendService
import im.vector.matrix.android.internal.session.room.state.DefaultStateService 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.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.timeline.DefaultTimelineService
import im.vector.matrix.android.internal.session.room.typing.DefaultTypingService import im.vector.matrix.android.internal.session.room.typing.DefaultTypingService
import im.vector.matrix.android.internal.session.room.uploads.DefaultUploadsService 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 reportingServiceFactory: DefaultReportingService.Factory,
private val readServiceFactory: DefaultReadService.Factory, private val readServiceFactory: DefaultReadService.Factory,
private val typingServiceFactory: DefaultTypingService.Factory, private val typingServiceFactory: DefaultTypingService.Factory,
private val tagsServiceFactory: DefaultTagsService.Factory,
private val relationServiceFactory: DefaultRelationService.Factory, private val relationServiceFactory: DefaultRelationService.Factory,
private val membershipServiceFactory: DefaultMembershipService.Factory, private val membershipServiceFactory: DefaultMembershipService.Factory,
private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory, private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
@ -72,6 +74,7 @@ internal class DefaultRoomFactory @Inject constructor(private val monarchy: Mona
reportingService = reportingServiceFactory.create(roomId), reportingService = reportingServiceFactory.create(roomId),
readService = readServiceFactory.create(roomId), readService = readServiceFactory.create(roomId),
typingService = typingServiceFactory.create(roomId), typingService = typingServiceFactory.create(roomId),
tagsService = tagsServiceFactory.create(roomId),
cryptoService = cryptoService, cryptoService = cryptoService,
relationService = relationServiceFactory.create(roomId), relationService = relationServiceFactory.create(roomId),
roomMembersService = membershipServiceFactory.create(roomId), roomMembersService = membershipServiceFactory.create(roomId),

View File

@ -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.reporting.ReportContentTask
import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask
import im.vector.matrix.android.internal.session.room.state.SendStateTask import im.vector.matrix.android.internal.session.room.state.SendStateTask
import im.vector.matrix.android.internal.session.room.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.DefaultFetchNextTokenAndPaginateTask
import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask
import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask
@ -186,4 +190,10 @@ internal abstract class RoomModule {
@Binds @Binds
abstract fun bindGetUploadsTask(task: DefaultGetUploadsTask): GetUploadsTask abstract fun bindGetUploadsTask(task: DefaultGetUploadsTask): GetUploadsTask
@Binds
abstract fun bindAddTagToRoomTask(task: DefaultAddTagToRoomTask): AddTagToRoomTask
@Binds
abstract fun bindDeleteTagFromRoomTask(task: DefaultDeleteTagFromRoomTask): DeleteTagFromRoomTask
} }

View File

@ -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<AddTagToRoomTask.Params, Unit> {
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<Unit>(eventBus) {
apiCall = roomAPI.putTag(
userId = userId,
roomId = params.roomId,
tag = params.tag,
body = TagBody(
order = params.order
)
)
}
}
}

View File

@ -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<Unit>): Cancelable {
val params = AddTagToRoomTask.Params(roomId, tag, order)
return addTagToRoomTask
.configureWith(params) {
this.callback = callback
}
.executeBy(taskExecutor)
}
override fun deleteTag(tag: String, callback: MatrixCallback<Unit>): Cancelable {
val params = DeleteTagFromRoomTask.Params(roomId, tag)
return deleteTagFromRoomTask
.configureWith(params) {
this.callback = callback
}
.executeBy(taskExecutor)
}
}

View File

@ -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<DeleteTagFromRoomTask.Params, Unit> {
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<Unit>(eventBus) {
apiCall = roomAPI.deleteTag(
userId = userId,
roomId = params.roomId,
tag = params.tag
)
}
}
}

View File

@ -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?
)

View File

@ -16,9 +16,12 @@
*/ */
package im.vector.riotx.core.epoxy.bottomsheet package im.vector.riotx.core.epoxy.bottomsheet
import android.content.res.ColorStateList
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.widget.ImageViewCompat
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.matrix.android.api.util.MatrixItem 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.VectorEpoxyModel
import im.vector.riotx.core.epoxy.onClick import im.vector.riotx.core.epoxy.onClick
import im.vector.riotx.core.extensions.setTextOrHide 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.home.AvatarRenderer
import im.vector.riotx.features.themes.ThemeUtils
/** /**
* A room preview for bottom sheet. * 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) @EpoxyModelClass(layout = R.layout.item_bottom_sheet_room_preview)
abstract class BottomSheetRoomPreviewItem : VectorEpoxyModel<BottomSheetRoomPreviewItem.Holder>() { abstract class BottomSheetRoomPreviewItem : VectorEpoxyModel<BottomSheetRoomPreviewItem.Holder>() {
@EpoxyAttribute @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute @EpoxyAttribute lateinit var stringProvider: StringProvider
lateinit var matrixItem: MatrixItem @EpoxyAttribute var izFavorite: Boolean = false
@EpoxyAttribute var settingsClickListener: ClickListener? = null @EpoxyAttribute var settingsClickListener: ClickListener? = null
@EpoxyAttribute var favoriteClickListener: ClickListener? = null
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
avatarRenderer.render(matrixItem, holder.avatar) avatarRenderer.render(matrixItem, holder.avatar)
holder.avatar.onClick(settingsClickListener) holder.avatar.onClick(settingsClickListener)
holder.roomName.setTextOrHide(matrixItem.displayName) 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) 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() { class Holder : VectorEpoxyHolder() {
val avatar by bind<ImageView>(R.id.bottomSheetRoomPreviewAvatar) val avatar by bind<ImageView>(R.id.bottomSheetRoomPreviewAvatar)
val roomName by bind<TextView>(R.id.bottomSheetRoomPreviewName) val roomName by bind<TextView>(R.id.bottomSheetRoomPreviewName)
val roomFavorite by bind<ImageView>(R.id.bottomSheetRoomPreviewFavorite)
val roomSettings by bind<View>(R.id.bottomSheetRoomPreviewSettings) val roomSettings by bind<View>(R.id.bottomSheetRoomPreviewSettings)
} }
} }

View File

@ -58,7 +58,7 @@ class ShortcutsHandler @Inject constructor(
.observeOn(Schedulers.computation()) .observeOn(Schedulers.computation())
.subscribe { rooms -> .subscribe { rooms ->
val shortcuts = 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 .take(n = 4) // Android only allows us to create 4 shortcuts
.map { room -> .map { room ->
val intent = RoomDetailActivity.shortcutIntent(context, room.roomId) val intent = RoomDetailActivity.shortcutIntent(context, room.roomId)

View File

@ -27,6 +27,7 @@ sealed class RoomListAction : VectorViewModelAction {
data class RejectInvitation(val roomSummary: RoomSummary) : RoomListAction() data class RejectInvitation(val roomSummary: RoomSummary) : RoomListAction()
data class FilterWith(val filter: String) : RoomListAction() data class FilterWith(val filter: String) : RoomListAction()
data class ChangeRoomNotificationState(val roomId: String, val notificationState: RoomNotificationState) : 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() data class LeaveRoom(val roomId: String) : RoomListAction()
object MarkAllRoomsRead : RoomListAction() object MarkAllRoomsRead : RoomListAction()
} }

View File

@ -230,6 +230,9 @@ class RoomListFragment @Inject constructor(
is RoomListQuickActionsSharedAction.Settings -> { is RoomListQuickActionsSharedAction.Settings -> {
navigator.openRoomProfile(requireActivity(), quickAction.roomId) navigator.openRoomProfile(requireActivity(), quickAction.roomId)
} }
is RoomListQuickActionsSharedAction.Favorite -> {
roomListViewModel.handle(RoomListAction.ToggleFavorite(quickAction.roomId))
}
is RoomListQuickActionsSharedAction.Leave -> { is RoomListQuickActionsSharedAction.Leave -> {
AlertDialog.Builder(requireContext()) AlertDialog.Builder(requireContext())
.setTitle(R.string.room_participants_leave_prompt_title) .setTitle(R.string.room_participants_leave_prompt_title)
@ -239,8 +242,9 @@ class RoomListFragment @Inject constructor(
} }
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show() .show()
Unit
} }
} }.exhaustive
} }
override fun invalidate() = withState(roomListViewModel) { state -> override fun invalidate() = withState(roomListViewModel) { state ->

View File

@ -21,6 +21,7 @@ import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.NoOpMatrixCallback 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.Session
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary 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.MarkAllRoomsRead -> handleMarkAllRoomsRead()
is RoomListAction.LeaveRoom -> handleLeaveRoom(action) is RoomListAction.LeaveRoom -> handleLeaveRoom(action)
is RoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action) is RoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
is RoomListAction.ToggleFavorite -> handleToggleFavorite(action)
}.exhaustive }.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<Unit> {
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) { private fun handleLeaveRoom(action: RoomListAction.LeaveRoom) {
_viewEvents.post(RoomListViewEvents.Loading(null)) _viewEvents.post(RoomListViewEvents.Loading(null))
session.getRoom(action.roomId)?.leave(null, object : MatrixCallback<Unit> { session.getRoom(action.roomId)?.leave(null, object : MatrixCallback<Unit> {

View File

@ -86,7 +86,11 @@ class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), R
override fun didSelectMenuAction(quickAction: RoomListQuickActionsSharedAction) { override fun didSelectMenuAction(quickAction: RoomListQuickActionsSharedAction) {
sharedActionViewModel.post(quickAction) sharedActionViewModel.post(quickAction)
dismiss() // Do not dismiss for all the actions
when (quickAction) {
is RoomListQuickActionsSharedAction.Favorite -> Unit
else -> dismiss()
}
} }
companion object { companion object {

View File

@ -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.bottomSheetActionItem
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetRoomPreviewItem import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetRoomPreviewItem
import im.vector.riotx.core.epoxy.dividerItem import im.vector.riotx.core.epoxy.dividerItem
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import javax.inject.Inject import javax.inject.Inject
/** /**
* Epoxy controller for room list actions * Epoxy controller for room list actions
*/ */
class RoomListQuickActionsEpoxyController @Inject constructor(private val avatarRenderer: AvatarRenderer) class RoomListQuickActionsEpoxyController @Inject constructor(
: TypedEpoxyController<RoomListQuickActionsState>() { private val avatarRenderer: AvatarRenderer,
private val stringProvider: StringProvider
) : TypedEpoxyController<RoomListQuickActionsState>() {
var listener: Listener? = null var listener: Listener? = null
@ -38,12 +41,15 @@ class RoomListQuickActionsEpoxyController @Inject constructor(private val avatar
val showAll = state.mode == RoomListActionsArgs.Mode.FULL val showAll = state.mode == RoomListActionsArgs.Mode.FULL
if (showAll) { if (showAll) {
// Preview // Preview, favorite, settings
bottomSheetRoomPreviewItem { bottomSheetRoomPreviewItem {
id("room_preview") id("room_preview")
avatarRenderer(avatarRenderer) avatarRenderer(avatarRenderer)
matrixItem(roomSummary.toMatrixItem()) matrixItem(roomSummary.toMatrixItem())
stringProvider(stringProvider)
izFavorite(roomSummary.isFavorite)
settingsClickListener { listener?.didSelectMenuAction(RoomListQuickActionsSharedAction.Settings(roomSummary.roomId)) } settingsClickListener { listener?.didSelectMenuAction(RoomListQuickActionsSharedAction.Settings(roomSummary.roomId)) }
favoriteClickListener { listener?.didSelectMenuAction(RoomListQuickActionsSharedAction.Favorite(roomSummary.roomId)) }
} }
// Notifications // Notifications
@ -73,8 +79,7 @@ class RoomListQuickActionsEpoxyController @Inject constructor(private val avatar
is RoomListQuickActionsSharedAction.NotificationsAll -> roomNotificationState == RoomNotificationState.ALL_MESSAGES is RoomListQuickActionsSharedAction.NotificationsAll -> roomNotificationState == RoomNotificationState.ALL_MESSAGES
is RoomListQuickActionsSharedAction.NotificationsMentionsOnly -> roomNotificationState == RoomNotificationState.MENTIONS_ONLY is RoomListQuickActionsSharedAction.NotificationsMentionsOnly -> roomNotificationState == RoomNotificationState.MENTIONS_ONLY
is RoomListQuickActionsSharedAction.NotificationsMute -> roomNotificationState == RoomNotificationState.MUTE is RoomListQuickActionsSharedAction.NotificationsMute -> roomNotificationState == RoomNotificationState.MUTE
is RoomListQuickActionsSharedAction.Settings, else -> false
is RoomListQuickActionsSharedAction.Leave -> false
} }
return bottomSheetActionItem { return bottomSheetActionItem {
id("action_$index") id("action_$index")

View File

@ -52,6 +52,10 @@ sealed class RoomListQuickActionsSharedAction(
R.drawable.ic_room_actions_settings 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( data class Leave(val roomId: String) : RoomListQuickActionsSharedAction(
R.string.room_list_quick_actions_leave, R.string.room_list_quick_actions_leave,
R.drawable.ic_room_actions_leave, R.drawable.ic_room_actions_leave,

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,2L15.09,8.26L22,9.27L17,14.14L18.18,21.02L12,17.77L5.82,21.02L7,14.14L2,9.27L8.91,8.26L12,2Z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#03B381"
android:fillType="evenOdd"
android:pathData="M12,2L15.09,8.26L22,9.27L17,14.14L18.18,21.02L12,17.77L5.82,21.02L7,14.14L2,9.27L8.91,8.26L12,2Z"
android:strokeWidth="2"
android:strokeColor="#03B381"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>

View File

@ -11,13 +11,13 @@
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginStart="@dimen/layout_horizontal_margin" android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:background="@drawable/circle" android:background="@drawable/circle"
android:importantForAccessibility="no"
android:contentDescription="@string/avatar" android:contentDescription="@string/avatar"
android:importantForAccessibility="no"
android:scaleType="centerCrop" android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -39,19 +39,33 @@
android:textSize="14sp" android:textSize="14sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/bottomSheetRoomPreviewAvatar" 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_constraintStart_toEndOf="@id/bottomSheetRoomPreviewAvatar"
app:layout_constraintTop_toTopOf="@id/bottomSheetRoomPreviewAvatar" app:layout_constraintTop_toTopOf="@id/bottomSheetRoomPreviewAvatar"
tools:text="@tools:sample/full_names" /> tools:text="@tools:sample/full_names" />
<ImageView
android:id="@+id/bottomSheetRoomPreviewFavorite"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackground"
android:scaleType="centerInside"
app:layout_constraintBottom_toBottomOf="@+id/bottomSheetRoomPreviewAvatar"
app:layout_constraintEnd_toStartOf="@+id/bottomSheetRoomPreviewSettings"
app:layout_constraintTop_toTopOf="@id/bottomSheetRoomPreviewAvatar"
tools:contentDescription="@string/room_list_quick_actions_favorite_add"
tools:src="@drawable/ic_star_24dp"
tools:tint="?riotx_text_secondary" />
<ImageView <ImageView
android:id="@+id/bottomSheetRoomPreviewSettings" android:id="@+id/bottomSheetRoomPreviewSettings"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:contentDescription="@string/room_list_quick_actions_settings"
android:scaleType="centerInside" android:scaleType="centerInside"
android:src="@drawable/ic_room_actions_settings" android:src="@drawable/ic_room_actions_settings"
android:contentDescription="@string/room_list_quick_actions_settings" android:tint="?riotx_text_secondary"
app:layout_constraintBottom_toBottomOf="@+id/bottomSheetRoomPreviewAvatar" app:layout_constraintBottom_toBottomOf="@+id/bottomSheetRoomPreviewAvatar"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/bottomSheetRoomPreviewAvatar" /> app:layout_constraintTop_toTopOf="@id/bottomSheetRoomPreviewAvatar" />

View File

@ -1821,6 +1821,8 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
<string name="room_list_quick_actions_notifications_mentions">"Mentions only"</string> <string name="room_list_quick_actions_notifications_mentions">"Mentions only"</string>
<string name="room_list_quick_actions_notifications_mute">"Mute"</string> <string name="room_list_quick_actions_notifications_mute">"Mute"</string>
<string name="room_list_quick_actions_settings">"Settings"</string> <string name="room_list_quick_actions_settings">"Settings"</string>
<string name="room_list_quick_actions_favorite_add">"Add to favorites"</string>
<string name="room_list_quick_actions_favorite_remove">"Remove from favorites"</string>
<string name="room_list_quick_actions_leave">"Leave the room"</string> <string name="room_list_quick_actions_leave">"Leave the room"</string>
<string name="notice_member_no_changes">"%1$s made no changes"</string> <string name="notice_member_no_changes">"%1$s made no changes"</string>
<string name="notice_member_no_changes_by_you">"You made no changes"</string> <string name="notice_member_no_changes_by_you">"You made no changes"</string>