Home: change some UI in room list

This commit is contained in:
ganfra 2019-05-20 17:13:12 +02:00 committed by Benoit Marty
parent 1691537a1e
commit 9f9f4c0755
15 changed files with 166 additions and 86 deletions

View File

@ -1,66 +1,68 @@
/*
* Copyright 2019 New Vector Ltd
*
* * 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.
* 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.riotredesign.features.home.group
package im.vector.riotredesign.features.home

import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import androidx.core.view.forEachIndexed
import com.airbnb.mvrx.args
import com.google.android.material.bottomnavigation.BottomNavigationItemView
import com.google.android.material.bottomnavigation.BottomNavigationMenuView
import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.ToolbarConfigurable
import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.features.home.AvatarRenderer
import im.vector.riotredesign.features.home.room.list.RoomListFragment
import im.vector.riotredesign.features.home.room.list.RoomListParams
import im.vector.riotredesign.features.home.room.list.UnreadCounterBadgeView
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_selected_group.*
import kotlinx.android.synthetic.main.fragment_home_detail.*


@Parcelize
data class SelectedGroupParams(
data class HomeDetailParams(
val groupId: String,
val groupName: String,
val groupAvatar: String
) : Parcelable


private const val CURRENT_DISPLAY_MODE = "CURRENT_DISPLAY_MODE"

class SelectedGroupFragment : VectorBaseFragment() {
class HomeDetailFragment : VectorBaseFragment() {

private val selectedGroupParams: SelectedGroupParams by args()
private val params: HomeDetailParams by args()
private val unreadCounterBadgeViews = arrayListOf<UnreadCounterBadgeView>()
private lateinit var currentDisplayMode: RoomListFragment.DisplayMode

override fun getLayoutResId(): Int {
return R.layout.fragment_selected_group
return R.layout.fragment_home_detail
}

override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if (savedInstanceState == null) {
currentDisplayMode = RoomListFragment.DisplayMode.HOME
} else {
currentDisplayMode = savedInstanceState.getSerializable(CURRENT_DISPLAY_MODE) as? RoomListFragment.DisplayMode
?: RoomListFragment.DisplayMode.HOME
}
renderState(currentDisplayMode)
currentDisplayMode = savedInstanceState?.getSerializable(CURRENT_DISPLAY_MODE) as? RoomListFragment.DisplayMode
?: RoomListFragment.DisplayMode.HOME
switchDisplayMode(currentDisplayMode)
setupBottomNavigationView()
setupToolbar()
}


override fun onSaveInstanceState(outState: Bundle) {
outState.putSerializable(CURRENT_DISPLAY_MODE, currentDisplayMode)
super.onSaveInstanceState(outState)
@ -73,9 +75,9 @@ class SelectedGroupFragment : VectorBaseFragment() {
}
groupToolbar.title = ""
AvatarRenderer.render(
selectedGroupParams.groupAvatar,
selectedGroupParams.groupId,
selectedGroupParams.groupName,
params.groupAvatar,
params.groupId,
params.groupName,
groupToolbarAvatarImageView
)
groupToolbarAvatarImageView.setOnClickListener {
@ -92,13 +94,21 @@ class SelectedGroupFragment : VectorBaseFragment() {
}
if (currentDisplayMode != displayMode) {
currentDisplayMode = displayMode
renderState(displayMode)
switchDisplayMode(displayMode)
}
true
}
val menuView = bottomNavigationView.getChildAt(0) as BottomNavigationMenuView
menuView.forEachIndexed { index, view ->
val itemView = view as BottomNavigationItemView
val badgeLayout = LayoutInflater.from(requireContext()).inflate(R.layout.vector_unread_layout, menuView, false)
val unreadCounterBadgeView: UnreadCounterBadgeView = badgeLayout.findViewById(R.id.actionUnreadCounterBadgeView)
itemView.addView(badgeLayout)
unreadCounterBadgeViews.add(index, unreadCounterBadgeView)
}
}

private fun renderState(displayMode: RoomListFragment.DisplayMode) {
private fun switchDisplayMode(displayMode: RoomListFragment.DisplayMode) {
groupToolbarTitleView.setText(displayMode.titleRes)
updateSelectedFragment(displayMode)
}
@ -117,8 +127,8 @@ class SelectedGroupFragment : VectorBaseFragment() {

companion object {

fun newInstance(args: SelectedGroupParams): SelectedGroupFragment {
return SelectedGroupFragment().apply {
fun newInstance(args: HomeDetailParams): HomeDetailFragment {
return HomeDetailFragment().apply {
setArguments(args)
}
}

View File

@ -21,8 +21,6 @@ import androidx.fragment.app.FragmentManager
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.replaceFragment
import im.vector.riotredesign.features.home.group.SelectedGroupFragment
import im.vector.riotredesign.features.home.group.SelectedGroupParams
import im.vector.riotredesign.features.home.room.detail.RoomDetailActivity
import im.vector.riotredesign.features.home.room.detail.RoomDetailArgs
import kotlinx.android.synthetic.main.activity_home.*
@ -37,10 +35,10 @@ class HomeNavigator {
fun openSelectedGroup(groupSummary: GroupSummary) {
Timber.v("Open selected group ${groupSummary.groupId}")
activity?.let {
val args = SelectedGroupParams(groupSummary.groupId, groupSummary.displayName, groupSummary.avatarUrl)
val selectedGroupFragment = SelectedGroupFragment.newInstance(args)
val args = HomeDetailParams(groupSummary.groupId, groupSummary.displayName, groupSummary.avatarUrl)
val homeDetailFragment = HomeDetailFragment.newInstance(args)
it.drawerLayout?.closeDrawer(GravityCompat.START)
it.replaceFragment(selectedGroupFragment, R.id.homeDetailFragmentContainer)
it.replaceFragment(homeDetailFragment, R.id.homeDetailFragmentContainer)
}
}


View File

@ -18,5 +18,18 @@ package im.vector.riotredesign.features.home

import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotredesign.core.utils.RxStore
import im.vector.riotredesign.features.home.room.list.RoomListDisplayModeFilter
import im.vector.riotredesign.features.home.room.list.RoomListFragment
import io.reactivex.Observable

class HomeRoomListObservableStore : RxStore<List<RoomSummary>>(emptyList())
class HomeRoomListObservableStore : RxStore<List<RoomSummary>>() {

fun observeFilteredBy(displayMode: RoomListFragment.DisplayMode): Observable<List<RoomSummary>> {
return observe()
.flatMapSingle {
Observable.fromIterable(it).filter(RoomListDisplayModeFilter(displayMode)).toList()
}
}


}

View File

@ -389,15 +389,15 @@ class RoomDetailFragment :
private fun renderState(state: RoomDetailViewState) {
renderRoomSummary(state)
val summary = state.asyncRoomSummary()
val inviter = state.inviter()
val inviter = state.asyncInviter()
if (summary?.membership == Membership.JOIN) {
timelineEventController.setTimeline(state.timeline)
inviteView.visibility = View.GONE
} else if (summary?.membership == Membership.INVITE && inviter != null) {
inviteView.visibility = View.VISIBLE
inviteView.render(inviter, VectorInviteView.Mode.LARGE)
} else {
//TODO : close the screen
} else if (state.asyncInviter.complete) {
vectorBaseActivity.finish()
}
}


View File

@ -292,7 +292,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
summary.lastMessage?.sender?.let { senderId ->
session.getUser(senderId)
}?.also {
setState { copy(inviter = Success(it)) }
setState { copy(asyncInviter = Success(it)) }
}
}
}

View File

@ -28,7 +28,7 @@ data class RoomDetailViewState(
val roomId: String,
val eventId: String?,
val timeline: Timeline? = null,
val inviter: Async<User> = Uninitialized,
val asyncInviter: Async<User> = Uninitialized,
val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
val asyncTimelineData: Async<TimelineData> = Uninitialized
) : MvRxState {

View File

@ -41,7 +41,7 @@ abstract class RoomCategoryItem : VectorEpoxyModel<RoomCategoryItem.Holder>() {
val expandedArrowDrawable = ContextCompat.getDrawable(holder.rootView.context, expandedArrowDrawableRes)?.also {
DrawableCompat.setTint(it, tintColor)
}
holder.unreadCounterBadgeView.render(unreadCount, showHighlighted)
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadCount, showHighlighted))
holder.titleView.setCompoundDrawablesWithIntrinsicBounds(expandedArrowDrawable, null, null, null)
holder.titleView.text = title
holder.rootView.setOnClickListener { listener?.invoke() }

View File

@ -0,0 +1,32 @@
/*
* 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.riotredesign.features.home.room.list

import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary
import io.reactivex.functions.Predicate

class RoomListDisplayModeFilter(private val displayMode: RoomListFragment.DisplayMode) : Predicate<RoomSummary> {

override fun test(roomSummary: RoomSummary): Boolean {
return when (displayMode) {
RoomListFragment.DisplayMode.HOME -> roomSummary.notificationCount > 0 || roomSummary.membership == Membership.INVITE
RoomListFragment.DisplayMode.PEOPLE -> roomSummary.isDirect && roomSummary.membership == Membership.JOIN
RoomListFragment.DisplayMode.ROOMS -> !roomSummary.isDirect && roomSummary.membership == Membership.JOIN
}
}
}

View File

@ -29,7 +29,6 @@ import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.riotredesign.core.platform.VectorViewModel
import im.vector.riotredesign.core.utils.LiveEvent
import im.vector.riotredesign.features.home.HomeRoomListObservableStore
import io.reactivex.Observable
import org.koin.android.ext.android.get

typealias RoomListFilterName = CharSequence
@ -54,6 +53,7 @@ class RoomListViewModel(initialState: RoomListViewState,
}

private val displayMode = initialState.displayMode
private val roomListDisplayModeFilter = RoomListDisplayModeFilter(displayMode)
private val roomListFilter = BehaviorRelay.createDefault<Option<RoomListFilterName>>(Option.empty())

private val _openRoomLiveData = MutableLiveData<LiveEvent<String>>()
@ -95,27 +95,13 @@ class RoomListViewModel(initialState: RoomListViewState,
copy(asyncRooms = asyncRooms)
}

homeRoomListObservableSource
.observe()
.flatMapSingle {
Observable.fromIterable(it)
.filter(filterByDisplayMode(displayMode))
.toList()
}
homeRoomListObservableSource.observeFilteredBy(displayMode)
.map { buildRoomSummaries(it) }
.execute { async ->
copy(asyncFilteredRooms = async)
}
}

private fun filterByDisplayMode(displayMode: RoomListFragment.DisplayMode) = { roomSummary: RoomSummary ->
when (displayMode) {
RoomListFragment.DisplayMode.HOME -> roomSummary.notificationCount > 0
RoomListFragment.DisplayMode.PEOPLE -> roomSummary.isDirect
RoomListFragment.DisplayMode.ROOMS -> !roomSummary.isDirect
}
}

private fun buildRoomSummaries(rooms: List<RoomSummary>): RoomSummaries {
val invites = ArrayList<RoomSummary>()
val favourites = ArrayList<RoomSummary>()

View File

@ -17,12 +17,16 @@
package im.vector.riotredesign.features.home.room.list

import androidx.annotation.StringRes
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotredesign.R

data class RoomListViewState(
val displayMode: RoomListFragment.DisplayMode,
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
val asyncFilteredRooms: Async<RoomSummaries> = Uninitialized,
val isInviteExpanded: Boolean = true,
val isFavouriteRoomsExpanded: Boolean = true,
val isDirectRoomsExpanded: Boolean = true,

View File

@ -46,7 +46,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
holder.titleView.text = roomName
holder.lastEventTimeView.text = lastEventTime
holder.lastEventView.text = lastFormattedEvent
holder.unreadCounterBadgeView.render(unreadCount, showHighlighted)
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadCount, showHighlighted))
AvatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView)
}


View File

@ -29,24 +29,24 @@ class UnreadCounterBadgeView : AppCompatTextView {

constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

fun render(count: Int, highlighted: Boolean) {
if (count == 0) {
fun render(state: State) {
if (state.count == 0) {
visibility = View.INVISIBLE
} else {
visibility = View.VISIBLE
val bgRes = if (highlighted) {
val bgRes = if (state.highlighted) {
R.drawable.bg_unread_highlight
} else {
R.drawable.bg_unread_notification
}
setBackgroundResource(bgRes)
text = RoomSummaryFormatter.formatUnreadMessagesCounter(count)
text = RoomSummaryFormatter.formatUnreadMessagesCounter(state.count)
}
}

enum class Status {
NOTIFICATION,
HIGHLIGHT
}
data class State(
val count: Int,
val highlighted: Boolean
)

}

View File

@ -57,6 +57,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
app:labelVisibilityMode="unlabeled"
app:itemIconTint="@android:color/white"
app:itemTextColor="@color/home_bottom_nav_view_tint"
app:layout_constraintBottom_toBottomOf="parent"

View File

@ -10,11 +10,7 @@
android:clickable="true"
android:focusable="true"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingRight="16dp"
android:paddingBottom="8dp">
android:paddingLeft="8dp">

<ImageView
android:id="@+id/roomAvatarImageView"
@ -22,7 +18,7 @@
android:layout_height="40dp"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
@ -32,7 +28,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:duplicateParentState="true"
android:ellipsize="end"
android:maxLines="1"
@ -43,14 +42,14 @@
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/roomAvatarImageView"
app:layout_constraintTop_toTopOf="@+id/roomAvatarImageView"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/full_names" />


<im.vector.riotredesign.features.home.room.list.UnreadCounterBadgeView
android:id="@+id/roomUnreadCounterBadgeView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:gravity="center"
android:minWidth="16dp"
@ -59,9 +58,10 @@
android:paddingRight="4dp"
android:textColor="@android:color/white"
android:textSize="10sp"
app:layout_constraintBottom_toBottomOf="@+id/roomNameView"
app:layout_constraintEnd_toStartOf="@+id/roomLastEventTimeView"
app:layout_constraintStart_toEndOf="@+id/roomNameView"
app:layout_constraintTop_toTopOf="@+id/roomAvatarImageView"
app:layout_constraintTop_toTopOf="@+id/roomNameView"
tools:background="@drawable/bg_unread_highlight"
tools:text="4" />

@ -69,20 +69,21 @@
android:id="@+id/roomLastEventTimeView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:textColor="@color/black_38"
android:textSize="12sp"
app:layout_constraintBaseline_toBaselineOf="@id/messageMemberNameView"
app:layout_constraintTop_toTopOf="@+id/roomAvatarImageView"
app:layout_constraintBottom_toBottomOf="@+id/roomNameView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/roomNameView"
tools:text="@tools:sample/date/hhmm" />


<TextView
android:id="@+id/roomLastEventView"
android:layout_width="0dp"
android:layout_height="34dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="@color/black_38"
@ -92,5 +93,14 @@
app:layout_constraintTop_toBottomOf="@+id/roomNameView"
tools:text="@tools:sample/lorem/random" />

<View
android:id="@+id/roomDividerView"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="#1e000000"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/roomLastEventView"
app:layout_constraintStart_toStartOf="@+id/roomNameView" />


</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">

<im.vector.riotredesign.features.home.room.list.UnreadCounterBadgeView
android:id="@+id/actionUnreadCounterBadgeView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:gravity="center"
android:minWidth="8dp"
android:minHeight="8dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:textColor="@android:color/white"
android:textSize="10sp"
tools:background="@drawable/bg_unread_highlight"
tools:text="4" />

</FrameLayout>