Spaces quick fixes and updates

Make drawer menu more obvious + fix notification count
This commit is contained in:
Valere 2021-04-20 10:29:44 +02:00
parent 5e75a3a80b
commit d30ba9c749
15 changed files with 211 additions and 85 deletions

View file

@ -2,7 +2,7 @@ Changes in Element 1.1.7 (2021-XX-XX)
===================================================
Features ✨:
-
- Spaces beta
Improvements 🙌:
-

View file

@ -20,6 +20,6 @@ data class RoomAggregateNotificationCount(
val notificationCount: Int,
val highlightCount: Int
) {
val totalCount = notificationCount + highlightCount
val totalCount = notificationCount
val isHighlight = highlightCount > 0
}

View file

@ -349,6 +349,29 @@ internal class RoomSummaryUpdater @Inject constructor(
// Timber.v("## SPACES: flatten of ${dmRoom.otherMemberIds.joinToString(",")} is ${dmRoom.flattenParentIds}")
}
// Maybe a good place to count the number of notifications for spaces?
realm.where(RoomSummaryEntity::class.java)
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
.equalTo(RoomSummaryEntityFields.ROOM_TYPE, RoomType.SPACE)
.findAll().forEach { space ->
// get all children
var highlightCount = 0
var notificationCount = 0
realm.where(RoomSummaryEntity::class.java)
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, listOf(Membership.JOIN))
.notEqualTo(RoomSummaryEntityFields.ROOM_TYPE, RoomType.SPACE)
.contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, space.roomId)
.findAll().forEach {
highlightCount += it.highlightCount
notificationCount += it.notificationCount
}
space.highlightCount = highlightCount
space.notificationCount = notificationCount
}
// xxx invites??
// LEGACY GROUPS
// lets mark rooms that belongs to groups
val existingGroups = GroupSummaryEntity.where(realm).findAll()

View file

@ -28,6 +28,7 @@ import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.platform.CheckableConstraintLayout
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
import im.vector.app.features.themes.ThemeUtils
@EpoxyModelClass(layout = R.layout.item_space)
@ -35,6 +36,7 @@ abstract class HomeSpaceSummaryItem : VectorEpoxyModel<HomeSpaceSummaryItem.Hold
@EpoxyAttribute var selected: Boolean = false
@EpoxyAttribute var listener: (() -> Unit)? = null
@EpoxyAttribute var countState : UnreadCounterBadgeView.State = UnreadCounterBadgeView.State(0, false)
override fun getViewType(): Int {
// mm.. it's reusing the same layout for basic space item
@ -52,6 +54,8 @@ abstract class HomeSpaceSummaryItem : VectorEpoxyModel<HomeSpaceSummaryItem.Hold
holder.avatarImageView.imageTintList = ColorStateList.valueOf(ThemeUtils.getColor(holder.view.context, R.attr.riot_primary_text_color))
holder.avatarImageView.scaleType = ImageView.ScaleType.CENTER_INSIDE
holder.leaveView.isVisible = false
holder.counterBadgeView.render(countState)
}
class Holder : VectorEpoxyHolder() {
@ -59,5 +63,6 @@ abstract class HomeSpaceSummaryItem : VectorEpoxyModel<HomeSpaceSummaryItem.Hold
val groupNameView by bind<TextView>(R.id.groupNameView)
val rootView by bind<CheckableConstraintLayout>(R.id.itemGroupLayout)
val leaveView by bind<ImageView>(R.id.groupTmpLeave)
val counterBadgeView by bind<UnreadCounterBadgeView>(R.id.groupCounterBadge)
}
}

View file

