Merge pull request #6749 from vector-im/feature/eric/space-list-modal

Adds Space List Bottom Sheet
This commit is contained in:
Eric Decanini 2022-08-17 10:51:56 +02:00 committed by GitHub
commit 4fedafc1be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 896 additions and 165 deletions

1
changelog.d/6749.wip Normal file
View file

@ -0,0 +1 @@
Adds space list bottom sheet for new app layout

View file

@ -34,6 +34,7 @@ import im.vector.app.features.home.HomeSharedActionViewModel
import im.vector.app.features.home.room.detail.RoomDetailSharedActionViewModel
import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
import im.vector.app.features.home.room.list.actions.RoomListSharedActionViewModel
import im.vector.app.features.reactions.EmojiChooserViewModel
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel
@ -157,4 +158,9 @@ interface ViewModelModule {
@IntoMap
@ViewModelKey(SpacePeopleSharedActionViewModel::class)
fun bindSpacePeopleSharedActionViewModel(viewModel: SpacePeopleSharedActionViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(RoomListSharedActionViewModel::class)
fun bindRoomListSharedActionViewModel(viewModel: RoomListSharedActionViewModel): ViewModel
}

View file

@ -37,6 +37,7 @@ import im.vector.app.features.themes.ThemeUtils
@EpoxyModelClass
abstract class HomeSpaceSummaryItem : VectorEpoxyModel<HomeSpaceSummaryItem.Holder>(R.layout.item_space) {
@EpoxyAttribute var text: String = ""
@EpoxyAttribute var selected: Boolean = false
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null
@EpoxyAttribute var countState: UnreadCounterBadgeView.State = UnreadCounterBadgeView.State(0, false)

View file

@ -0,0 +1,69 @@
/*
* 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.app.features.grouplist
import android.content.res.ColorStateList
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.epoxy.onClick
import im.vector.app.core.platform.CheckableConstraintLayout
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
import im.vector.app.features.themes.ThemeUtils
@EpoxyModelClass
abstract class NewHomeSpaceSummaryItem : VectorEpoxyModel<NewHomeSpaceSummaryItem.Holder>(R.layout.item_new_space) {
@EpoxyAttribute var text: String = ""
@EpoxyAttribute var selected: Boolean = false
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null
@EpoxyAttribute var countState: UnreadCounterBadgeView.State = UnreadCounterBadgeView.State(0, false)
@EpoxyAttribute var showSeparator: Boolean = false
override fun getViewType() = R.id.space_item_home
override fun bind(holder: Holder) {
super.bind(holder)
holder.root.onClick(listener)
holder.name.text = holder.view.context.getString(R.string.all_chats)
holder.root.isChecked = selected
holder.root.context.resources
holder.avatar.background = ContextCompat.getDrawable(holder.view.context, R.drawable.new_space_home_background)
holder.avatar.backgroundTintList = ColorStateList.valueOf(
ColorUtils.setAlphaComponent(ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_tertiary), (255 * 0.3).toInt()))
holder.avatar.setImageResource(R.drawable.ic_space_home)
holder.avatar.imageTintList = ColorStateList.valueOf(ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_primary))
holder.avatar.scaleType = ImageView.ScaleType.CENTER_INSIDE
holder.unreadCounter.render(countState)
}
class Holder : VectorEpoxyHolder() {
val root by bind<CheckableConstraintLayout>(R.id.root)
val avatar by bind<ImageView>(R.id.avatar)
val name by bind<TextView>(R.id.name)
val unreadCounter by bind<UnreadCounterBadgeView>(R.id.unread_counter)
}
}

View file

@ -67,7 +67,7 @@ class NewHomeDetailFragment @Inject constructor(
private val alertManager: PopupAlertManager,
private val callManager: WebRtcCallManager,
private val vectorPreferences: VectorPreferences,
private val appStateHandler: SpaceStateHandler,
private val spaceStateHandler: SpaceStateHandler,
private val session: Session,
) : VectorBaseFragment<FragmentNewHomeDetailBinding>(),
KeysBackupBanner.Delegate,
@ -176,13 +176,13 @@ class NewHomeDetailFragment @Inject constructor(
}
private fun navigateBack() {
val previousSpaceId = appStateHandler.getSpaceBackstack().removeLastOrNull()
val parentSpaceId = appStateHandler.getCurrentSpace()?.flattenParentIds?.lastOrNull()
val previousSpaceId = spaceStateHandler.getSpaceBackstack().removeLastOrNull()
val parentSpaceId = spaceStateHandler.getCurrentSpace()?.flattenParentIds?.lastOrNull()
setCurrentSpace(previousSpaceId ?: parentSpaceId)
}
private fun setCurrentSpace(spaceId: String?) {
appStateHandler.setCurrentSpace(spaceId, isForwardNavigation = false)
spaceStateHandler.setCurrentSpace(spaceId, isForwardNavigation = false)
sharedActionViewModel.post(HomeActivitySharedAction.OnCloseSpace)
}
@ -205,7 +205,7 @@ class NewHomeDetailFragment @Inject constructor(
}
private fun refreshSpaceState() {
appStateHandler.getCurrentSpace()?.let {
spaceStateHandler.getCurrentSpace()?.let {
onSpaceChange(it)
}
}
@ -450,7 +450,7 @@ class NewHomeDetailFragment @Inject constructor(
return this
}
override fun onBackPressed(toolbarButton: Boolean) = if (appStateHandler.getCurrentSpace() != null) {
override fun onBackPressed(toolbarButton: Boolean) = if (spaceStateHandler.getCurrentSpace() != null) {
navigateBack()
true
} else {

View file

@ -25,8 +25,7 @@ sealed class RoomListQuickActionsSharedAction(
@StringRes val titleRes: Int,
@DrawableRes val iconResId: Int?,
val destructive: Boolean = false
) :
VectorSharedAction {
) : VectorSharedAction {
data class NotificationsAllNoisy(val roomId: String) : RoomListQuickActionsSharedAction(
R.string.room_list_quick_actions_notifications_all_noisy,

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2022 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.app.features.home.room.list.actions
import im.vector.app.core.platform.VectorSharedAction
sealed class RoomListSharedAction : VectorSharedAction {
object CloseBottomSheet : RoomListSharedAction()
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2022 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.app.features.home.room.list.actions
import im.vector.app.core.platform.VectorSharedActionViewModel
import javax.inject.Inject
class RoomListSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<RoomListSharedAction>()

View file

@ -43,9 +43,12 @@ import im.vector.app.features.home.room.list.RoomSummaryItemFactory
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
import im.vector.app.features.home.room.list.actions.RoomListSharedAction
import im.vector.app.features.home.room.list.actions.RoomListSharedActionViewModel
import im.vector.app.features.home.room.list.home.filter.HomeFilteredRoomsController
import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter
import im.vector.app.features.home.room.list.home.recent.RecentRoomCarouselController
import im.vector.app.features.spaces.SpaceListBottomSheet
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.session.room.model.RoomSummary
@ -62,10 +65,13 @@ class HomeRoomListFragment @Inject constructor(
RoomListListener {
private val roomListViewModel: HomeRoomListViewModel by fragmentViewModel()
private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel
private lateinit var sharedQuickActionsViewModel: RoomListQuickActionsSharedActionViewModel
private lateinit var sharedActionViewModel: RoomListSharedActionViewModel
private var concatAdapter = ConcatAdapter()
private var modelBuildListener: OnModelBuildFinishedListener? = null
private val spaceListBottomSheet = SpaceListBottomSheet()
private lateinit var stateRestorer: LayoutManagerStateRestorer
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomListBinding {
@ -74,15 +80,25 @@ class HomeRoomListFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider[RoomListQuickActionsSharedActionViewModel::class.java]
sharedActionViewModel
.stream()
.onEach { handleQuickActions(it) }
.launchIn(viewLifecycleOwner.lifecycleScope)
views.stateView.contentView = views.roomListView
views.stateView.state = StateView.State.Loading
setupObservers()
setupRecyclerView()
setupFabs()
}
private fun setupObservers() {
sharedQuickActionsViewModel = activityViewModelProvider[RoomListQuickActionsSharedActionViewModel::class.java]
sharedActionViewModel = activityViewModelProvider[RoomListSharedActionViewModel::class.java]
sharedActionViewModel
.stream()
.onEach(::handleSharedAction)
.launchIn(viewLifecycleOwner.lifecycleScope)
sharedQuickActionsViewModel
.stream()
.onEach(::handleQuickActions)
.launchIn(viewLifecycleOwner.lifecycleScope)
roomListViewModel.observeViewEvents {
when (it) {
@ -92,9 +108,42 @@ class HomeRoomListFragment @Inject constructor(
is HomeRoomListViewEvents.Done -> Unit
}
}
}
setupRecyclerView()
setupFabs()
private fun handleSharedAction(action: RoomListSharedAction) {
when (action) {
RoomListSharedAction.CloseBottomSheet -> spaceListBottomSheet.dismiss()
}
}
private fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) {
when (quickAction) {
is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> {
roomListViewModel.handle(HomeRoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.ALL_MESSAGES_NOISY))
}
is RoomListQuickActionsSharedAction.NotificationsAll -> {
roomListViewModel.handle(HomeRoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.ALL_MESSAGES))
}
is RoomListQuickActionsSharedAction.NotificationsMentionsOnly -> {
roomListViewModel.handle(HomeRoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.MENTIONS_ONLY))
}
is RoomListQuickActionsSharedAction.NotificationsMute -> {
roomListViewModel.handle(HomeRoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.MUTE))
}
is RoomListQuickActionsSharedAction.Settings -> {
navigator.openRoomProfile(requireActivity(), quickAction.roomId)
}
is RoomListQuickActionsSharedAction.Favorite -> {
roomListViewModel.handle(HomeRoomListAction.ToggleTag(quickAction.roomId, RoomTag.ROOM_TAG_FAVOURITE))
}
is RoomListQuickActionsSharedAction.LowPriority -> {
roomListViewModel.handle(HomeRoomListAction.ToggleTag(quickAction.roomId, RoomTag.ROOM_TAG_LOW_PRIORITY))
}
is RoomListQuickActionsSharedAction.Leave -> {
roomListViewModel.handle(HomeRoomListAction.LeaveRoom(quickAction.roomId))
promptLeaveRoom(quickAction.roomId)
}
}
}
private fun setupRecyclerView() {
@ -121,7 +170,8 @@ class HomeRoomListFragment @Inject constructor(
}
views.newLayoutOpenSpacesButton.setOnClickListener {
// Click action for open spaces modal goes here (Issue #6499)
// Click action for open spaces modal goes here
spaceListBottomSheet.show(requireActivity().supportFragmentManager, SpaceListBottomSheet.TAG)
}
// Hide FABs when list is scrolling
@ -158,36 +208,6 @@ class HomeRoomListFragment @Inject constructor(
}
}
private fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) {
when (quickAction) {
is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> {
roomListViewModel.handle(HomeRoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.ALL_MESSAGES_NOISY))
}
is RoomListQuickActionsSharedAction.NotificationsAll -> {
roomListViewModel.handle(HomeRoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.ALL_MESSAGES))
}
is RoomListQuickActionsSharedAction.NotificationsMentionsOnly -> {
roomListViewModel.handle(HomeRoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.MENTIONS_ONLY))
}
is RoomListQuickActionsSharedAction.NotificationsMute -> {
roomListViewModel.handle(HomeRoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.MUTE))
}
is RoomListQuickActionsSharedAction.Settings -> {
navigator.openRoomProfile(requireActivity(), quickAction.roomId)
}
is RoomListQuickActionsSharedAction.Favorite -> {
roomListViewModel.handle(HomeRoomListAction.ToggleTag(quickAction.roomId, RoomTag.ROOM_TAG_FAVOURITE))
}
is RoomListQuickActionsSharedAction.LowPriority -> {
roomListViewModel.handle(HomeRoomListAction.ToggleTag(quickAction.roomId, RoomTag.ROOM_TAG_LOW_PRIORITY))
}
is RoomListQuickActionsSharedAction.Leave -> {
roomListViewModel.handle(HomeRoomListAction.LeaveRoom(quickAction.roomId))
promptLeaveRoom(quickAction.roomId)
}
}
}
private fun promptLeaveRoom(roomId: String) {
val isPublicRoom = roomListViewModel.isPublicRoom(roomId)
val message = buildString {

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2021 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.app.features.spaces
import android.content.res.ColorStateList
import android.widget.ImageView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.epoxy.onClick
import im.vector.app.features.themes.ThemeUtils
@EpoxyModelClass
abstract class NewSpaceAddItem : VectorEpoxyModel<NewSpaceAddItem.Holder>(R.layout.item_new_space_add) {
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.view.onClick(listener)
holder.plus.imageTintList = ColorStateList.valueOf(ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_primary))
}
class Holder : VectorEpoxyHolder() {
val plus by bind<ImageView>(R.id.plus)
}
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2021 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.app.features.spaces
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
@EpoxyModelClass
abstract class NewSpaceListHeaderItem : VectorEpoxyModel<NewSpaceListHeaderItem.Holder>(R.layout.item_new_space_list_header) {
class Holder : VectorEpoxyHolder()
}

View file

@ -0,0 +1,149 @@
/*
* Copyright (c) 2021 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.app.features.spaces
import com.airbnb.epoxy.EpoxyController
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.grouplist.newHomeSpaceSummaryItem
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
class NewSpaceSummaryController @Inject constructor(
private val avatarRenderer: AvatarRenderer,
private val stringProvider: StringProvider,
) : EpoxyController() {
var callback: Callback? = null
private var viewState: SpaceListViewState? = null
private val subSpaceComparator: Comparator<SpaceChildInfo> = compareBy<SpaceChildInfo> { it.order }.thenBy { it.childRoomId }
fun update(viewState: SpaceListViewState) {
this.viewState = viewState
requestModelBuild()
}
override fun buildModels() {
val nonNullViewState = viewState ?: return
buildGroupModels(
nonNullViewState.spaces,
nonNullViewState.selectedSpace,
nonNullViewState.rootSpacesOrdered,
nonNullViewState.homeAggregateCount
)
}
private fun buildGroupModels(
spaceSummaries: List<RoomSummary>?,
selectedSpace: RoomSummary?,
rootSpaces: List<RoomSummary>?,
homeCount: RoomAggregateNotificationCount
) {
val host = this
newSpaceListHeaderItem {
id("space_list_header")
}
if (selectedSpace != null) {
addSubSpaces(selectedSpace, spaceSummaries, homeCount)
} else {
addHomeItem(true, homeCount)
addRootSpaces(rootSpaces)
}
newSpaceAddItem {
id("create")
listener { host.callback?.onAddSpaceSelected() }
}
}
private fun addHomeItem(selected: Boolean, homeCount: RoomAggregateNotificationCount) {
val host = this
newHomeSpaceSummaryItem {
id("space_home")
text(host.stringProvider.getString(R.string.all_chats))
selected(selected)
countState(UnreadCounterBadgeView.State(homeCount.totalCount, homeCount.isHighlight))
listener { host.callback?.onSpaceSelected(null) }
}
}
private fun addSubSpaces(
selectedSpace: RoomSummary,
spaceSummaries: List<RoomSummary>?,
homeCount: RoomAggregateNotificationCount,
) {
val host = this
val spaceChildren = selectedSpace.spaceChildren
var subSpacesAdded = false
spaceChildren?.sortedWith(subSpaceComparator)?.forEach { spaceChild ->
val subSpaceSummary = spaceSummaries?.firstOrNull { it.roomId == spaceChild.childRoomId } ?: return@forEach
if (subSpaceSummary.membership != Membership.INVITE) {
subSpacesAdded = true
newSpaceSummaryItem {
avatarRenderer(host.avatarRenderer)
id(subSpaceSummary.roomId)
matrixItem(subSpaceSummary.toMatrixItem())
selected(false)
listener { host.callback?.onSpaceSelected(subSpaceSummary) }
countState(
UnreadCounterBadgeView.State(
subSpaceSummary.notificationCount,
subSpaceSummary.highlightCount > 0
)
)
}
}
}
if (!subSpacesAdded) {
addHomeItem(false, homeCount)
}
}
private fun addRootSpaces(rootSpaces: List<RoomSummary>?) {
val host = this
rootSpaces
?.filter { it.membership != Membership.INVITE }
?.forEach { roomSummary ->
newSpaceSummaryItem {
avatarRenderer(host.avatarRenderer)
id(roomSummary.roomId)
matrixItem(roomSummary.toMatrixItem())
listener { host.callback?.onSpaceSelected(roomSummary) }
countState(UnreadCounterBadgeView.State(roomSummary.notificationCount, roomSummary.highlightCount > 0))
}
}
}
interface Callback {
fun onSpaceSelected(spaceSummary: RoomSummary?)
fun onSpaceInviteSelected(spaceSummary: RoomSummary)
fun onSpaceSettings(spaceSummary: RoomSummary)
fun onAddSpaceSelected()
fun sendFeedBack()
}
}

View file

@ -0,0 +1,63 @@
/*
* Copyright (c) 2021 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.app.features.spaces
import android.widget.ImageView
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.epoxy.onClick
import im.vector.app.core.platform.CheckableConstraintLayout
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
import org.matrix.android.sdk.api.util.MatrixItem
@EpoxyModelClass
abstract class NewSpaceSummaryItem : VectorEpoxyModel<NewSpaceSummaryItem.Holder>(R.layout.item_new_space) {
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var selected: Boolean = false
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null
@EpoxyAttribute var countState: UnreadCounterBadgeView.State = UnreadCounterBadgeView.State(0, false)
override fun bind(holder: Holder) {
super.bind(holder)
holder.rootView.onClick(listener)
holder.name.text = matrixItem.displayName
holder.rootView.isChecked = selected
avatarRenderer.render(matrixItem, holder.avatar)
holder.unreadCounter.render(countState)
}
override fun unbind(holder: Holder) {
avatarRenderer.clear(holder.avatar)
super.unbind(holder)
}
class Holder : VectorEpoxyHolder() {
val rootView by bind<CheckableConstraintLayout>(R.id.root)
val avatar by bind<ImageView>(R.id.avatar)
val name by bind<TextView>(R.id.name)
val unreadCounter by bind<UnreadCounterBadgeView>(R.id.unread_counter)
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2022 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.app.features.spaces
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import im.vector.app.R
import im.vector.app.core.extensions.replaceChildFragment
import im.vector.app.databinding.FragmentSpacesBottomSheetBinding
class SpaceListBottomSheet : BottomSheetDialogFragment() {
private lateinit var binding: FragmentSpacesBottomSheetBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentSpacesBottomSheetBinding.inflate(inflater, container, false)
if (savedInstanceState == null) {
replaceChildFragment(R.id.space_list, SpaceListFragment::class.java)
}
return binding.root
}
companion object {
const val TAG = "SpacesBottomSheet"
}
}

View file

@ -32,20 +32,29 @@ import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.StateView
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentSpaceListBinding
import im.vector.app.features.VectorFeatures
import im.vector.app.features.home.HomeActivitySharedAction
import im.vector.app.features.home.HomeSharedActionViewModel
import im.vector.app.features.home.room.list.actions.RoomListSharedAction
import im.vector.app.features.home.room.list.actions.RoomListSharedActionViewModel
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import javax.inject.Inject
/**
* This Fragment is displayed in the navigation drawer [im.vector.app.features.home.HomeDrawerFragment] and
* is displaying the space hierarchy, with some actions on Spaces.
*
* In the New App Layout this fragment will instead be displayed in a Bottom Sheet [SpaceListBottomSheet]
* and will only display spaces that are direct children of the currently selected space (or root spaces if none)
*/
class SpaceListFragment @Inject constructor(
private val spaceController: SpaceSummaryController
) : VectorBaseFragment<FragmentSpaceListBinding>(), SpaceSummaryController.Callback {
private val spaceController: SpaceSummaryController,
private val newSpaceController: NewSpaceSummaryController,
private val vectorFeatures: VectorFeatures,
) : VectorBaseFragment<FragmentSpaceListBinding>(), SpaceSummaryController.Callback, NewSpaceSummaryController.Callback {
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
private lateinit var homeActivitySharedActionViewModel: HomeSharedActionViewModel
private lateinit var roomListSharedActionViewModel: RoomListSharedActionViewModel
private val viewModel: SpaceListViewModel by fragmentViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSpaceListBinding {
@ -54,10 +63,69 @@ class SpaceListFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java)
spaceController.callback = this
homeActivitySharedActionViewModel = activityViewModelProvider[HomeSharedActionViewModel::class.java]
roomListSharedActionViewModel = activityViewModelProvider[RoomListSharedActionViewModel::class.java]
views.stateView.contentView = views.groupListView
views.groupListView.configureWith(spaceController)
setupSpaceController()
observeViewEvents()
}
private fun setupSpaceController() {
if (vectorFeatures.isNewAppLayoutEnabled()) {
enableDragAndDropForNewSpaceController()
newSpaceController.callback = this
views.groupListView.configureWith(newSpaceController)
} else {
enableDragAndDropForSpaceController()
spaceController.callback = this
views.groupListView.configureWith(spaceController)
}
}
private fun enableDragAndDropForNewSpaceController() {
EpoxyTouchHelper.initDragging(newSpaceController)
.withRecyclerView(views.groupListView)
.forVerticalList()
.withTarget(NewSpaceSummaryItem::class.java)
.andCallbacks(object : EpoxyTouchHelper.DragCallbacks<NewSpaceSummaryItem>() {
var toPositionM: Int? = null
var fromPositionM: Int? = null
var initialElevation: Float? = null
override fun onDragStarted(model: NewSpaceSummaryItem?, itemView: View?, adapterPosition: Int) {
toPositionM = null
fromPositionM = null
model?.matrixItem?.id?.let {
viewModel.handle(SpaceListAction.OnStartDragging(it, false))
}
itemView?.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
initialElevation = itemView?.elevation
itemView?.elevation = 6f
}
override fun onDragReleased(model: NewSpaceSummaryItem?, itemView: View?) {
if (toPositionM == null || fromPositionM == null) return
val movedSpaceId = model?.matrixItem?.id ?: return
viewModel.handle(SpaceListAction.MoveSpace(movedSpaceId, toPositionM!! - fromPositionM!!))
}
override fun clearView(model: NewSpaceSummaryItem?, itemView: View?) {
itemView?.elevation = initialElevation ?: 0f
}
override fun onModelMoved(fromPosition: Int, toPosition: Int, modelBeingMoved: NewSpaceSummaryItem?, itemView: View?) {
if (fromPositionM == null) {
fromPositionM = fromPosition
}
if (toPositionM != toPosition) {
toPositionM = toPosition
itemView?.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
}
}
})
}
private fun enableDragAndDropForSpaceController() {
EpoxyTouchHelper.initDragging(spaceController)
.withRecyclerView(views.groupListView)
.forVerticalList()
@ -100,14 +168,14 @@ class SpaceListFragment @Inject constructor(
return model?.canDrag == true
}
})
}
viewModel.observeViewEvents {
when (it) {
is SpaceListViewEvents.OpenSpaceSummary -> sharedActionViewModel.post(HomeActivitySharedAction.OpenSpacePreview(it.id))
is SpaceListViewEvents.AddSpace -> sharedActionViewModel.post(HomeActivitySharedAction.AddSpace)
is SpaceListViewEvents.OpenSpaceInvite -> sharedActionViewModel.post(HomeActivitySharedAction.OpenSpaceInvite(it.id))
SpaceListViewEvents.CloseDrawer -> sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
}
private fun observeViewEvents() = viewModel.observeViewEvents {
when (it) {
is SpaceListViewEvents.OpenSpaceSummary -> homeActivitySharedActionViewModel.post(HomeActivitySharedAction.OpenSpacePreview(it.id))
is SpaceListViewEvents.AddSpace -> homeActivitySharedActionViewModel.post(HomeActivitySharedAction.AddSpace)
is SpaceListViewEvents.OpenSpaceInvite -> homeActivitySharedActionViewModel.post(HomeActivitySharedAction.OpenSpaceInvite(it.id))
SpaceListViewEvents.CloseDrawer -> homeActivitySharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
}
}
@ -124,11 +192,17 @@ class SpaceListFragment @Inject constructor(
is Success -> views.stateView.state = StateView.State.Content
else -> Unit
}
spaceController.update(state)
if (vectorFeatures.isNewAppLayoutEnabled()) {
newSpaceController.update(state)
} else {
spaceController.update(state)
}
}
override fun onSpaceSelected(spaceSummary: RoomSummary?) {
viewModel.handle(SpaceListAction.SelectSpace(spaceSummary))
roomListSharedActionViewModel.post(RoomListSharedAction.CloseBottomSheet)
}
override fun onSpaceInviteSelected(spaceSummary: RoomSummary) {
@ -136,7 +210,7 @@ class SpaceListFragment @Inject constructor(
}
override fun onSpaceSettings(spaceSummary: RoomSummary) {
sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(spaceSummary.roomId))
homeActivitySharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(spaceSummary.roomId))
}
override fun onToggleExpand(spaceSummary: RoomSummary) {
@ -148,6 +222,6 @@ class SpaceListFragment @Inject constructor(
}
override fun sendFeedBack() {
sharedActionViewModel.post(HomeActivitySharedAction.SendSpaceFeedBack)
homeActivitySharedActionViewModel.post(HomeActivitySharedAction.SendSpaceFeedBack)
}
}

