diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/group/SelectedGroupFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDetailFragment.kt similarity index 54% rename from vector/src/main/java/im/vector/riotredesign/features/home/group/SelectedGroupFragment.kt rename to vector/src/main/java/im/vector/riotredesign/features/home/HomeDetailFragment.kt index a7fbe349..7550f02d 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/group/SelectedGroupFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDetailFragment.kt @@ -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() 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) } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt index d435cd60..05a2bd25 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt @@ -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) } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeRoomListObservableStore.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeRoomListObservableStore.kt index a2a3b649..9eaa47a7 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeRoomListObservableStore.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeRoomListObservableStore.kt @@ -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>(emptyList()) \ No newline at end of file +class HomeRoomListObservableStore : RxStore>() { + + fun observeFilteredBy(displayMode: RoomListFragment.DisplayMode): Observable> { + return observe() + .flatMapSingle { + Observable.fromIterable(it).filter(RoomListDisplayModeFilter(displayMode)).toList() + } + } + + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 87b30e8f..f0fdf38a 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -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() } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt index 7f7f27eb..1a5b58d8 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -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)) } } } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt index 43fbe9bd..8d5d9dcc 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewState.kt @@ -28,7 +28,7 @@ data class RoomDetailViewState( val roomId: String, val eventId: String?, val timeline: Timeline? = null, - val inviter: Async = Uninitialized, + val asyncInviter: Async = Uninitialized, val asyncRoomSummary: Async = Uninitialized, val asyncTimelineData: Async = Uninitialized ) : MvRxState { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomCategoryItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomCategoryItem.kt index 96f97e37..5a837fd0 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomCategoryItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomCategoryItem.kt @@ -41,7 +41,7 @@ abstract class RoomCategoryItem : VectorEpoxyModel() { 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() } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListDisplayModeFilter.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListDisplayModeFilter.kt new file mode 100644 index 00000000..958c431a --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListDisplayModeFilter.kt @@ -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 { + + 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 + } + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt index 6c640d30..c9ead506 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt @@ -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.empty()) private val _openRoomLiveData = MutableLiveData>() @@ -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): RoomSummaries { val invites = ArrayList() val favourites = ArrayList() diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt index 476dfac3..a6afc66d 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewState.kt @@ -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> = Uninitialized, + val asyncFilteredRooms: Async = Uninitialized, val isInviteExpanded: Boolean = true, val isFavouriteRoomsExpanded: Boolean = true, val isDirectRoomsExpanded: Boolean = true, diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt index f9ada8a5..bf6fbcaa 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryItem.kt @@ -46,7 +46,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { 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) } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/UnreadCounterBadgeView.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/UnreadCounterBadgeView.kt index 44de73e8..1f69a6a7 100755 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/UnreadCounterBadgeView.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/UnreadCounterBadgeView.kt @@ -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 + ) } diff --git a/vector/src/main/res/layout/fragment_selected_group.xml b/vector/src/main/res/layout/fragment_home_detail.xml similarity index 98% rename from vector/src/main/res/layout/fragment_selected_group.xml rename to vector/src/main/res/layout/fragment_home_detail.xml index d29b1e7d..9ef12b15 100644 --- a/vector/src/main/res/layout/fragment_selected_group.xml +++ b/vector/src/main/res/layout/fragment_home_detail.xml @@ -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" diff --git a/vector/src/main/res/layout/item_room.xml b/vector/src/main/res/layout/item_room.xml index 677d2742..05953f1a 100644 --- a/vector/src/main/res/layout/item_room.xml +++ b/vector/src/main/res/layout/item_room.xml @@ -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"> @@ -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" /> - @@ -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" /> - + + diff --git a/vector/src/main/res/layout/vector_unread_layout.xml b/vector/src/main/res/layout/vector_unread_layout.xml new file mode 100644 index 00000000..60be539d --- /dev/null +++ b/vector/src/main/res/layout/vector_unread_layout.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file