@ -22,7 +22,6 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import com.airbnb.mvrx.activityViewModel
@ -32,7 +31,6 @@ import com.google.android.material.badge.BadgeDrawable
import im.vector.app.R
import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.glide.GlideApp
import im.vector.app.core.platform.ToolbarConfigurable
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseFragment
@ -56,14 +54,9 @@ import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
import im.vector.app.features.workers.signout.ServerBackupStatusViewState
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import javax.inject.Inject
private const val INDEX_PEOPLE = 0
private const val INDEX_ROOMS = 1
private const val INDEX_CATCHUP = 2
class HomeDetailFragment @Inject constructor(
val homeDetailViewModelFactory: HomeDetailViewModel.Factory,
private val serverBackupStatusViewModelFactory: ServerBackupStatusViewModel.Factory,
@ -251,19 +244,8 @@ class HomeDetailFragment @Inject constructor(
private fun onGroupChange(groupSummary: GroupSummary?) {
groupSummary ?: return
if (groupSummary.groupId == ALL_COMMUNITIES_GROUP_ID) {
// Special case
avatarRenderer.clear(views.groupToolbarAvatarImageView)
views.groupToolbarAvatarImageView.background = null
val myMxItem = withState(viewModel) { it.myMatrixItem }
if (myMxItem != null) {
avatarRenderer.render(myMxItem, views.groupToolbarAvatarImageView, GlideApp.with(requireActivity()))
}
views.groupToolbarSpaceTitleView.isVisible = false
} else {
views.groupToolbarAvatarImageView.background = null
// Use GlideApp with activity context to avoid the glideRequests to be paused
avatarRenderer.render(groupSummary.toMatrixItem(), views.groupToolbarAvatarImageView, GlideApp.with(requireActivity()))
views.groupToolbarSpaceTitleView.isVisible = true
views.groupToolbarSpaceTitleView.text = groupSummary.displayName
}
@ -271,24 +253,9 @@ class HomeDetailFragment @Inject constructor(
private fun onSpaceChange(spaceSummary: RoomSummary?) {
spaceSummary ?: return
// Use GlideApp with activity context to avoid the glideRequests to be paused
if (spaceSummary.roomId == ALL_COMMUNITIES_GROUP_ID) {
// Special case
views.groupToolbarAvatarImageView.background = ContextCompat.getDrawable(requireContext(), R.drawable.space_home_background)
views.groupToolbarAvatarImageView.scaleType = ImageView.ScaleType.CENTER_INSIDE
ThemeUtils.tintDrawableWithColor(
ContextCompat.getDrawable(requireContext(), R.drawable.ic_space_home)!!,
ThemeUtils.getColor(requireContext(), R.attr.riot_primary_text_color)
).let {
views.groupToolbarAvatarImageView.setImageDrawable(it)
}
views.groupToolbarSpaceTitleView.isVisible = false
} else {
avatarRenderer.clear(views.groupToolbarAvatarImageView)
views.groupToolbarAvatarImageView.background = null
avatarRenderer.renderSpace(spaceSummary.toMatrixItem(), views.groupToolbarAvatarImageView, GlideApp.with(requireActivity()))
views.groupToolbarSpaceTitleView.isVisible = true
views.groupToolbarSpaceTitleView.text = spaceSummary.displayName
}

View file

@ -35,6 +35,7 @@ import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.RoomCategoryFilter
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.util.toMatrixItem
@ -163,7 +164,8 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
session.getPagedRoomSummariesLive(
roomSummaryQueryParams {
memberships = Membership.activeMemberships()
}
},
sortOrder = RoomSortOrder.NONE
).asObservable()
}

View file

@ -314,23 +314,7 @@ class SpaceRoomListSectionBuilder(
val name = stringProvider.getString(nameRes)
session.getFilteredPagedRoomSummariesLive(
when (spaceFilterStrategy) {
RoomListViewModel.SpaceFilterStrategy.NORMAL -> {
roomQueryParams.copy(
activeSpaceId = ActiveSpaceFilter.ActiveSpace(appStateHandler.safeActiveSpaceId())
)
}
RoomListViewModel.SpaceFilterStrategy.NOT_IF_ALL -> {
if (appStateHandler.safeActiveSpaceId() == null) {
roomQueryParams
} else {
roomQueryParams.copy(
activeSpaceId = ActiveSpaceFilter.ActiveSpace(appStateHandler.safeActiveSpaceId())
)
}
}
RoomListViewModel.SpaceFilterStrategy.NONE -> roomQueryParams
},
roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId()),
pagedListConfig
).also {
when (spaceFilterStrategy) {
@ -371,7 +355,9 @@ class SpaceRoomListSectionBuilder(
.subscribe {
sections.find { it.sectionName == name }
?.notificationCount
?.postValue(session.getNotificationCountForRooms(roomQueryParams))
?.postValue(session.getNotificationCountForRooms(
roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())
))
}.also {
onDisposable.invoke(it)
}
@ -395,4 +381,24 @@ class SpaceRoomListSectionBuilder(
.build()
.let { block(it) }
}
internal fun RoomSummaryQueryParams.process(spaceFilter: RoomListViewModel.SpaceFilterStrategy, currentSpace: String?): RoomSummaryQueryParams {
return when (spaceFilter) {
RoomListViewModel.SpaceFilterStrategy.NORMAL -> {
copy(
activeSpaceId = ActiveSpaceFilter.ActiveSpace(currentSpace)
)
}
RoomListViewModel.SpaceFilterStrategy.NOT_IF_ALL -> {
if (currentSpace == null) {
this
} else {
copy(
activeSpaceId = ActiveSpaceFilter.ActiveSpace(currentSpace)
)
}
}
RoomListViewModel.SpaceFilterStrategy.NONE -> this
}
}
}

View file

@ -20,10 +20,12 @@ import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
data class SpaceListViewState(
val asyncSpaces: Async<List<RoomSummary>> = Uninitialized,
val selectedSpace: RoomSummary? = null,
val rootSpaces: List<RoomSummary>? = null,
val expandedStates: Map<String, Boolean> = emptyMap()
val expandedStates: Map<String, Boolean> = emptyMap(),
val homeAggregateCount : RoomAggregateNotificationCount = RoomAggregateNotificationCount(0, 0)
) : MvRxState

View file

@ -25,8 +25,10 @@ import im.vector.app.core.ui.list.genericItemHeader
import im.vector.app.core.utils.DebouncedClickListener
import im.vector.app.features.grouplist.homeSpaceSummaryItem
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.summary.RoomAggregateNotificationCount
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
@ -53,13 +55,15 @@ class SpaceSummaryController @Inject constructor(
nonNullViewState.asyncSpaces(),
nonNullViewState.selectedSpace,
nonNullViewState.rootSpaces,
nonNullViewState.expandedStates)
nonNullViewState.expandedStates,
nonNullViewState.homeAggregateCount)
}
private fun buildGroupModels(summaries: List<RoomSummary>?,
selected: RoomSummary?,
rootSpaces: List<RoomSummary>?,
expandedStates: Map<String, Boolean>) {
expandedStates: Map<String, Boolean>,
homeCount: RoomAggregateNotificationCount) {
if (summaries.isNullOrEmpty()) {
return
}
@ -99,22 +103,14 @@ class SpaceSummaryController @Inject constructor(
homeSpaceSummaryItem {
id(it.roomId)
selected(it.roomId == selected?.roomId)
countState(UnreadCounterBadgeView.State(homeCount.totalCount, homeCount.isHighlight))
listener { callback?.onSpaceSelected(it) }
}
}
// summaries
// .filter { it.membership == Membership.JOIN }
rootSpaces
?.forEach { groupSummary ->
val isSelected = groupSummary.roomId == selected?.roomId
// if (groupSummary.roomId == ALL_COMMUNITIES_GROUP_ID) {
// homeSpaceSummaryItem {
// id(groupSummary.roomId)
// selected(isSelected)
// listener { callback?.onSpaceSelected(groupSummary) }
// }
// } else {
// does it have children?
val subSpaces = groupSummary.children?.filter { childInfo ->
summaries.indexOfFirst { it.roomId == childInfo.childRoomId } != -1
@ -132,24 +128,36 @@ class SpaceSummaryController @Inject constructor(
onMore { callback?.onSpaceSettings(groupSummary) }
listener { callback?.onSpaceSelected(groupSummary) }
toggleExpand { callback?.onToggleExpand(groupSummary) }
countState(
UnreadCounterBadgeView.State(
groupSummary.notificationCount,
groupSummary.highlightCount > 0
)
)
}
if (hasChildren && expanded) {
// it's expanded
subSpaces?.forEach { child ->
summaries.firstOrNull { it.roomId == child.childRoomId }?.let { childSum ->
val isChildSelected = childSum.roomId == selected?.roomId
spaceSummaryItem {
avatarRenderer(avatarRenderer)
id(child.childRoomId)
hasChildren(false)
selected(isChildSelected)
matrixItem(MatrixItem.RoomItem(child.childRoomId, child.name, child.avatarUrl))
listener { callback?.onSpaceSelected(childSum) }
indent(1)
}
subSpaces?.forEach { child ->
summaries.firstOrNull { it.roomId == child.childRoomId }?.let { childSum ->
val isChildSelected = childSum.roomId == selected?.roomId
spaceSummaryItem {
avatarRenderer(avatarRenderer)
id(child.childRoomId)
hasChildren(false)
selected(isChildSelected)
matrixItem(MatrixItem.RoomItem(child.childRoomId, child.name, child.avatarUrl))
listener { callback?.onSpaceSelected(childSum) }
indent(1)
countState(
UnreadCounterBadgeView.State(
groupSummary.notificationCount,
groupSummary.highlightCount > 0
)
)
}
}
}
}
}

View file

@ -30,6 +30,7 @@ import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.platform.CheckableConstraintLayout
import im.vector.app.core.utils.DebouncedClickListener
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(layout = R.layout.item_space)
@ -44,6 +45,7 @@ abstract class SpaceSummaryItem : VectorEpoxyModel<SpaceSummaryItem.Holder>() {
@EpoxyAttribute var expanded: Boolean = false
@EpoxyAttribute var hasChildren: Boolean = false
@EpoxyAttribute var indent: Int = 0
@EpoxyAttribute var countState : UnreadCounterBadgeView.State = UnreadCounterBadgeView.State(0, false)
override fun bind(holder: Holder) {
super.bind(holder)
@ -91,6 +93,7 @@ abstract class SpaceSummaryItem : VectorEpoxyModel<SpaceSummaryItem.Holder>() {
holder.indentSpace.isVisible = indent > 0
avatarRenderer.renderSpace(matrixItem, holder.avatarImageView)
holder.counterBadgeView.render(countState)
}
override fun unbind(holder: Holder) {
@ -105,5 +108,6 @@ abstract class SpaceSummaryItem : VectorEpoxyModel<SpaceSummaryItem.Holder>() {
val moreView by bind<ImageView>(R.id.groupTmpLeave)
val collapseIndicator by bind<ImageView>(R.id.groupChildrenCollapse)
val indentSpace by bind<Space>(R.id.indent)
val counterBadgeView by bind<UnreadCounterBadgeView>(R.id.groupCounterBadge)
}
}

View file

@ -32,12 +32,16 @@ import io.reactivex.Observable
import io.reactivex.functions.BiFunction
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.RoomSortOrder
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.roomSummaryQueryParams
import org.matrix.android.sdk.rx.asObservable
import org.matrix.android.sdk.rx.rx
import java.util.concurrent.TimeUnit
const val ALL_COMMUNITIES_GROUP_ID = "+ALL_COMMUNITIES_GROUP_ID"
@ -78,6 +82,27 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp
}
}
.disposeOnClear()
session.getPagedRoomSummariesLive(
roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN)
this.activeSpaceId = ActiveSpaceFilter.ActiveSpace(null)
}, sortOrder = RoomSortOrder.NONE
).asObservable()
.throttleFirst(300, TimeUnit.MILLISECONDS)
.subscribe {
val counts = session.getNotificationCountForRooms(
roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN)
this.activeSpaceId = ActiveSpaceFilter.ActiveSpace(null)
}
)
setState {
copy(
homeAggregateCount = counts
)
}
}.disposeOnClear()
}
private fun observeSelectionState() {

View file

@ -0,0 +1,27 @@
<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="M3,12H21"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#8D99A5"
android:strokeLineCap="round"/>
<path
android:pathData="M3,6H21"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#8D99A5"
android:strokeLineCap="round"/>
<path
android:pathData="M3,18H21"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#8D99A5"
android:strokeLineCap="round"/>
</vector>

View file

@ -19,15 +19,50 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal">
android:orientation="horizontal"
android:baselineAligned="false">
<ImageView
android:layout_gravity="center_vertical"
<RelativeLayout
android:id="@+id/groupToolbarAvatarImageView"
android:layout_width="32dp"
android:layout_height="32dp"
android:contentDescription="@string/a11y_open_drawer"
tools:src="@tools:sample/avatars" />
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="40dp"
android:layout_height="40dp">
<ImageView
android:importantForAccessibility="no"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_centerVertical="true"
android:src="@drawable/ic_space_icons"
app:tint="?riotx_text_secondary"
android:layout_marginStart="4dp"
/>
<!-- Not yet done -->
<!-- <im.vector.app.features.home.room.list.UnreadCounterBadgeView-->
<!-- android:id="@+id/roomUnreadCounterBadgeView"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginEnd="0dp"-->
<!-- android:gravity="center"-->
<!-- android:minWidth="16dp"-->
<!-- android:minHeight="16dp"-->
<!-- android:paddingStart="4dp"-->
<!-- android:paddingEnd="4dp"-->
<!-- android:textColor="@android:color/white"-->
<!-- android:textSize="10sp"-->
<!-- android:visibility="gone"-->
<!-- android:layout_alignParentTop="true"-->
<!-- android:layout_alignParentEnd="true"-->
<!-- tools:background="@drawable/bg_unread_highlight"-->
<!-- tools:text="4"-->
<!-- tools:visibility="visible" />-->
</RelativeLayout>
<LinearLayout
android:layout_width="0dp"

View file

@ -60,7 +60,7 @@
android:layout_width="0dp"
android:layout_height="30dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginEnd="20dp"
android:background="@drawable/rounded_rect_shape_8"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/roomNameView"

View file

@ -33,6 +33,28 @@
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<im.vector.app.features.home.room.list.UnreadCounterBadgeView
android:id="@+id/groupCounterBadge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:gravity="center"
android:minWidth="16dp"
android:minHeight="16dp"
android:textColor="@android:color/white"
android:textSize="10sp"
app:layout_constraintCircle="@+id/groupAvatarImageView"
app:layout_constraintCircleAngle="45"
app:layout_constraintCircleRadius="24dp"
android:visibility="gone"
tools:visibility="visible"
tools:background="@drawable/bg_unread_highlight"
tools:text="147" />
<TextView
android:id="@+id/groupNameView"
android:layout_width="0dp"