View file

@ -194,7 +194,7 @@ class SpaceListViewModel @AssistedInject constructor(
val moved = removeAt(index)
add(index + action.delta, moved)
},
spaceOrderLocalEchos = updatedLocalEchos
spaceOrderLocalEchos = updatedLocalEchos,
)
}
session.coroutineScope.launch {
@ -257,29 +257,29 @@ class SpaceListViewModel @AssistedInject constructor(
}
combine(
session.flow()
.liveSpaceSummaries(params),
session.flow().liveSpaceSummaries(params),
session.accountDataService()
.getLiveRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER))
.asFlow()
) { spaces, _ ->
spaces
}.execute { asyncSpaces ->
val spaces = asyncSpaces.invoke().orEmpty()
val rootSpaces = asyncSpaces.invoke().orEmpty().filter { it.flattenParentIds.isEmpty() }
val orders = rootSpaces.associate {
it.roomId to session.getRoom(it.roomId)
?.roomAccountDataService()
?.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER)
?.content.toModel<SpaceOrderContent>()
?.safeOrder()
}
copy(
asyncSpaces = asyncSpaces,
spaces = spaces,
rootSpacesOrdered = rootSpaces.sortedWith(TopLevelSpaceComparator(orders)),
spaceOrderInfo = orders
)
}
.execute { async ->
val rootSpaces = async.invoke().orEmpty().filter { it.flattenParentIds.isEmpty() }
val orders = rootSpaces.associate {
it.roomId to session.getRoom(it.roomId)
?.roomAccountDataService()
?.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER)
?.content.toModel<SpaceOrderContent>()
?.safeOrder()
}
copy(
asyncSpaces = async,
rootSpacesOrdered = rootSpaces.sortedWith(TopLevelSpaceComparator(orders)),
spaceOrderInfo = orders
)
}
// clear local echos on update
session.accountDataService()

View file

@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.util.MatrixItem
data class SpaceListViewState(
val myMxItem: Async<MatrixItem.UserItem> = Uninitialized,
val asyncSpaces: Async<List<RoomSummary>> = Uninitialized,
val spaces: List<RoomSummary> = emptyList(),
val selectedSpace: RoomSummary? = null,
val rootSpacesOrdered: List<RoomSummary>? = null,
val spaceOrderInfo: Map<String, String?>? = null,

View file

@ -0,0 +1,11 @@
<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.7822,4.2963C12.7822,3.8641 12.4318,3.5137 11.9996,3.5137C11.5673,3.5137 11.217,3.8641 11.217,4.2963V11.2173L4.2963,11.2173C3.8641,11.2173 3.5137,11.5676 3.5137,11.9999C3.5137,12.4321 3.8641,12.7825 4.2963,12.7825H11.217V19.7038C11.217,20.136 11.5673,20.4864 11.9996,20.4864C12.4318,20.4864 12.7822,20.136 12.7822,19.7038V12.7825H19.7038C20.136,12.7825 20.4864,12.4321 20.4864,11.9999C20.4864,11.5676 20.136,11.2173 19.7038,11.2173L12.7822,11.2173V4.2963Z"
android:fillColor="#17191C"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:width="40dp" android:height="40dp"/>
<solid android:color="?android:colorBackground" />
<corners android:radius="8dp" />
</shape>

View file

@ -10,4 +10,4 @@
<corners android:radius="8dp" />
</shape>
</shape>

View file

@ -1,101 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<im.vector.app.core.platform.StateView xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout 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/stateView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:colorBackground">
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/roomListView"
<im.vector.app.core.platform.StateView
android:id="@+id/stateView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="always"
tools:itemCount="5"
tools:listitem="@layout/item_room" />
android:background="?android:colorBackground">
<im.vector.app.features.home.room.list.widget.NotifsFabMenuView
android:id="@+id/createChatFabMenu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
app:layoutDescription="@xml/motion_scene_notifs_fab_menu"
tools:showPaths="true"
tools:visibility="visible" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/roomListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="always"
tools:itemCount="5"
tools:listitem="@layout/item_room" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/createChatRoomButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:accessibilityTraversalBefore="@id/roomListView"
android:contentDescription="@string/a11y_create_direct_message"
android:scaleType="center"
android:src="@drawable/ic_fab_add_chat"
android:visibility="gone"
app:maxImageSize="34dp"
tools:layout_marginEnd="80dp"
tools:visibility="visible" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/createGroupRoomButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:accessibilityTraversalBefore="@id/roomListView"
android:contentDescription="@string/a11y_create_room"
android:src="@drawable/ic_fab_add_room"
android:visibility="gone"
app:maxImageSize="32dp"
tools:layout_marginEnd="144dp"
tools:visibility="visible" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom|end">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/newLayoutOpenSpacesButton"
style="@style/Widget.Vector.FloatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:accessibilityTraversalAfter="@id/newLayoutCreateChatButton"
android:contentDescription="@string/a11y_open_spaces"
android:src="@drawable/ic_open_spaces"
<im.vector.app.features.home.room.list.widget.NotifsFabMenuView
android:id="@+id/createChatFabMenu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
app:backgroundTint="?attr/vctr_toolbar_background"
app:fabSize="mini"
app:layout_constraintBottom_toTopOf="@id/newLayoutCreateChatButton"
app:layout_constraintEnd_toEndOf="@id/newLayoutCreateChatButton"
app:layout_constraintStart_toStartOf="@id/newLayoutCreateChatButton"
app:tint="?attr/colorPrimary"
tools:visibility="visible"
tools:targetApi="lollipop_mr1" />
app:layoutDescription="@xml/motion_scene_notifs_fab_menu"
tools:showPaths="true"
tools:visibility="visible" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/newLayoutCreateChatButton"
style="@style/Widget.Vector.FloatingActionButton"
android:id="@+id/createChatRoomButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:accessibilityTraversalBefore="@id/roomListView"
android:contentDescription="@string/a11y_create_message"
android:src="@drawable/ic_new_chat"
android:contentDescription="@string/a11y_create_direct_message"
android:scaleType="center"
android:src="@drawable/ic_fab_add_chat"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:visibility="visible"
tools:targetApi="lollipop_mr1" />
app:maxImageSize="34dp"
tools:layout_marginEnd="80dp"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/createGroupRoomButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:accessibilityTraversalBefore="@id/roomListView"
android:contentDescription="@string/a11y_create_room"
android:src="@drawable/ic_fab_add_room"
android:visibility="gone"
app:maxImageSize="32dp"
tools:layout_marginEnd="144dp"
tools:visibility="visible" />
</im.vector.app.core.platform.StateView>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom|end">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/newLayoutOpenSpacesButton"
style="@style/Widget.Vector.FloatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:accessibilityTraversalAfter="@id/newLayoutCreateChatButton"
android:contentDescription="@string/a11y_open_spaces"
android:src="@drawable/ic_open_spaces"
android:visibility="gone"
app:backgroundTint="?attr/vctr_toolbar_background"
app:fabSize="mini"
app:layout_constraintBottom_toTopOf="@id/newLayoutCreateChatButton"
app:layout_constraintEnd_toEndOf="@id/newLayoutCreateChatButton"
app:layout_constraintStart_toStartOf="@id/newLayoutCreateChatButton"
app:tint="?attr/colorPrimary"
tools:targetApi="lollipop_mr1"
tools:visibility="visible" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/newLayoutCreateChatButton"
style="@style/Widget.Vector.FloatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:accessibilityTraversalBefore="@id/roomListView"
android:contentDescription="@string/a11y_create_message"
android:src="@drawable/ic_new_chat"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:targetApi="lollipop_mr1"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</im.vector.app.core.platform.StateView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/space_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</FrameLayout>

View file

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<im.vector.app.core.platform.CheckableConstraintLayout 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/root"
android:layout_width="match_parent"
android:layout_height="65dp"
android:background="@drawable/bg_space_item"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
tools:viewBindingIgnore="true">
<ImageView
android:id="@+id/avatar"
android:layout_width="42dp"
android:layout_height="42dp"
android:layout_gravity="center"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:duplicateParentState="true"
android:importantForAccessibility="no"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@sample/space_avatars" />
<TextView
android:id="@+id/name"
style="@style/Widget.Vector.TextView.Subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?vctr_content_primary"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintEnd_toStartOf="@id/unread_counter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Element Corp" />
<im.vector.app.features.home.room.list.UnreadCounterBadgeView
android:id="@+id/unread_counter"
style="@style/Widget.Vector.TextView.Micro"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:gravity="center"
android:minWidth="16dp"
android:minHeight="16dp"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:textColor="?colorOnError"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@id/chevron"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:background="@drawable/bg_unread_highlight"
tools:text="147"
tools:visibility="visible" />
<ImageView
android:id="@+id/chevron"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="21dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_arrow_right"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:tint="?vctr_content_secondary"
tools:ignore="MissingPrefix" />
</im.vector.app.core.platform.CheckableConstraintLayout>

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<im.vector.app.core.platform.CheckableConstraintLayout 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/itemGroupLayout"
android:layout_width="match_parent"
android:layout_height="65dp"
android:background="@drawable/bg_space_item"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
tools:viewBindingIgnore="true">
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="26dp"
android:background="?attr/vctr_system"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/plus"
android:layout_width="42dp"
android:layout_height="42dp"
android:layout_gravity="center"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:background="@drawable/rounded_rect_shape_8"
android:backgroundTint="#4D8D97A5"
android:duplicateParentState="true"
android:importantForAccessibility="no"
android:padding="10dp"
android:src="@drawable/ic_plus"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/groupNameView"
style="@style/Widget.Vector.TextView.Subtitle.Medium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/create_space"
android:textColor="?vctr_message_text_color"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/plus"
app:layout_constraintTop_toTopOf="parent" />
</im.vector.app.core.platform.CheckableConstraintLayout>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="@style/TextAppearance.Vector.Body.Medium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_space_item"
android:ellipsize="middle"
android:orientation="vertical"
android:padding="16dp"
android:singleLine="true"
android:text="@string/change_space"
android:textAllCaps="true"
android:textColor="?vctr_content_tertiary"
android:textSize="14sp"
tools:viewBindingIgnore="true" />

View file

@ -137,6 +137,7 @@
<!-- Home Screen -->
<string name="all_chats">All Chats</string>
<string name="change_space">Change Space</string>
<!-- Last seen time -->