From 268730e71b4f6d9c2f6d2282fc6665311418ce61 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 15 May 2019 19:44:06 +0200 Subject: [PATCH 01/34] Home: start reworking UX [WIP] --- vector/src/main/AndroidManifest.xml | 1 + .../features/home/HomeActivity.kt | 22 ++-- .../features/home/HomeDrawerFragment.kt | 14 ++- .../features/home/HomeNavigator.kt | 33 +++--- .../features/home/HomePermalinkHandler.kt | 4 +- .../features/home/LoadingFragment.kt | 49 ++++++++ .../features/home/group/GroupListFragment.kt | 6 + .../features/home/group/GroupListViewModel.kt | 33 ++++-- .../features/home/group/GroupSummaryItem.kt | 8 +- .../home/group/SelectedGroupFragment.kt | 111 ++++++++++++++++++ .../room/detail/LoadingRoomDetailFragment.kt | 47 -------- .../home/room/detail/RoomDetailActivity.kt | 57 +++++++++ .../home/room/detail/RoomDetailFragment.kt | 43 +++---- .../home/room/list/RoomListFragment.kt | 38 +++--- .../home/room/list/RoomListViewModel.kt | 6 +- .../main/res/layout/activity_room_detail.xml | 8 ++ .../main/res/layout/fragment_group_list.xml | 3 +- .../main/res/layout/fragment_home_drawer.xml | 61 ++++++++-- ...g_room_detail.xml => fragment_loading.xml} | 0 .../main/res/layout/fragment_room_list.xml | 52 ++------ .../res/layout/fragment_selected_group.xml | 38 ++++++ vector/src/main/res/layout/item_group.xml | 40 +++++-- .../res/menu/selected_group_navigation.xml | 25 ++++ 23 files changed, 498 insertions(+), 201 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/LoadingFragment.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/group/SelectedGroupFragment.kt delete mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/LoadingRoomDetailFragment.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActivity.kt create mode 100644 vector/src/main/res/layout/activity_room_detail.xml rename vector/src/main/res/layout/{fragment_loading_room_detail.xml => fragment_loading.xml} (100%) create mode 100644 vector/src/main/res/layout/fragment_selected_group.xml create mode 100644 vector/src/main/res/menu/selected_group_navigation.xml diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 591b1885..5d8d3dc7 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -44,6 +44,7 @@ android:label="@string/title_activity_emoji_reaction_picker" /> + { // TODO better UI if (it) { @@ -115,30 +110,27 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { override fun configure(toolbar: Toolbar) { setSupportActionBar(toolbar) supportActionBar?.setHomeButtonEnabled(true) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - val drawerToggle = ActionBarDrawerToggle(this, drawerLayout, toolbar, 0, 0) - drawerLayout.addDrawerListener(drawerToggle) - drawerToggle.syncState() + supportActionBar?.setDisplayUseLogoEnabled(true) } override fun getMenuRes() = R.menu.home override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - android.R.id.home -> { + android.R.id.home -> { drawerLayout.openDrawer(GravityCompat.START) return true } - R.id.sliding_menu_settings -> { + R.id.sliding_menu_settings -> { startActivity(VectorSettingsActivity.getIntent(this, "TODO")) return true } - R.id.sliding_menu_sign_out -> { + R.id.sliding_menu_sign_out -> { SignOutUiWorker(this).perform(Matrix.getInstance().currentSession!!) return true } // TODO Temporary code here to create a room - R.id.tmp_menu_create_room -> { + R.id.tmp_menu_create_room -> { // Start Activity for now startActivity(Intent(this, RoomDirectoryActivity::class.java)) return true diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt index 4458d456..9566e881 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt @@ -17,11 +17,12 @@ package im.vector.riotredesign.features.home import android.os.Bundle +import im.vector.matrix.android.api.Matrix import im.vector.riotredesign.R import im.vector.riotredesign.core.extensions.replaceChildFragment import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.features.home.group.GroupListFragment -import im.vector.riotredesign.features.home.room.list.RoomListFragment +import kotlinx.android.synthetic.main.fragment_home_drawer.* class HomeDrawerFragment : VectorBaseFragment() { @@ -38,9 +39,14 @@ class HomeDrawerFragment : VectorBaseFragment() { super.onActivityCreated(savedInstanceState) if (savedInstanceState == null) { val groupListFragment = GroupListFragment.newInstance() - replaceChildFragment(groupListFragment, R.id.groupListFragmentContainer) - val roomListFragment = RoomListFragment.newInstance() - replaceChildFragment(roomListFragment, R.id.roomListFragmentContainer) + replaceChildFragment(groupListFragment, R.id.homeDrawerGroupListContainer) + } + val session = Matrix.getInstance().currentSession ?: return + val user = session.getUser(session.sessionParams.credentials.userId) + if (user != null) { + AvatarRenderer.render(user.avatarUrl, user.userId, user.displayName, homeDrawerHeaderAvatarView) + homeDrawerUsernameView.text = user.displayName + homeDrawerUserIdView.text = user.userId } } 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 ad3cd4cf..d435cd60 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 @@ -18,11 +18,13 @@ package im.vector.riotredesign.features.home import androidx.core.view.GravityCompat 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.addFragmentToBackstack 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 im.vector.riotredesign.features.home.room.detail.RoomDetailFragment import kotlinx.android.synthetic.main.activity_home.* import timber.log.Timber @@ -32,22 +34,25 @@ class HomeNavigator { private var rootRoomId: String? = null + 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) + it.drawerLayout?.closeDrawer(GravityCompat.START) + it.replaceFragment(selectedGroupFragment, R.id.homeDetailFragmentContainer) + } + } + fun openRoomDetail(roomId: String, - eventId: String?, - addToBackstack: Boolean = false) { - Timber.v("Open room detail $roomId - $eventId - $addToBackstack") + eventId: String?) { + Timber.v("Open room detail $roomId - $eventId") activity?.let { //TODO enable eventId permalink. It doesn't work enough at the moment. - val args = RoomDetailArgs(roomId) - val roomDetailFragment = RoomDetailFragment.newInstance(args) it.drawerLayout?.closeDrawer(GravityCompat.START) - if (addToBackstack) { - it.addFragmentToBackstack(roomDetailFragment, R.id.homeDetailFragmentContainer, roomId) - } else { - rootRoomId = roomId - clearBackStack(it.supportFragmentManager) - it.replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer) - } + val args = RoomDetailArgs(roomId) + val roomDetailIntent = RoomDetailActivity.newIntent(it, args) + it.startActivity(roomDetailIntent) } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomePermalinkHandler.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomePermalinkHandler.kt index 143ddb57..f2a42a33 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomePermalinkHandler.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomePermalinkHandler.kt @@ -34,10 +34,10 @@ class HomePermalinkHandler(private val navigator: HomeNavigator) { val permalinkData = PermalinkParser.parse(deepLink) when (permalinkData) { is PermalinkData.EventLink -> { - navigator.openRoomDetail(permalinkData.roomIdOrAlias, permalinkData.eventId, true) + navigator.openRoomDetail(permalinkData.roomIdOrAlias, permalinkData.eventId) } is PermalinkData.RoomLink -> { - navigator.openRoomDetail(permalinkData.roomIdOrAlias, null, true) + navigator.openRoomDetail(permalinkData.roomIdOrAlias, null) } is PermalinkData.GroupLink -> { navigator.openGroupDetail(permalinkData.groupId) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/LoadingFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/LoadingFragment.kt new file mode 100644 index 00000000..de5eca71 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/LoadingFragment.kt @@ -0,0 +1,49 @@ +/* + * + * * 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 + +import android.graphics.drawable.AnimationDrawable +import android.os.Bundle +import android.view.View +import im.vector.riotredesign.R +import im.vector.riotredesign.core.platform.VectorBaseFragment +import kotlinx.android.synthetic.main.fragment_loading.* + +class LoadingFragment : VectorBaseFragment() { + + companion object { + + fun newInstance(): LoadingFragment { + return LoadingFragment() + } + } + + override fun getLayoutResId() = R.layout.fragment_loading + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val background = animatedLogoImageView.background + if (background is AnimationDrawable) { + background.start() + } + } + + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListFragment.kt index 5ed550a2..884b7a61 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListFragment.kt @@ -22,9 +22,11 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.fragmentViewModel import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.riotredesign.R +import im.vector.riotredesign.core.extensions.observeEvent import im.vector.riotredesign.core.platform.StateView import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.features.home.HomeModule +import im.vector.riotredesign.features.home.HomeNavigator import kotlinx.android.synthetic.main.fragment_group_list.* import org.koin.android.ext.android.inject import org.koin.android.scope.ext.android.bindScope @@ -39,6 +41,7 @@ class GroupListFragment : VectorBaseFragment(), GroupSummaryController.Callback } private val viewModel: GroupListViewModel by fragmentViewModel() + private val homeNavigator by inject() private val groupController by inject() override fun getLayoutResId() = R.layout.fragment_group_list @@ -50,6 +53,9 @@ class GroupListFragment : VectorBaseFragment(), GroupSummaryController.Callback stateView.contentView = epoxyRecyclerView epoxyRecyclerView.setController(groupController) viewModel.subscribe { renderState(it) } + viewModel.openGroupLiveData.observeEvent(this) { + homeNavigator.openSelectedGroup(it) + } } private fun renderState(state: GroupListViewState) { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt index 1cd23802..bbe042df 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt @@ -16,17 +16,25 @@ package im.vector.riotredesign.features.home.group +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import arrow.core.Option import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.rx.rx import im.vector.riotredesign.core.platform.VectorViewModel +import im.vector.riotredesign.core.resources.StringProvider +import im.vector.riotredesign.core.utils.LiveEvent import org.koin.android.ext.android.get +const val ALL_COMMUNITIES_GROUP_ID = "ALL_COMMUNITIES_GROUP_ID" + class GroupListViewModel(initialState: GroupListViewState, private val selectedGroupHolder: SelectedGroupStore, - private val session: Session + private val session: Session, + private val stringProvider: StringProvider ) : VectorViewModel(initialState) { companion object : MvRxViewModelFactory { @@ -35,10 +43,15 @@ class GroupListViewModel(initialState: GroupListViewState, override fun create(viewModelContext: ViewModelContext, state: GroupListViewState): GroupListViewModel? { val currentSession = viewModelContext.activity.get() val selectedGroupHolder = viewModelContext.activity.get() - return GroupListViewModel(state, selectedGroupHolder, currentSession) + val stringProvider = viewModelContext.activity.get() + return GroupListViewModel(state, selectedGroupHolder, currentSession, stringProvider) } } + private val _openGroupLiveData = MutableLiveData>() + val openGroupLiveData: LiveData> + get() = _openGroupLiveData + init { observeGroupSummaries() observeState() @@ -46,8 +59,8 @@ class GroupListViewModel(initialState: GroupListViewState, private fun observeState() { subscribe { - val selectedGroup = Option.fromNullable(it.selectedGroup) - selectedGroupHolder.post(selectedGroup) + val optionGroup = Option.fromNullable(it.selectedGroup) + selectedGroupHolder.post(optionGroup) } } @@ -62,15 +75,21 @@ class GroupListViewModel(initialState: GroupListViewState, private fun handleSelectGroup(action: GroupListActions.SelectGroup) = withState { state -> if (state.selectedGroup?.groupId != action.groupSummary.groupId) { setState { copy(selectedGroup = action.groupSummary) } - } else { - setState { copy(selectedGroup = null) } + _openGroupLiveData.postValue(LiveEvent(action.groupSummary)) } } - private fun observeGroupSummaries() { session .rx().liveGroupSummaries() + .map { + val myUser = session.getUser(session.sessionParams.credentials.userId) + val allCommunityGroup = GroupSummary( + groupId = ALL_COMMUNITIES_GROUP_ID, + displayName = "All Communities", + avatarUrl = myUser?.avatarUrl ?: "") + listOf(allCommunityGroup) + it + } .execute { async -> copy(asyncGroups = async) } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryItem.kt index eef0e865..2bb1f81e 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryItem.kt @@ -16,13 +16,14 @@ package im.vector.riotredesign.features.home.group +import android.view.ViewGroup import android.widget.ImageView +import android.widget.TextView import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotredesign.R import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder import im.vector.riotredesign.core.epoxy.VectorEpoxyModel -import im.vector.riotredesign.core.platform.CheckableFrameLayout import im.vector.riotredesign.features.home.AvatarRenderer @EpoxyModelClass(layout = R.layout.item_group) @@ -36,14 +37,15 @@ abstract class GroupSummaryItem : VectorEpoxyModel() { override fun bind(holder: Holder) { super.bind(holder) - holder.rootView.isSelected = selected holder.rootView.setOnClickListener { listener?.invoke() } + holder.groupNameView.text = groupName AvatarRenderer.render(avatarUrl, groupId, groupName.toString(), holder.avatarImageView) } class Holder : VectorEpoxyHolder() { val avatarImageView by bind(R.id.groupAvatarImageView) - val rootView by bind(R.id.itemGroupLayout) + val groupNameView by bind(R.id.groupNameView) + val rootView by bind(R.id.itemGroupLayout) } } \ No newline at end of file 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/group/SelectedGroupFragment.kt new file mode 100644 index 00000000..add2ef6f --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/group/SelectedGroupFragment.kt @@ -0,0 +1,111 @@ +/* + * + * * 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.group + +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.os.Parcelable +import com.airbnb.mvrx.args +import com.bumptech.glide.request.target.SimpleTarget +import com.bumptech.glide.request.transition.Transition +import im.vector.riotredesign.R +import im.vector.riotredesign.core.extensions.replaceChildFragment +import im.vector.riotredesign.core.glide.GlideApp +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 kotlinx.android.parcel.Parcelize +import kotlinx.android.synthetic.main.fragment_selected_group.* + +@Parcelize +data class SelectedGroupParams( + val groupId: String, + val groupName: String, + val groupAvatar: String +) : Parcelable + +class SelectedGroupFragment : VectorBaseFragment() { + + private val selectedGroupParams: SelectedGroupParams by args() + + override fun getLayoutResId(): Int { + return R.layout.fragment_selected_group + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + if (savedInstanceState == null) { + updateSelectedFragment(RoomListFragment.DisplayMode.HOME) + toolbar.setTitle(RoomListFragment.DisplayMode.HOME.titleRes) + } + setupBottomNavigationView() + setupToolbar() + } + + private fun setupToolbar() { + val parentActivity = vectorBaseActivity + if (parentActivity is ToolbarConfigurable) { + parentActivity.configure(toolbar) + } + val toolbarLogoTarget = object : SimpleTarget() { + override fun onResourceReady(resource: Drawable, transition: Transition?) { + toolbar.logo = resource + } + } + AvatarRenderer.render( + requireContext(), + GlideApp.with(this), + selectedGroupParams.groupAvatar, + selectedGroupParams.groupId, + selectedGroupParams.groupName, + toolbarLogoTarget + ) + } + + private fun setupBottomNavigationView() { + bottomNavigationView.setOnNavigationItemSelectedListener { + val displayMode = when { + it.itemId == R.id.bottom_action_people -> RoomListFragment.DisplayMode.PEOPLE + it.itemId == R.id.bottom_action_rooms -> RoomListFragment.DisplayMode.ROOMS + else -> RoomListFragment.DisplayMode.HOME + } + updateSelectedFragment(displayMode) + toolbar.setTitle(displayMode.titleRes) + true + } + } + + private fun updateSelectedFragment(displayMode: RoomListFragment.DisplayMode) { + val roomListParams = RoomListParams(displayMode) + val roomListFragment = RoomListFragment.newInstance(roomListParams) + replaceChildFragment(roomListFragment, R.id.roomListContainer) + } + + companion object { + + fun newInstance(args: SelectedGroupParams): SelectedGroupFragment { + return SelectedGroupFragment().apply { + setArguments(args) + } + } + + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/LoadingRoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/LoadingRoomDetailFragment.kt deleted file mode 100644 index eefb6566..00000000 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/LoadingRoomDetailFragment.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.detail - -import android.graphics.drawable.AnimationDrawable -import android.os.Bundle -import android.view.View -import im.vector.riotredesign.R -import im.vector.riotredesign.core.platform.VectorBaseFragment -import kotlinx.android.synthetic.main.fragment_loading_room_detail.* - -class LoadingRoomDetailFragment : VectorBaseFragment() { - - companion object { - - fun newInstance(): LoadingRoomDetailFragment { - return LoadingRoomDetailFragment() - } - } - - override fun getLayoutResId() = R.layout.fragment_loading_room_detail - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val background = animatedLogoImageView.background - if (background is AnimationDrawable) { - background.start() - } - } - - -} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActivity.kt new file mode 100644 index 00000000..fd79e8c1 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActivity.kt @@ -0,0 +1,57 @@ +/* + * + * * 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.detail + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import im.vector.riotredesign.R +import im.vector.riotredesign.core.extensions.replaceFragment +import im.vector.riotredesign.core.platform.VectorBaseActivity + +class RoomDetailActivity : VectorBaseActivity() { + + override fun getLayoutRes(): Int { + return R.layout.activity_room_detail + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (savedInstanceState == null) { + val roomDetailArgs: RoomDetailArgs = intent?.extras?.getParcelable(EXTRA_ROOM_DETAIL_ARGS) + ?: return + val roomDetailFragment = RoomDetailFragment.newInstance(roomDetailArgs) + replaceFragment(roomDetailFragment, R.id.roomDetailContainer) + } + } + + companion object { + + private const val EXTRA_ROOM_DETAIL_ARGS = "EXTRA_ROOM_DETAIL_ARGS" + + fun newIntent(context: Context, roomDetailArgs: RoomDetailArgs): Intent { + return Intent(context, RoomDetailActivity::class.java).apply { + putExtra(EXTRA_ROOM_DETAIL_ARGS, roomDetailArgs) + } + } + + + } + +} \ 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 0278607a..dbc7408a 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 @@ -64,7 +64,6 @@ import im.vector.riotredesign.core.epoxy.LayoutManagerStateRestorer import im.vector.riotredesign.core.extensions.hideKeyboard import im.vector.riotredesign.core.extensions.observeEvent import im.vector.riotredesign.core.glide.GlideApp -import im.vector.riotredesign.core.platform.ToolbarConfigurable import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.utils.* import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandPresenter @@ -170,7 +169,6 @@ class RoomDetailFragment : actionViewModel = ViewModelProviders.of(requireActivity()).get(ActionsHandler::class.java) bindScope(getOrCreateScope(HomeModule.ROOM_DETAIL_SCOPE)) setupRecyclerView() - setupToolbar() setupComposer() setupAttachmentButton() setupInviteView() @@ -194,7 +192,7 @@ class RoomDetailFragment : if (resultCode == RESULT_OK && data != null) { when (requestCode) { REQUEST_FILES_REQUEST_CODE, TAKE_IMAGE_REQUEST_CODE -> handleMediaIntent(data) - REACTION_SELECT_REQUEST_CODE -> { + REACTION_SELECT_REQUEST_CODE -> { val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID) ?: return val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT) @@ -213,13 +211,6 @@ class RoomDetailFragment : // PRIVATE METHODS ***************************************************************************** - private fun setupToolbar() { - val parentActivity = vectorBaseActivity - if (parentActivity is ToolbarConfigurable) { - parentActivity.configure(toolbar) - } - } - private fun setupRecyclerView() { val epoxyVisibilityTracker = EpoxyVisibilityTracker() epoxyVisibilityTracker.attach(recyclerView) @@ -362,24 +353,24 @@ class RoomDetailFragment : private fun onSendChoiceClicked(dialogListItem: DialogListItem) { Timber.v("On send choice clicked: $dialogListItem") when (dialogListItem) { - is DialogListItem.SendFile -> { + is DialogListItem.SendFile -> { // launchFileIntent } - is DialogListItem.SendVoice -> { + is DialogListItem.SendVoice -> { //launchAudioRecorderIntent() } - is DialogListItem.SendSticker -> { + is DialogListItem.SendSticker -> { //startStickerPickerActivity() } is DialogListItem.TakePhotoVideo -> if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) { // launchCamera() } - is DialogListItem.TakePhoto -> + is DialogListItem.TakePhoto -> if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA)) { openCamera(requireActivity(), CAMERA_VALUE_TITLE, TAKE_IMAGE_REQUEST_CODE) } - is DialogListItem.TakeVideo -> + is DialogListItem.TakeVideo -> if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA)) { // launchNativeVideoRecorder() } @@ -426,20 +417,20 @@ class RoomDetailFragment : private fun renderSendMessageResult(sendMessageResult: SendMessageResult) { when (sendMessageResult) { is SendMessageResult.MessageSent, - is SendMessageResult.SlashCommandHandled -> { + is SendMessageResult.SlashCommandHandled -> { // Clear composer composerEditText.text = null } - is SendMessageResult.SlashCommandError -> { + is SendMessageResult.SlashCommandError -> { displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command)) } - is SendMessageResult.SlashCommandUnknown -> { + is SendMessageResult.SlashCommandUnknown -> { displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command)) } - is SendMessageResult.SlashCommandResultOk -> { + is SendMessageResult.SlashCommandResultOk -> { // Ignore } - is SendMessageResult.SlashCommandResultError -> { + is SendMessageResult.SlashCommandResultError -> { displayCommandError(sendMessageResult.throwable.localizedMessage) } is SendMessageResult.SlashCommandNotImplemented -> { @@ -537,22 +528,22 @@ class RoomDetailFragment : it?.getContentIfNotHandled()?.let { actionData -> when (actionData.actionId) { - MessageMenuViewModel.ACTION_ADD_REACTION -> { + MessageMenuViewModel.ACTION_ADD_REACTION -> { val eventId = actionData.data?.toString() ?: return startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), eventId), REACTION_SELECT_REQUEST_CODE) } - MessageMenuViewModel.ACTION_COPY -> { + MessageMenuViewModel.ACTION_COPY -> { //I need info about the current selected message :/ copyToClipboard(requireContext(), actionData.data?.toString() ?: "", false) val snack = Snackbar.make(view!!, requireContext().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT) snack.view.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.notification_accent_color)) snack.show() } - MessageMenuViewModel.ACTION_DELETE -> { + MessageMenuViewModel.ACTION_DELETE -> { val eventId = actionData.data?.toString() ?: return roomDetailViewModel.process(RoomDetailActions.RedactAction(eventId, context?.getString(R.string.event_redacted_by_user_reason))) } - MessageMenuViewModel.ACTION_SHARE -> { + MessageMenuViewModel.ACTION_SHARE -> { //TODO current data communication is too limited //Need to now the media type actionData.data?.toString()?.let { @@ -595,13 +586,13 @@ class RoomDetailFragment : .setPositiveButton(R.string.ok) { dialog, id -> dialog.cancel() } .show() } - MessageMenuViewModel.ACTION_QUICK_REACT -> { + MessageMenuViewModel.ACTION_QUICK_REACT -> { //eventId,ClickedOn,Opposite (actionData.data as? Triple)?.let { (eventId, clickedOn, opposite) -> roomDetailViewModel.process(RoomDetailActions.UpdateQuickReactAction(eventId, clickedOn, opposite)) } } - else -> { + else -> { Toast.makeText(context, "Action ${actionData.actionId} not implemented", Toast.LENGTH_LONG).show() } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt index 5a43a21e..794c011d 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt @@ -17,8 +17,8 @@ package im.vector.riotredesign.features.home.room.list import android.os.Bundle -import android.text.Editable -import android.text.TextWatcher +import android.os.Parcelable +import androidx.annotation.StringRes import androidx.recyclerview.widget.LinearLayoutManager import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Incomplete @@ -29,21 +29,35 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotredesign.R import im.vector.riotredesign.core.epoxy.LayoutManagerStateRestorer import im.vector.riotredesign.core.extensions.observeEvent -import im.vector.riotredesign.core.extensions.setupAsSearch import im.vector.riotredesign.core.platform.StateView import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.features.home.HomeModule import im.vector.riotredesign.features.home.HomeNavigator +import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_room_list.* import org.koin.android.ext.android.inject import org.koin.android.scope.ext.android.bindScope import org.koin.android.scope.ext.android.getOrCreateScope +@Parcelize +data class RoomListParams( + val displayMode: RoomListFragment.DisplayMode +) : Parcelable + + class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback { + enum class DisplayMode(@StringRes val titleRes: Int) { + HOME(R.string.bottom_action_home), + PEOPLE(R.string.bottom_action_people), + ROOMS(R.string.bottom_action_rooms) + } + companion object { - fun newInstance(): RoomListFragment { - return RoomListFragment() + fun newInstance(roomListParams: RoomListParams): RoomListFragment { + return RoomListFragment().apply { + setArguments(roomListParams) + } } } @@ -57,7 +71,6 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback { super.onActivityCreated(savedInstanceState) bindScope(getOrCreateScope(HomeModule.ROOM_LIST_SCOPE)) setupRecyclerView() - setupFilterView() roomListViewModel.subscribe { renderState(it) } roomListViewModel.openRoomLiveData.observeEvent(this) { homeNavigator.openRoomDetail(it, null) @@ -74,19 +87,6 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback { epoxyRecyclerView.setController(roomController) } - private fun setupFilterView() { - filterRoomView.setupAsSearch() - filterRoomView.addTextChangedListener(object : TextWatcher { - override fun afterTextChanged(s: Editable?) = Unit - - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - roomListViewModel.accept(RoomListActions.FilterRooms(s)) - } - }) - } - private fun renderState(state: RoomListViewState) { when (state.asyncRooms) { is Incomplete -> renderLoading() 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 b9476ce5..fd9c19d3 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 @@ -30,6 +30,7 @@ import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.matrix.rx.rx import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.utils.LiveEvent +import im.vector.riotredesign.features.home.group.ALL_COMMUNITIES_GROUP_ID import im.vector.riotredesign.features.home.group.SelectedGroupStore import im.vector.riotredesign.features.home.room.VisibleRoomStore import io.reactivex.Observable @@ -118,7 +119,7 @@ class RoomListViewModel(initialState: RoomListViewState, val filteredDirectRooms = filteredRooms .filter { it.isDirect } .filter { - if (selectedGroup == null) { + if (selectedGroup == null || selectedGroup.groupId == ALL_COMMUNITIES_GROUP_ID) { true } else { it.otherMemberIds @@ -130,7 +131,8 @@ class RoomListViewModel(initialState: RoomListViewState, val filteredGroupRooms = filteredRooms .filter { !it.isDirect } .filter { - selectedGroup?.roomIds?.contains(it.roomId) ?: true + selectedGroup?.groupId == ALL_COMMUNITIES_GROUP_ID + || selectedGroup?.roomIds?.contains(it.roomId) ?: true } buildRoomSummaries(filteredDirectRooms + filteredGroupRooms) } diff --git a/vector/src/main/res/layout/activity_room_detail.xml b/vector/src/main/res/layout/activity_room_detail.xml new file mode 100644 index 00000000..1dae010e --- /dev/null +++ b/vector/src/main/res/layout/activity_room_detail.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_group_list.xml b/vector/src/main/res/layout/fragment_group_list.xml index a075d511..810fe3e4 100644 --- a/vector/src/main/res/layout/fragment_group_list.xml +++ b/vector/src/main/res/layout/fragment_group_list.xml @@ -4,8 +4,7 @@ + android:layout_height="match_parent"> - + + + + + + + + + + + + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/homeDrawerHeader" /> \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_loading_room_detail.xml b/vector/src/main/res/layout/fragment_loading.xml similarity index 100% rename from vector/src/main/res/layout/fragment_loading_room_detail.xml rename to vector/src/main/res/layout/fragment_loading.xml diff --git a/vector/src/main/res/layout/fragment_room_list.xml b/vector/src/main/res/layout/fragment_room_list.xml index 6355db77..19cfdd59 100644 --- a/vector/src/main/res/layout/fragment_room_list.xml +++ b/vector/src/main/res/layout/fragment_room_list.xml @@ -1,53 +1,15 @@ - - + - - - - - - - \ No newline at end of file + diff --git a/vector/src/main/res/layout/fragment_selected_group.xml b/vector/src/main/res/layout/fragment_selected_group.xml new file mode 100644 index 00000000..e2cd454d --- /dev/null +++ b/vector/src/main/res/layout/fragment_selected_group.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_group.xml b/vector/src/main/res/layout/item_group.xml index 7dec77be..cfc230c9 100644 --- a/vector/src/main/res/layout/item_group.xml +++ b/vector/src/main/res/layout/item_group.xml @@ -1,22 +1,48 @@ - + android:padding="16dp"> - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/vector/src/main/res/menu/selected_group_navigation.xml b/vector/src/main/res/menu/selected_group_navigation.xml new file mode 100644 index 00000000..e0491950 --- /dev/null +++ b/vector/src/main/res/menu/selected_group_navigation.xml @@ -0,0 +1,25 @@ + + + + + + + + + + From 275521db70419371e81c5594297f5179fc7147d7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 16 May 2019 19:14:02 +0200 Subject: [PATCH 02/34] Home: continue architecture rework. WIP --- .../android/api/session/user/UserService.kt | 8 ++ .../internal/database/mapper/UserMapper.kt | 27 +++--- .../internal/session/DefaultSession.kt | 5 + .../session/user/DefaultUserService.kt | 23 +++-- .../vector/riotredesign/core/di/AppModule.kt | 11 --- .../features/home/HomeActivity.kt | 6 -- .../features/home/HomeActivityViewModel.kt | 72 ++++++++------ .../features/home/HomeDrawerFragment.kt | 17 ++-- .../riotredesign/features/home/HomeModule.kt | 25 +++-- .../home/HomeRoomListObservableStore.kt | 22 +++++ .../features/home/group/GroupListViewModel.kt | 17 ++-- .../features/home/group/GroupSummaryItem.kt | 5 +- .../home/group/SelectedGroupFragment.kt | 58 +++++++---- .../home/room/detail/RoomDetailActions.kt | 1 - .../home/room/detail/RoomDetailActivity.kt | 14 ++- .../home/room/detail/RoomDetailFragment.kt | 24 +++-- .../home/room/detail/RoomDetailViewModel.kt | 62 ++++++------ .../home/room/list/RoomListFragment.kt | 10 ++ .../home/room/list/RoomListViewModel.kt | 76 ++------------- .../home/room/list/RoomListViewState.kt | 1 - .../home/room/list/RoomSummaryController.kt | 6 +- .../home/room/list/RoomSummaryItem.kt | 6 +- .../{bg_room_item.xml => bg_group_item.xml} | 2 +- .../main/res/layout/fragment_home_drawer.xml | 17 ++-- .../main/res/layout/fragment_room_detail.xml | 20 ++-- .../main/res/layout/fragment_room_list.xml | 7 ++ .../res/layout/fragment_selected_group.xml | 35 ++++++- vector/src/main/res/layout/item_group.xml | 84 +++++++++------- vector/src/main/res/layout/item_room.xml | 96 +++++++++---------- vector/src/main/res/menu/home.xml | 5 - 30 files changed, 416 insertions(+), 346 deletions(-) rename vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSelectionRepository.kt => matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/UserMapper.kt (51%) create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/HomeRoomListObservableStore.kt rename vector/src/main/res/drawable/{bg_room_item.xml => bg_group_item.xml} (86%) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt index 8e90e5e9..356ea6cc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/user/UserService.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.user +import androidx.lifecycle.LiveData import im.vector.matrix.android.api.session.user.model.User /** @@ -32,4 +33,11 @@ interface UserService { */ fun getUser(userId: String): User? + /** + * Observe a live user from a userId + * @param userId the userId to look for. + * @return a Livedata of user with userId + */ + fun observeUser(userId: String): LiveData + } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSelectionRepository.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/UserMapper.kt similarity index 51% rename from vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSelectionRepository.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/UserMapper.kt index 9131d429..2389427c 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSelectionRepository.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/UserMapper.kt @@ -14,23 +14,22 @@ * limitations under the License. */ -package im.vector.riotredesign.features.home.room.list +package im.vector.matrix.android.internal.database.mapper -import android.content.SharedPreferences +import im.vector.matrix.android.api.session.user.model.User +import im.vector.matrix.android.internal.database.model.UserEntity -private const val SHARED_PREFS_SELECTED_ROOM_KEY = "SHARED_PREFS_SELECTED_ROOM_KEY" +internal object UserMapper { -class RoomSelectionRepository(private val sharedPreferences: SharedPreferences) { - - fun lastSelectedRoom(): String? { - return sharedPreferences.getString(SHARED_PREFS_SELECTED_ROOM_KEY, null) + fun map(userEntity: UserEntity): User { + return User( + userEntity.userId, + userEntity.displayName, + userEntity.avatarUrl + ) } - - fun saveLastSelectedRoom(roomId: String) { - sharedPreferences.edit() - .putString(SHARED_PREFS_SELECTED_ROOM_KEY, roomId) - .apply() - } - } +internal fun UserEntity.asDomain(): User { + return UserMapper.map(this) +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 420d675c..2b43c15c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -231,6 +231,11 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi return userService.getUser(userId) } + override fun observeUser(userId: String): LiveData { + assert(isOpen) + return userService.observeUser(userId) + } + // Private methods ***************************************************************************** private fun assertMainThread() { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/DefaultUserService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/DefaultUserService.kt index b8e1f028..e3f68f06 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/DefaultUserService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/DefaultUserService.kt @@ -18,9 +18,13 @@ package im.vector.matrix.android.internal.session.user +import androidx.lifecycle.LiveData +import androidx.lifecycle.Transformations import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.api.session.user.model.User +import im.vector.matrix.android.internal.database.RealmLiveData +import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.UserEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.util.fetchCopied @@ -29,12 +33,19 @@ internal class DefaultUserService(private val monarchy: Monarchy) : UserService override fun getUser(userId: String): User? { val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() } - ?: return null + ?: return null - return User( - userEntity.userId, - userEntity.displayName, - userEntity.avatarUrl - ) + return userEntity.asDomain() + } + + override fun observeUser(userId: String): LiveData { + val liveRealmData = RealmLiveData(monarchy.realmConfiguration) { realm -> + UserEntity.where(realm, userId) + } + return Transformations.map(liveRealmData) { results -> + results + .map { it.asDomain() } + .firstOrNull() + } } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt index 6aff3e5a..2c654126 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt @@ -24,8 +24,6 @@ import im.vector.riotredesign.core.resources.LocaleProvider import im.vector.riotredesign.core.resources.StringArrayProvider import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.features.home.group.SelectedGroupStore -import im.vector.riotredesign.features.home.room.VisibleRoomStore -import im.vector.riotredesign.features.home.room.list.RoomSelectionRepository import im.vector.riotredesign.features.home.room.list.RoomSummaryComparator import im.vector.riotredesign.features.notifications.NotificationDrawerManager import org.koin.dsl.module.module @@ -50,18 +48,10 @@ class AppModule(private val context: Context) { context.getSharedPreferences("im.vector.riot", MODE_PRIVATE) } - single { - RoomSelectionRepository(get()) - } - single { SelectedGroupStore() } - single { - VisibleRoomStore() - } - single { RoomSummaryComparator() } @@ -78,6 +68,5 @@ class AppModule(private val context: Context) { Matrix.getInstance().currentSession!! } - } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt index e30dcd16..a61412df 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt @@ -109,8 +109,6 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { override fun configure(toolbar: Toolbar) { setSupportActionBar(toolbar) - supportActionBar?.setHomeButtonEnabled(true) - supportActionBar?.setDisplayUseLogoEnabled(true) } override fun getMenuRes() = R.menu.home @@ -121,10 +119,6 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { drawerLayout.openDrawer(GravityCompat.START) return true } - R.id.sliding_menu_settings -> { - startActivity(VectorSettingsActivity.getIntent(this, "TODO")) - return true - } R.id.sliding_menu_sign_out -> { SignOutUiWorker(this).perform(Matrix.getInstance().currentSession!!) return true diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivityViewModel.kt index 03529618..c710b477 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivityViewModel.kt @@ -18,26 +18,31 @@ package im.vector.riotredesign.features.home import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import arrow.core.Option import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.group.model.GroupSummary +import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams -import im.vector.matrix.android.api.session.sync.FilterService import im.vector.matrix.rx.rx import im.vector.riotredesign.core.platform.VectorViewModel -import im.vector.riotredesign.core.utils.LiveEvent -import im.vector.riotredesign.features.home.room.list.RoomSelectionRepository -import io.reactivex.rxkotlin.subscribeBy +import im.vector.riotredesign.features.home.group.ALL_COMMUNITIES_GROUP_ID +import im.vector.riotredesign.features.home.group.SelectedGroupStore +import io.reactivex.Observable +import io.reactivex.functions.BiFunction import org.koin.android.ext.android.get +import java.util.concurrent.TimeUnit data class EmptyState(val isEmpty: Boolean = true) : MvRxState class HomeActivityViewModel(state: EmptyState, private val session: Session, - roomSelectionRepository: RoomSelectionRepository + private val selectedGroupStore: SelectedGroupStore, + private val homeRoomListStore: HomeRoomListObservableStore ) : VectorViewModel(state), Session.Listener { companion object : MvRxViewModelFactory { @@ -45,8 +50,9 @@ class HomeActivityViewModel(state: EmptyState, @JvmStatic override fun create(viewModelContext: ViewModelContext, state: EmptyState): HomeActivityViewModel? { val session = Matrix.getInstance().currentSession!! - val roomSelectionRepository = viewModelContext.activity.get() - return HomeActivityViewModel(state, session, roomSelectionRepository) + val selectedGroupStore = viewModelContext.activity.get() + val homeRoomListObservableSource = viewModelContext.activity.get() + return HomeActivityViewModel(state, session, selectedGroupStore, homeRoomListObservableSource) } } @@ -54,29 +60,41 @@ class HomeActivityViewModel(state: EmptyState, val isLoading: LiveData get() = _isLoading - private val _openRoomLiveData = MutableLiveData>() - val openRoomLiveData: LiveData> - get() = _openRoomLiveData - init { session.addListener(this) - val lastSelectedRoomId = roomSelectionRepository.lastSelectedRoom() - if (lastSelectedRoomId == null || session.getRoom(lastSelectedRoomId) == null) { - getTheFirstRoomWhenAvailable() - } else { - _openRoomLiveData.postValue(LiveEvent(lastSelectedRoomId)) - } + observeRoomAndGroup() } - private fun getTheFirstRoomWhenAvailable() { - session.rx().liveRoomSummaries() - .filter { it.isNotEmpty() } - .first(emptyList()) - .subscribeBy { - val firstRoom = it.firstOrNull() - if (firstRoom != null) { - _openRoomLiveData.postValue(LiveEvent(firstRoom.roomId)) - } + private fun observeRoomAndGroup() { + Observable + .combineLatest, Option, List>( + session.rx().liveRoomSummaries().throttleLast(300, TimeUnit.MILLISECONDS), + selectedGroupStore.observe(), + BiFunction { rooms, selectedGroupOption -> + val selectedGroup = selectedGroupOption.orNull() + val filteredDirectRooms = rooms + .filter { it.isDirect } + .filter { + if (selectedGroup == null || selectedGroup.groupId == ALL_COMMUNITIES_GROUP_ID) { + true + } else { + it.otherMemberIds + .intersect(selectedGroup.userIds) + .isNotEmpty() + } + } + + val filteredGroupRooms = rooms + .filter { !it.isDirect } + .filter { + selectedGroup?.groupId == ALL_COMMUNITIES_GROUP_ID + || selectedGroup?.roomIds?.contains(it.roomId) ?: true + } + filteredDirectRooms + filteredGroupRooms + } + ) + .subscribe { + homeRoomListStore.post(it) } .disposeOnClear() } @@ -87,8 +105,6 @@ class HomeActivityViewModel(state: EmptyState, session.createRoom(createRoomParams, object : MatrixCallback { override fun onSuccess(data: String) { _isLoading.value = false - // Open room id - _openRoomLiveData.postValue(LiveEvent(data)) } override fun onFailure(failure: Throwable) { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt index 9566e881..f32216db 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt @@ -19,9 +19,11 @@ package im.vector.riotredesign.features.home import android.os.Bundle import im.vector.matrix.android.api.Matrix import im.vector.riotredesign.R +import im.vector.riotredesign.core.extensions.observeK import im.vector.riotredesign.core.extensions.replaceChildFragment import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.features.home.group.GroupListFragment +import im.vector.riotredesign.features.settings.VectorSettingsActivity import kotlinx.android.synthetic.main.fragment_home_drawer.* class HomeDrawerFragment : VectorBaseFragment() { @@ -42,12 +44,15 @@ class HomeDrawerFragment : VectorBaseFragment() { replaceChildFragment(groupListFragment, R.id.homeDrawerGroupListContainer) } val session = Matrix.getInstance().currentSession ?: return - val user = session.getUser(session.sessionParams.credentials.userId) - if (user != null) { - AvatarRenderer.render(user.avatarUrl, user.userId, user.displayName, homeDrawerHeaderAvatarView) - homeDrawerUsernameView.text = user.displayName - homeDrawerUserIdView.text = user.userId + session.observeUser(session.sessionParams.credentials.userId).observeK(this) { user -> + if (user != null) { + AvatarRenderer.render(user.avatarUrl, user.userId, user.displayName, homeDrawerHeaderAvatarView) + homeDrawerUsernameView.text = user.displayName + homeDrawerUserIdView.text = user.userId + } + } + homeDrawerHeaderSettingsView.setOnClickListener { + startActivity(VectorSettingsActivity.getIntent(requireContext(), "TODO")) } } - } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt index 1474d2da..e5f085aa 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt @@ -25,7 +25,14 @@ import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserControl import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserPresenter import im.vector.riotredesign.features.home.group.GroupSummaryController import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController -import im.vector.riotredesign.features.home.room.detail.timeline.factory.* +import im.vector.riotredesign.features.home.room.detail.timeline.factory.CallItemFactory +import im.vector.riotredesign.features.home.room.detail.timeline.factory.DefaultItemFactory +import im.vector.riotredesign.features.home.room.detail.timeline.factory.MessageItemFactory +import im.vector.riotredesign.features.home.room.detail.timeline.factory.RoomHistoryVisibilityItemFactory +import im.vector.riotredesign.features.home.room.detail.timeline.factory.RoomMemberItemFactory +import im.vector.riotredesign.features.home.room.detail.timeline.factory.RoomNameItemFactory +import im.vector.riotredesign.features.home.room.detail.timeline.factory.RoomTopicItemFactory +import im.vector.riotredesign.features.home.room.detail.timeline.factory.TimelineItemFactory import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotredesign.features.home.room.list.RoomSummaryController @@ -49,6 +56,10 @@ class HomeModule { HomeNavigator() } + scope(HOME_SCOPE) { + HomeRoomListObservableStore() + } + scope(HOME_SCOPE) { HomePermalinkHandler(get()) } @@ -63,12 +74,12 @@ class HomeModule { val messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer) val timelineItemFactory = TimelineItemFactory(messageItemFactory = messageItemFactory, - roomNameItemFactory = RoomNameItemFactory(get()), - roomTopicItemFactory = RoomTopicItemFactory(get()), - roomMemberItemFactory = RoomMemberItemFactory(get()), - roomHistoryVisibilityItemFactory = RoomHistoryVisibilityItemFactory(get()), - callItemFactory = CallItemFactory(get()), - defaultItemFactory = DefaultItemFactory() + roomNameItemFactory = RoomNameItemFactory(get()), + roomTopicItemFactory = RoomTopicItemFactory(get()), + roomMemberItemFactory = RoomMemberItemFactory(get()), + roomHistoryVisibilityItemFactory = RoomHistoryVisibilityItemFactory(get()), + callItemFactory = CallItemFactory(get()), + defaultItemFactory = DefaultItemFactory() ) TimelineEventController(timelineDateFormatter, timelineItemFactory, timelineMediaSizeProvider) } 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 new file mode 100644 index 00000000..a2a3b649 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeRoomListObservableStore.kt @@ -0,0 +1,22 @@ +/* + * 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 + +import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.riotredesign.core.utils.RxStore + +class HomeRoomListObservableStore : RxStore>(emptyList()) \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt index bbe042df..f8141e14 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt @@ -54,13 +54,16 @@ class GroupListViewModel(initialState: GroupListViewState, init { observeGroupSummaries() - observeState() + observeSelectionState() } - private fun observeState() { - subscribe { - val optionGroup = Option.fromNullable(it.selectedGroup) - selectedGroupHolder.post(optionGroup) + private fun observeSelectionState() { + selectSubscribe(GroupListViewState::selectedGroup) { + if (it != null) { + _openGroupLiveData.postValue(LiveEvent(it)) + val optionGroup = Option.fromNullable(it) + selectedGroupHolder.post(optionGroup) + } } } @@ -75,7 +78,6 @@ class GroupListViewModel(initialState: GroupListViewState, private fun handleSelectGroup(action: GroupListActions.SelectGroup) = withState { state -> if (state.selectedGroup?.groupId != action.groupSummary.groupId) { setState { copy(selectedGroup = action.groupSummary) } - _openGroupLiveData.postValue(LiveEvent(action.groupSummary)) } } @@ -91,7 +93,8 @@ class GroupListViewModel(initialState: GroupListViewState, listOf(allCommunityGroup) + it } .execute { async -> - copy(asyncGroups = async) + val newSelectedGroup = selectedGroup ?: async()?.firstOrNull() + copy(asyncGroups = async, selectedGroup = newSelectedGroup) } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryItem.kt index 2bb1f81e..b9afe738 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupSummaryItem.kt @@ -16,7 +16,6 @@ package im.vector.riotredesign.features.home.group -import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import com.airbnb.epoxy.EpoxyAttribute @@ -24,6 +23,7 @@ import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotredesign.R import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder import im.vector.riotredesign.core.epoxy.VectorEpoxyModel +import im.vector.riotredesign.core.platform.CheckableFrameLayout import im.vector.riotredesign.features.home.AvatarRenderer @EpoxyModelClass(layout = R.layout.item_group) @@ -39,13 +39,14 @@ abstract class GroupSummaryItem : VectorEpoxyModel() { super.bind(holder) holder.rootView.setOnClickListener { listener?.invoke() } holder.groupNameView.text = groupName + holder.rootView.isChecked = selected AvatarRenderer.render(avatarUrl, groupId, groupName.toString(), holder.avatarImageView) } class Holder : VectorEpoxyHolder() { val avatarImageView by bind(R.id.groupAvatarImageView) val groupNameView by bind(R.id.groupNameView) - val rootView by bind(R.id.itemGroupLayout) + val rootView by bind(R.id.itemGroupLayout) } } \ No newline at end of file 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/group/SelectedGroupFragment.kt index add2ef6f..a7fbe349 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/group/SelectedGroupFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/group/SelectedGroupFragment.kt @@ -18,15 +18,10 @@ package im.vector.riotredesign.features.home.group -import android.graphics.drawable.Drawable import android.os.Bundle import android.os.Parcelable import com.airbnb.mvrx.args -import com.bumptech.glide.request.target.SimpleTarget -import com.bumptech.glide.request.transition.Transition import im.vector.riotredesign.R -import im.vector.riotredesign.core.extensions.replaceChildFragment -import im.vector.riotredesign.core.glide.GlideApp import im.vector.riotredesign.core.platform.ToolbarConfigurable import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.features.home.AvatarRenderer @@ -42,9 +37,12 @@ data class SelectedGroupParams( val groupAvatar: String ) : Parcelable +private const val CURRENT_DISPLAY_MODE = "CURRENT_DISPLAY_MODE" + class SelectedGroupFragment : VectorBaseFragment() { private val selectedGroupParams: SelectedGroupParams by args() + private lateinit var currentDisplayMode: RoomListFragment.DisplayMode override fun getLayoutResId(): Int { return R.layout.fragment_selected_group @@ -53,31 +51,36 @@ class SelectedGroupFragment : VectorBaseFragment() { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) if (savedInstanceState == null) { - updateSelectedFragment(RoomListFragment.DisplayMode.HOME) - toolbar.setTitle(RoomListFragment.DisplayMode.HOME.titleRes) + currentDisplayMode = RoomListFragment.DisplayMode.HOME + } else { + currentDisplayMode = savedInstanceState.getSerializable(CURRENT_DISPLAY_MODE) as? RoomListFragment.DisplayMode + ?: RoomListFragment.DisplayMode.HOME } + renderState(currentDisplayMode) setupBottomNavigationView() setupToolbar() } + override fun onSaveInstanceState(outState: Bundle) { + outState.putSerializable(CURRENT_DISPLAY_MODE, currentDisplayMode) + super.onSaveInstanceState(outState) + } + private fun setupToolbar() { val parentActivity = vectorBaseActivity if (parentActivity is ToolbarConfigurable) { - parentActivity.configure(toolbar) - } - val toolbarLogoTarget = object : SimpleTarget() { - override fun onResourceReady(resource: Drawable, transition: Transition?) { - toolbar.logo = resource - } + parentActivity.configure(groupToolbar) } + groupToolbar.title = "" AvatarRenderer.render( - requireContext(), - GlideApp.with(this), selectedGroupParams.groupAvatar, selectedGroupParams.groupId, selectedGroupParams.groupName, - toolbarLogoTarget + groupToolbarAvatarImageView ) + groupToolbarAvatarImageView.setOnClickListener { + + } } private fun setupBottomNavigationView() { @@ -87,16 +90,29 @@ class SelectedGroupFragment : VectorBaseFragment() { it.itemId == R.id.bottom_action_rooms -> RoomListFragment.DisplayMode.ROOMS else -> RoomListFragment.DisplayMode.HOME } - updateSelectedFragment(displayMode) - toolbar.setTitle(displayMode.titleRes) + if (currentDisplayMode != displayMode) { + currentDisplayMode = displayMode + renderState(displayMode) + } true } } + private fun renderState(displayMode: RoomListFragment.DisplayMode) { + groupToolbarTitleView.setText(displayMode.titleRes) + updateSelectedFragment(displayMode) + } + private fun updateSelectedFragment(displayMode: RoomListFragment.DisplayMode) { - val roomListParams = RoomListParams(displayMode) - val roomListFragment = RoomListFragment.newInstance(roomListParams) - replaceChildFragment(roomListFragment, R.id.roomListContainer) + val fragmentTag = "FRAGMENT_TAG_${displayMode.name}" + var fragment = childFragmentManager.findFragmentByTag(fragmentTag) + if (fragment == null) { + fragment = RoomListFragment.newInstance(RoomListParams(displayMode)) + } + childFragmentManager.beginTransaction() + .replace(R.id.roomListContainer, fragment, fragmentTag) + .addToBackStack(fragmentTag) + .commit() } companion object { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt index 7d6ffc04..c4732061 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt @@ -25,7 +25,6 @@ sealed class RoomDetailActions { data class SendMessage(val text: String) : RoomDetailActions() data class SendMedia(val mediaFiles: List) : RoomDetailActions() - object IsDisplayed : RoomDetailActions() data class EventDisplayed(val event: TimelineEvent) : RoomDetailActions() data class LoadMore(val direction: Timeline.Direction) : RoomDetailActions() data class SendReaction(val reaction: String, val targetEventId: String) : RoomDetailActions() diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActivity.kt index fd79e8c1..b2ebc4c3 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActivity.kt @@ -21,11 +21,13 @@ package im.vector.riotredesign.features.home.room.detail import android.content.Context import android.content.Intent import android.os.Bundle +import androidx.appcompat.widget.Toolbar import im.vector.riotredesign.R import im.vector.riotredesign.core.extensions.replaceFragment +import im.vector.riotredesign.core.platform.ToolbarConfigurable import im.vector.riotredesign.core.platform.VectorBaseActivity -class RoomDetailActivity : VectorBaseActivity() { +class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable { override fun getLayoutRes(): Int { return R.layout.activity_room_detail @@ -35,12 +37,20 @@ class RoomDetailActivity : VectorBaseActivity() { super.onCreate(savedInstanceState) if (savedInstanceState == null) { val roomDetailArgs: RoomDetailArgs = intent?.extras?.getParcelable(EXTRA_ROOM_DETAIL_ARGS) - ?: return + ?: return val roomDetailFragment = RoomDetailFragment.newInstance(roomDetailArgs) replaceFragment(roomDetailFragment, R.id.roomDetailContainer) } } + override fun configure(toolbar: Toolbar) { + setSupportActionBar(toolbar) + supportActionBar?.let { + it.setDisplayShowHomeEnabled(true) + it.setDisplayHomeAsUpEnabled(true) + } + } + companion object { private const val EXTRA_ROOM_DETAIL_ARGS = "EXTRA_ROOM_DETAIL_ARGS" 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 dbc7408a..87b30e8f 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 @@ -64,6 +64,7 @@ import im.vector.riotredesign.core.epoxy.LayoutManagerStateRestorer import im.vector.riotredesign.core.extensions.hideKeyboard import im.vector.riotredesign.core.extensions.observeEvent import im.vector.riotredesign.core.glide.GlideApp +import im.vector.riotredesign.core.platform.ToolbarConfigurable import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.utils.* import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandPresenter @@ -168,6 +169,7 @@ class RoomDetailFragment : super.onActivityCreated(savedInstanceState) actionViewModel = ViewModelProviders.of(requireActivity()).get(ActionsHandler::class.java) bindScope(getOrCreateScope(HomeModule.ROOM_DETAIL_SCOPE)) + setupToolbar() setupRecyclerView() setupComposer() setupAttachmentButton() @@ -187,6 +189,13 @@ class RoomDetailFragment : }) } + private fun setupToolbar() { + val parentActivity = vectorBaseActivity + if (parentActivity is ToolbarConfigurable) { + parentActivity.configure(roomToolbar) + } + } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (resultCode == RESULT_OK && data != null) { @@ -204,11 +213,6 @@ class RoomDetailFragment : } } - override fun onResume() { - super.onResume() - roomDetailViewModel.process(RoomDetailActions.IsDisplayed) - } - // PRIVATE METHODS ***************************************************************************** private fun setupRecyclerView() { @@ -399,13 +403,13 @@ class RoomDetailFragment : private fun renderRoomSummary(state: RoomDetailViewState) { state.asyncRoomSummary()?.let { - toolbarTitleView.text = it.displayName - AvatarRenderer.render(it, toolbarAvatarImageView) + roomToolbarTitleView.text = it.displayName + AvatarRenderer.render(it, roomToolbarAvatarImageView) if (it.topic.isNotEmpty()) { - toolbarSubtitleView.visibility = View.VISIBLE - toolbarSubtitleView.text = it.topic + roomToolbarSubtitleView.visibility = View.VISIBLE + roomToolbarSubtitleView.text = it.topic } else { - toolbarSubtitleView.visibility = View.GONE + roomToolbarSubtitleView.visibility = View.GONE } } } 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 44c2aa8f..7f7f27eb 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 @@ -33,7 +33,6 @@ import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.utils.LiveEvent import im.vector.riotredesign.features.command.CommandParser import im.vector.riotredesign.features.command.ParsedCommand -import im.vector.riotredesign.features.home.room.VisibleRoomStore import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDisplayableEvents import io.reactivex.rxkotlin.subscribeBy import org.koin.android.ext.android.get @@ -42,8 +41,7 @@ import java.util.* import java.util.concurrent.TimeUnit class RoomDetailViewModel(initialState: RoomDetailViewState, - private val session: Session, - private val visibleRoomHolder: VisibleRoomStore + private val session: Session ) : VectorViewModel(initialState) { private val room = session.getRoom(initialState.roomId)!! @@ -59,8 +57,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, @JvmStatic override fun create(viewModelContext: ViewModelContext, state: RoomDetailViewState): RoomDetailViewModel? { val currentSession = viewModelContext.activity.get() - val visibleRoomHolder = viewModelContext.activity.get() - return RoomDetailViewModel(state, currentSession, visibleRoomHolder) + return RoomDetailViewModel(state, currentSession) } } @@ -75,18 +72,17 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, fun process(action: RoomDetailActions) { when (action) { - is RoomDetailActions.SendMessage -> handleSendMessage(action) - is RoomDetailActions.IsDisplayed -> handleIsDisplayed() - is RoomDetailActions.SendMedia -> handleSendMedia(action) - is RoomDetailActions.EventDisplayed -> handleEventDisplayed(action) - is RoomDetailActions.LoadMore -> handleLoadMore(action) - is RoomDetailActions.SendReaction -> handleSendReaction(action) - is RoomDetailActions.AcceptInvite -> handleAcceptInvite() - is RoomDetailActions.RejectInvite -> handleRejectInvite() - is RoomDetailActions.RedactAction -> handleRedactEvent(action) - is RoomDetailActions.UndoReaction -> handleUndoReact(action) + is RoomDetailActions.SendMessage -> handleSendMessage(action) + is RoomDetailActions.SendMedia -> handleSendMedia(action) + is RoomDetailActions.EventDisplayed -> handleEventDisplayed(action) + is RoomDetailActions.LoadMore -> handleLoadMore(action) + is RoomDetailActions.SendReaction -> handleSendReaction(action) + is RoomDetailActions.AcceptInvite -> handleAcceptInvite() + is RoomDetailActions.RejectInvite -> handleRejectInvite() + is RoomDetailActions.RedactAction -> handleRedactEvent(action) + is RoomDetailActions.UndoReaction -> handleUndoReact(action) is RoomDetailActions.UpdateQuickReactAction -> handleUpdateQuickReaction(action) - is RoomDetailActions.ShowEditHistoryAction -> handleShowEditHistoryReaction(action) + is RoomDetailActions.ShowEditHistoryAction -> handleShowEditHistoryReaction(action) } } @@ -107,63 +103,63 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, val slashCommandResult = CommandParser.parseSplashCommand(action.text) when (slashCommandResult) { - is ParsedCommand.ErrorNotACommand -> { + is ParsedCommand.ErrorNotACommand -> { // Send the text message to the room room.sendTextMessage(action.text) _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent)) } - is ParsedCommand.ErrorSyntax -> { + is ParsedCommand.ErrorSyntax -> { _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandError(slashCommandResult.command))) } - is ParsedCommand.ErrorEmptySlashCommand -> { + is ParsedCommand.ErrorEmptySlashCommand -> { _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown("/"))) } is ParsedCommand.ErrorUnknownSlashCommand -> { _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown(slashCommandResult.slashCommand))) } - is ParsedCommand.Invite -> { + is ParsedCommand.Invite -> { handleInviteSlashCommand(slashCommandResult) } - is ParsedCommand.SetUserPowerLevel -> { + is ParsedCommand.SetUserPowerLevel -> { // TODO _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) } - is ParsedCommand.ClearScalarToken -> { + is ParsedCommand.ClearScalarToken -> { // TODO _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) } - is ParsedCommand.SetMarkdown -> { + is ParsedCommand.SetMarkdown -> { // TODO _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) } - is ParsedCommand.UnbanUser -> { + is ParsedCommand.UnbanUser -> { // TODO _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) } - is ParsedCommand.BanUser -> { + is ParsedCommand.BanUser -> { // TODO _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) } - is ParsedCommand.KickUser -> { + is ParsedCommand.KickUser -> { // TODO _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) } - is ParsedCommand.JoinRoom -> { + is ParsedCommand.JoinRoom -> { // TODO _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) } - is ParsedCommand.PartRoom -> { + is ParsedCommand.PartRoom -> { // TODO _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) } - is ParsedCommand.SendEmote -> { + is ParsedCommand.SendEmote -> { room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE) _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled)) } - is ParsedCommand.ChangeTopic -> { + is ParsedCommand.ChangeTopic -> { handleChangeTopicSlashCommand(slashCommandResult) } - is ParsedCommand.ChangeDisplayName -> { + is ParsedCommand.ChangeDisplayName -> { // TODO _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) } @@ -255,10 +251,6 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, displayedEventsObservable.accept(action) } - private fun handleIsDisplayed() { - visibleRoomHolder.post(roomId) - } - private fun handleLoadMore(action: RoomDetailActions.LoadMore) { timeline.paginate(action.direction, PAGINATION_COUNT) } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt index 794c011d..fedf61eb 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt @@ -23,6 +23,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Success +import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.room.model.RoomSummary @@ -61,6 +62,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback { } } + private val roomListParams: RoomListParams by args() private val roomController by inject() private val homeNavigator by inject() private val roomListViewModel: RoomListViewModel by fragmentViewModel() @@ -71,12 +73,20 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback { super.onActivityCreated(savedInstanceState) bindScope(getOrCreateScope(HomeModule.ROOM_LIST_SCOPE)) setupRecyclerView() + setupCreateRoomButton() roomListViewModel.subscribe { renderState(it) } roomListViewModel.openRoomLiveData.observeEvent(this) { homeNavigator.openRoomDetail(it, null) } } + private fun setupCreateRoomButton() { + createRoomButton.setImageResource(R.drawable.ic_add_white) + createRoomButton.setOnClickListener { + vectorBaseActivity.notImplemented() + } + } + private fun setupRecyclerView() { val layoutManager = LinearLayoutManager(context) val stateRestorer = LayoutManagerStateRestorer(layoutManager).register() 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 fd9c19d3..1c015aa0 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 @@ -23,28 +23,19 @@ import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import com.jakewharton.rxrelay2.BehaviorRelay import im.vector.matrix.android.api.session.Session -import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.tag.RoomTag -import im.vector.matrix.rx.rx import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.utils.LiveEvent -import im.vector.riotredesign.features.home.group.ALL_COMMUNITIES_GROUP_ID -import im.vector.riotredesign.features.home.group.SelectedGroupStore -import im.vector.riotredesign.features.home.room.VisibleRoomStore -import io.reactivex.Observable -import io.reactivex.functions.Function3 +import im.vector.riotredesign.features.home.HomeRoomListObservableStore import org.koin.android.ext.android.get -import java.util.concurrent.TimeUnit typealias RoomListFilterName = CharSequence class RoomListViewModel(initialState: RoomListViewState, private val session: Session, - private val selectedGroupHolder: SelectedGroupStore, - private val visibleRoomHolder: VisibleRoomStore, - private val roomSelectionRepository: RoomSelectionRepository, + private val homeRoomListObservableSource: HomeRoomListObservableStore, private val roomSummaryComparator: RoomSummaryComparator) : VectorViewModel(initialState) { @@ -53,11 +44,9 @@ class RoomListViewModel(initialState: RoomListViewState, @JvmStatic override fun create(viewModelContext: ViewModelContext, state: RoomListViewState): RoomListViewModel? { val currentSession = viewModelContext.activity.get() - val roomSelectionRepository = viewModelContext.activity.get() - val selectedGroupHolder = viewModelContext.activity.get() - val visibleRoomHolder = viewModelContext.activity.get() + val homeRoomListObservableSource = viewModelContext.activity.get() val roomSummaryComparator = viewModelContext.activity.get() - return RoomListViewModel(state, currentSession, selectedGroupHolder, visibleRoomHolder, roomSelectionRepository, roomSummaryComparator) + return RoomListViewModel(state, currentSession, homeRoomListObservableSource, roomSummaryComparator) } } @@ -70,7 +59,6 @@ class RoomListViewModel(initialState: RoomListViewState, init { observeRoomSummaries() - observeVisibleRoom() } fun accept(action: RoomListActions) { @@ -83,11 +71,8 @@ class RoomListViewModel(initialState: RoomListViewState, // PRIVATE METHODS ***************************************************************************** - private fun handleSelectRoom(action: RoomListActions.SelectRoom) = withState { state -> - if (state.visibleRoomId != action.roomSummary.roomId) { - roomSelectionRepository.saveLastSelectedRoom(action.roomSummary.roomId) - _openRoomLiveData.postValue(LiveEvent(action.roomSummary.roomId)) - } + private fun handleSelectRoom(action: RoomListActions.SelectRoom) { + _openRoomLiveData.postValue(LiveEvent(action.roomSummary.roomId)) } private fun handleFilterRooms(action: RoomListActions.FilterRooms) { @@ -99,44 +84,10 @@ class RoomListViewModel(initialState: RoomListViewState, this.toggle(action.category) } - private fun observeVisibleRoom() { - visibleRoomHolder.observe() - .doOnNext { - setState { copy(visibleRoomId = it) } - } - .subscribe() - .disposeOnClear() - } private fun observeRoomSummaries() { - Observable.combineLatest, Option, Option, RoomSummaries>( - session.rx().liveRoomSummaries().throttleLast(300, TimeUnit.MILLISECONDS), - selectedGroupHolder.observe(), - roomListFilter.throttleLast(300, TimeUnit.MILLISECONDS), - Function3 { rooms, selectedGroupOption, filterRoomOption -> - val filteredRooms = filterRooms(rooms, filterRoomOption) - val selectedGroup = selectedGroupOption.orNull() - val filteredDirectRooms = filteredRooms - .filter { it.isDirect } - .filter { - if (selectedGroup == null || selectedGroup.groupId == ALL_COMMUNITIES_GROUP_ID) { - true - } else { - it.otherMemberIds - .intersect(selectedGroup.userIds) - .isNotEmpty() - } - } - - val filteredGroupRooms = filteredRooms - .filter { !it.isDirect } - .filter { - selectedGroup?.groupId == ALL_COMMUNITIES_GROUP_ID - || selectedGroup?.roomIds?.contains(it.roomId) ?: true - } - buildRoomSummaries(filteredDirectRooms + filteredGroupRooms) - } - ) + homeRoomListObservableSource.observe() + .map { buildRoomSummaries(it) } .execute { async -> copy( asyncRooms = async @@ -144,17 +95,6 @@ class RoomListViewModel(initialState: RoomListViewState, } } - private fun filterRooms(rooms: List, filterRoomOption: Option): List { - val filterRoom = filterRoomOption.orNull() - return rooms.filter { - if (filterRoom.isNullOrBlank()) { - true - } else { - it.displayName.contains(other = filterRoom, ignoreCase = true) - } - } - } - 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 164b6d3b..10c95efa 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 @@ -25,7 +25,6 @@ import im.vector.riotredesign.R data class RoomListViewState( val asyncRooms: Async = Uninitialized, - val visibleRoomId: String? = null, 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/RoomSummaryController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt index 24641da2..c509af40 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt @@ -37,7 +37,7 @@ class RoomSummaryController(private val stringProvider: StringProvider callback?.onToggleRoomCategory(category) } if (isExpanded) { - buildRoomModels(summaries, viewState.visibleRoomId) + buildRoomModels(summaries) } } } @@ -71,18 +71,16 @@ class RoomSummaryController(private val stringProvider: StringProvider } } - private fun buildRoomModels(summaries: List, selectedRoomId: String?) { + private fun buildRoomModels(summaries: List) { summaries.forEach { roomSummary -> val unreadCount = roomSummary.notificationCount val showHighlighted = roomSummary.highlightCount > 0 - val isSelected = roomSummary.roomId == selectedRoomId roomSummaryItem { id(roomSummary.roomId) roomId(roomSummary.roomId) roomName(roomSummary.displayName) avatarUrl(roomSummary.avatarUrl) - selected(isSelected) showHighlighted(showHighlighted) unreadCount(unreadCount) listener { callback?.onRoomSelected(roomSummary) } 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 e6892610..1cdde14d 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 @@ -16,6 +16,7 @@ package im.vector.riotredesign.features.home.room.list +import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import com.airbnb.epoxy.EpoxyAttribute @@ -23,7 +24,6 @@ import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotredesign.R import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder import im.vector.riotredesign.core.epoxy.VectorEpoxyModel -import im.vector.riotredesign.core.platform.CheckableFrameLayout import im.vector.riotredesign.features.home.AvatarRenderer @@ -33,7 +33,6 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { @EpoxyAttribute lateinit var roomName: CharSequence @EpoxyAttribute lateinit var roomId: String @EpoxyAttribute var avatarUrl: String? = null - @EpoxyAttribute var selected: Boolean = false @EpoxyAttribute var unreadCount: Int = 0 @EpoxyAttribute var showHighlighted: Boolean = false @EpoxyAttribute var listener: (() -> Unit)? = null @@ -42,7 +41,6 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { override fun bind(holder: Holder) { super.bind(holder) holder.unreadCounterBadgeView.render(unreadCount, showHighlighted) - holder.rootView.isChecked = selected holder.rootView.setOnClickListener { listener?.invoke() } holder.titleView.text = roomName AvatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView) @@ -52,7 +50,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { val unreadCounterBadgeView by bind(R.id.roomUnreadCounterBadgeView) val titleView by bind(R.id.roomNameView) val avatarImageView by bind(R.id.roomAvatarImageView) - val rootView by bind(R.id.itemRoomLayout) + val rootView by bind(R.id.itemRoomLayout) } } \ No newline at end of file diff --git a/vector/src/main/res/drawable/bg_room_item.xml b/vector/src/main/res/drawable/bg_group_item.xml similarity index 86% rename from vector/src/main/res/drawable/bg_room_item.xml rename to vector/src/main/res/drawable/bg_group_item.xml index 7a8d5873..221150eb 100644 --- a/vector/src/main/res/drawable/bg_room_item.xml +++ b/vector/src/main/res/drawable/bg_group_item.xml @@ -3,7 +3,7 @@ - + diff --git a/vector/src/main/res/layout/fragment_home_drawer.xml b/vector/src/main/res/layout/fragment_home_drawer.xml index d1bc3801..00371513 100644 --- a/vector/src/main/res/layout/fragment_home_drawer.xml +++ b/vector/src/main/res/layout/fragment_home_drawer.xml @@ -2,9 +2,10 @@ + android:layout_height="match_parent" + android:clickable="true" + android:focusable="true"> - + app:layout_constraintTop_toBottomOf="@+id/homeDrawerUsernameView" /> diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml index c65ae0e0..345f1189 100644 --- a/vector/src/main/res/layout/fragment_room_detail.xml +++ b/vector/src/main/res/layout/fragment_room_detail.xml @@ -6,7 +6,7 @@ android:layout_height="match_parent"> @@ -79,7 +79,7 @@ app:layout_constraintBottom_toTopOf="@+id/composerDivider" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/toolbar" + app:layout_constraintTop_toBottomOf="@id/roomToolbar" tools:listitem="@layout/item_timeline_event_base" /> diff --git a/vector/src/main/res/layout/fragment_room_list.xml b/vector/src/main/res/layout/fragment_room_list.xml index 19cfdd59..249e14ae 100644 --- a/vector/src/main/res/layout/fragment_room_list.xml +++ b/vector/src/main/res/layout/fragment_room_list.xml @@ -12,4 +12,11 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> + + diff --git a/vector/src/main/res/layout/fragment_selected_group.xml b/vector/src/main/res/layout/fragment_selected_group.xml index e2cd454d..d29b1e7d 100644 --- a/vector/src/main/res/layout/fragment_selected_group.xml +++ b/vector/src/main/res/layout/fragment_selected_group.xml @@ -2,11 +2,12 @@ + app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> + + + + + + + + + + + app:layout_constraintTop_toBottomOf="@+id/groupToolbar" /> - + android:foreground="?attr/selectableItemBackground" + android:paddingStart="8dp" + android:paddingLeft="8dp" + android:paddingEnd="16dp" + android:paddingRight="16dp"> - + - + - + - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_room.xml b/vector/src/main/res/layout/item_room.xml index 0bbe7825..807d2363 100644 --- a/vector/src/main/res/layout/item_room.xml +++ b/vector/src/main/res/layout/item_room.xml @@ -1,70 +1,62 @@ - - + + + android:textColor="@color/color_room_title" + android:textSize="14sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/roomUnreadCounterBadgeView" + app:layout_constraintStart_toEndOf="@id/roomAvatarImageView" + app:layout_constraintTop_toTopOf="parent" + tools:text="@tools:sample/full_names" /> - + - - - - - - - \ No newline at end of file + diff --git a/vector/src/main/res/menu/home.xml b/vector/src/main/res/menu/home.xml index 2c8e3c0b..c6df14a9 100644 --- a/vector/src/main/res/menu/home.xml +++ b/vector/src/main/res/menu/home.xml @@ -1,11 +1,6 @@ - - Date: Fri, 17 May 2019 10:19:19 +0200 Subject: [PATCH 03/34] Home: start reworking room list. --- .../vector/riotredesign/core/di/AppModule.kt | 5 ++ .../core/resources/DateProvider.kt | 5 ++ .../riotredesign/features/home/HomeModule.kt | 33 +++++-------- .../home/room/list/RoomListViewModel.kt | 23 +++++++-- .../home/room/list/RoomListViewState.kt | 3 ++ .../home/room/list/RoomSummaryController.kt | 26 +++++++++- .../home/room/list/RoomSummaryItem.kt | 8 +++- vector/src/main/res/layout/item_room.xml | 47 +++++++++++-------- vector/src/main/res/values/colors.xml | 3 ++ 9 files changed, 106 insertions(+), 47 deletions(-) diff --git a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt index 2c654126..8b957d00 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt @@ -23,6 +23,7 @@ import im.vector.riotredesign.core.error.ErrorFormatter import im.vector.riotredesign.core.resources.LocaleProvider import im.vector.riotredesign.core.resources.StringArrayProvider import im.vector.riotredesign.core.resources.StringProvider +import im.vector.riotredesign.features.home.HomeRoomListObservableStore import im.vector.riotredesign.features.home.group.SelectedGroupStore import im.vector.riotredesign.features.home.room.list.RoomSummaryComparator import im.vector.riotredesign.features.notifications.NotificationDrawerManager @@ -52,6 +53,10 @@ class AppModule(private val context: Context) { SelectedGroupStore() } + single { + HomeRoomListObservableStore() + } + single { RoomSummaryComparator() } diff --git a/vector/src/main/java/im/vector/riotredesign/core/resources/DateProvider.kt b/vector/src/main/java/im/vector/riotredesign/core/resources/DateProvider.kt index cc01bdf1..8c485b1b 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/resources/DateProvider.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/resources/DateProvider.kt @@ -29,4 +29,9 @@ object DateProvider { return LocalDateTime.ofInstant(instant, zoneId) } + fun currentLocalDateTime(): LocalDateTime { + val instant = Instant.now() + return LocalDateTime.ofInstant(instant, zoneId) + } + } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt index e5f085aa..ca17d855 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt @@ -25,14 +25,7 @@ import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserControl import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserPresenter import im.vector.riotredesign.features.home.group.GroupSummaryController import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController -import im.vector.riotredesign.features.home.room.detail.timeline.factory.CallItemFactory -import im.vector.riotredesign.features.home.room.detail.timeline.factory.DefaultItemFactory -import im.vector.riotredesign.features.home.room.detail.timeline.factory.MessageItemFactory -import im.vector.riotredesign.features.home.room.detail.timeline.factory.RoomHistoryVisibilityItemFactory -import im.vector.riotredesign.features.home.room.detail.timeline.factory.RoomMemberItemFactory -import im.vector.riotredesign.features.home.room.detail.timeline.factory.RoomNameItemFactory -import im.vector.riotredesign.features.home.room.detail.timeline.factory.RoomTopicItemFactory -import im.vector.riotredesign.features.home.room.detail.timeline.factory.TimelineItemFactory +import im.vector.riotredesign.features.home.room.detail.timeline.factory.* import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotredesign.features.home.room.list.RoomSummaryController @@ -56,36 +49,36 @@ class HomeModule { HomeNavigator() } - scope(HOME_SCOPE) { - HomeRoomListObservableStore() - } - scope(HOME_SCOPE) { HomePermalinkHandler(get()) } // Fragment scopes + factory { + TimelineDateFormatter(get()) + } + factory { (fragment: Fragment) -> val eventHtmlRenderer = EventHtmlRenderer(GlideApp.with(fragment), fragment.requireContext(), get()) - val timelineDateFormatter = TimelineDateFormatter(get()) val timelineMediaSizeProvider = TimelineMediaSizeProvider() val colorProvider = ColorProvider(fragment.requireContext()) + val timelineDateFormatter = get() val messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer) val timelineItemFactory = TimelineItemFactory(messageItemFactory = messageItemFactory, - roomNameItemFactory = RoomNameItemFactory(get()), - roomTopicItemFactory = RoomTopicItemFactory(get()), - roomMemberItemFactory = RoomMemberItemFactory(get()), - roomHistoryVisibilityItemFactory = RoomHistoryVisibilityItemFactory(get()), - callItemFactory = CallItemFactory(get()), - defaultItemFactory = DefaultItemFactory() + roomNameItemFactory = RoomNameItemFactory(get()), + roomTopicItemFactory = RoomTopicItemFactory(get()), + roomMemberItemFactory = RoomMemberItemFactory(get()), + roomHistoryVisibilityItemFactory = RoomHistoryVisibilityItemFactory(get()), + callItemFactory = CallItemFactory(get()), + defaultItemFactory = DefaultItemFactory() ) TimelineEventController(timelineDateFormatter, timelineItemFactory, timelineMediaSizeProvider) } factory { - RoomSummaryController(get()) + RoomSummaryController(get(), get()) } factory { 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 1c015aa0..68b158e4 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,6 +29,7 @@ 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 @@ -50,7 +51,7 @@ class RoomListViewModel(initialState: RoomListViewState, } } - + private val displayMode = initialState.displayMode private val roomListFilter = BehaviorRelay.createDefault>(Option.empty()) private val _openRoomLiveData = MutableLiveData>() @@ -86,15 +87,27 @@ class RoomListViewModel(initialState: RoomListViewState, private fun observeRoomSummaries() { - homeRoomListObservableSource.observe() + homeRoomListObservableSource + .observe() + .flatMapSingle { + Observable.fromIterable(it) + .filter(filterByDisplayMode(displayMode)) + .toList() + } .map { buildRoomSummaries(it) } .execute { async -> - copy( - asyncRooms = async - ) + copy(asyncRooms = 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 10c95efa..dfe0b7b6 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 @@ -24,6 +24,7 @@ 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 isInviteExpanded: Boolean = true, val isFavouriteRoomsExpanded: Boolean = true, @@ -33,6 +34,8 @@ data class RoomListViewState( val isServerNoticeRoomsExpanded: Boolean = true ) : MvRxState { + constructor(args: RoomListParams) : this(displayMode = args.displayMode) + fun isCategoryExpanded(roomCategory: RoomCategory): Boolean { return when (roomCategory) { RoomCategory.INVITE -> isInviteExpanded diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt index c509af40..e25560d7 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt @@ -19,9 +19,13 @@ package im.vector.riotredesign.features.home.room.list import androidx.annotation.StringRes import com.airbnb.epoxy.TypedEpoxyController import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.riotredesign.core.extensions.localDateTime +import im.vector.riotredesign.core.resources.DateProvider import im.vector.riotredesign.core.resources.StringProvider +import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter -class RoomSummaryController(private val stringProvider: StringProvider +class RoomSummaryController(private val stringProvider: StringProvider, + private val timelineDateFormatter: TimelineDateFormatter ) : TypedEpoxyController() { var callback: Callback? = null @@ -76,9 +80,29 @@ class RoomSummaryController(private val stringProvider: StringProvider val unreadCount = roomSummary.notificationCount val showHighlighted = roomSummary.highlightCount > 0 + var lastMessageFormatted: CharSequence = "" + var lastMessageTime: CharSequence = "" + val lastMessage = roomSummary.lastMessage + if (lastMessage != null) { + val date = lastMessage.localDateTime() + val currentData = DateProvider.currentLocalDateTime() + val isSameDay = date.toLocalDate() == currentData.toLocalDate() + //TODO: get formatted + lastMessageFormatted = lastMessage.content?.toString() ?: "" + lastMessageTime = if (isSameDay) { + timelineDateFormatter.formatMessageHour(date) + } else { + //TODO: change this + timelineDateFormatter.formatMessageDay(date) + } + + + } roomSummaryItem { id(roomSummary.roomId) roomId(roomSummary.roomId) + lastEventTime(lastMessageTime) + lastFormattedEvent(lastMessageFormatted) roomName(roomSummary.displayName) avatarUrl(roomSummary.avatarUrl) showHighlighted(showHighlighted) 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 1cdde14d..0177757b 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 @@ -32,6 +32,8 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { @EpoxyAttribute lateinit var roomName: CharSequence @EpoxyAttribute lateinit var roomId: String + @EpoxyAttribute lateinit var lastFormattedEvent: CharSequence + @EpoxyAttribute lateinit var lastEventTime: CharSequence @EpoxyAttribute var avatarUrl: String? = null @EpoxyAttribute var unreadCount: Int = 0 @EpoxyAttribute var showHighlighted: Boolean = false @@ -40,15 +42,17 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { override fun bind(holder: Holder) { super.bind(holder) - holder.unreadCounterBadgeView.render(unreadCount, showHighlighted) holder.rootView.setOnClickListener { listener?.invoke() } holder.titleView.text = roomName + holder.lastEventTimeView.text = lastEventTime + holder.lastEventView.text = lastFormattedEvent AvatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView) } class Holder : VectorEpoxyHolder() { - val unreadCounterBadgeView by bind(R.id.roomUnreadCounterBadgeView) val titleView by bind(R.id.roomNameView) + val lastEventView by bind(R.id.roomLastEventView) + val lastEventTimeView by bind(R.id.roomLastEventTimeView) val avatarImageView by bind(R.id.roomAvatarImageView) val rootView by bind(R.id.itemRoomLayout) } diff --git a/vector/src/main/res/layout/item_room.xml b/vector/src/main/res/layout/item_room.xml index 807d2363..e53dc949 100644 --- a/vector/src/main/res/layout/item_room.xml +++ b/vector/src/main/res/layout/item_room.xml @@ -9,7 +9,8 @@ android:background="?attr/selectableItemBackground" android:clickable="true" android:focusable="true" - android:minHeight="48dp" + android:paddingBottom="16dp" + android:paddingTop="16dp" android:paddingStart="8dp" android:paddingLeft="8dp" android:paddingEnd="16dp" @@ -17,8 +18,8 @@ - + + + app:layout_constraintStart_toEndOf="@id/messageMemberNameView" + tools:text="@tools:sample/date/hhmm" /> + diff --git a/vector/src/main/res/values/colors.xml b/vector/src/main/res/values/colors.xml index 75bad082..a863247f 100644 --- a/vector/src/main/res/values/colors.xml +++ b/vector/src/main/res/values/colors.xml @@ -16,6 +16,9 @@ #ebedf8 #a5a5a5 #61708B + #de000000 + #61000000 + #5d000000 #000000 #de000000 From eb2344a43fe1c30fe91c70f9b63a6bac2e4cb1d7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 17 May 2019 17:07:02 +0200 Subject: [PATCH 04/34] Home: continue room list rework. --- .../riotredesign/core/platform/StateView.kt | 12 ++-- .../features/home/group/GroupListViewModel.kt | 16 +++-- .../home/room/list/RoomListAnimator.kt | 32 ++++++++++ .../home/room/list/RoomListFragment.kt | 27 ++++++-- .../home/room/list/RoomListViewModel.kt | 8 ++- .../home/room/list/RoomListViewState.kt | 5 +- .../home/room/list/RoomSummaryController.kt | 2 +- .../home/room/list/RoomSummaryItem.kt | 2 + vector/src/main/res/layout/item_room.xml | 63 +++++++++++++------ .../main/res/layout/item_room_category.xml | 45 +++++-------- vector/src/main/res/layout/view_state.xml | 31 +++++---- vector/src/main/res/values/strings_riotX.xml | 4 ++ 12 files changed, 165 insertions(+), 82 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListAnimator.kt diff --git a/vector/src/main/java/im/vector/riotredesign/core/platform/StateView.kt b/vector/src/main/java/im/vector/riotredesign/core/platform/StateView.kt index e75fb26c..a6fa7457 100755 --- a/vector/src/main/java/im/vector/riotredesign/core/platform/StateView.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/platform/StateView.kt @@ -17,9 +17,9 @@ package im.vector.riotredesign.core.platform import android.content.Context +import android.graphics.drawable.Drawable import android.util.AttributeSet import android.view.View -import android.view.ViewGroup import android.widget.FrameLayout import im.vector.riotredesign.R import kotlinx.android.synthetic.main.view_state.view.* @@ -30,7 +30,7 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? sealed class State { object Content : State() object Loading : State() - data class Empty(val message: CharSequence? = null) : State() + data class Empty(val title: CharSequence? = null, val image: Drawable? = null, val message: CharSequence? = null) : State() data class Error(val message: CharSequence? = null) : State() } @@ -52,7 +52,7 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? init { View.inflate(context, R.layout.view_state, this) - layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) + layoutParams = LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT) errorRetryView.setOnClickListener { eventCallback?.onRetryClicked() } @@ -74,16 +74,18 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? emptyView.visibility = View.INVISIBLE contentView?.visibility = View.INVISIBLE } - is StateView.State.Empty -> { + is StateView.State.Empty -> { progressBar.visibility = View.INVISIBLE errorView.visibility = View.INVISIBLE emptyView.visibility = View.VISIBLE + emptyImageView.setImageDrawable(newState.image) emptyMessageView.text = newState.message + emptyTitleView.text = newState.title if (contentView != null) { contentView!!.visibility = View.INVISIBLE } } - is StateView.State.Error -> { + is StateView.State.Error -> { progressBar.visibility = View.INVISIBLE errorView.visibility = View.VISIBLE emptyView.visibility = View.INVISIBLE diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt index f8141e14..47534b8b 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt @@ -85,12 +85,16 @@ class GroupListViewModel(initialState: GroupListViewState, session .rx().liveGroupSummaries() .map { - val myUser = session.getUser(session.sessionParams.credentials.userId) - val allCommunityGroup = GroupSummary( - groupId = ALL_COMMUNITIES_GROUP_ID, - displayName = "All Communities", - avatarUrl = myUser?.avatarUrl ?: "") - listOf(allCommunityGroup) + it + if (it.isEmpty()) { + it + } else { + val myUser = session.getUser(session.sessionParams.credentials.userId) + val allCommunityGroup = GroupSummary( + groupId = ALL_COMMUNITIES_GROUP_ID, + displayName = "All Communities", + avatarUrl = myUser?.avatarUrl ?: "") + listOf(allCommunityGroup) + it + } } .execute { async -> val newSelectedGroup = selectedGroup ?: async()?.firstOrNull() diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListAnimator.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListAnimator.kt new file mode 100644 index 00000000..37dbdf9b --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListAnimator.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 androidx.recyclerview.widget.DefaultItemAnimator + +private const val ANIM_DURATION_IN_MILLIS = 100L + +class RoomListAnimator : DefaultItemAnimator() { + + init { + addDuration = ANIM_DURATION_IN_MILLIS + removeDuration = ANIM_DURATION_IN_MILLIS + moveDuration = 0 + changeDuration = 0 + } + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt index fedf61eb..38463337 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt @@ -91,6 +91,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback { val layoutManager = LinearLayoutManager(context) val stateRestorer = LayoutManagerStateRestorer(layoutManager).register() epoxyRecyclerView.layoutManager = layoutManager + epoxyRecyclerView.itemAnimator = RoomListAnimator() roomController.callback = this roomController.addModelBuildListener { it.dispatchTo(stateRestorer) } stateView.contentView = epoxyRecyclerView @@ -98,22 +99,40 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback { } private fun renderState(state: RoomListViewState) { - when (state.asyncRooms) { + when (state.asyncFilteredRooms) { is Incomplete -> renderLoading() is Success -> renderSuccess(state) - is Fail -> renderFailure(state.asyncRooms.error) + is Fail -> renderFailure(state.asyncFilteredRooms.error) } } private fun renderSuccess(state: RoomListViewState) { - if (state.asyncRooms().isNullOrEmpty()) { - stateView.state = StateView.State.Empty(getString(R.string.room_list_empty)) + val allRooms = state.asyncRooms() + val filteredRooms = state.asyncFilteredRooms() + if (filteredRooms.isNullOrEmpty()) { + renderEmptyState(allRooms) } else { stateView.state = StateView.State.Content } roomController.setData(state) } + private fun renderEmptyState(allRooms: List?) { + val hasNoRoom = allRooms.isNullOrEmpty() + val emptyState = when (roomListParams.displayMode) { + DisplayMode.HOME -> { + if (hasNoRoom) { + StateView.State.Empty(getString(R.string.room_list_catchup_welcome_title), null, getString(R.string.room_list_catchup_welcome_body)) + } else { + StateView.State.Empty(getString(R.string.room_list_catchup_empty_title), null, getString(R.string.room_list_catchup_empty_body)) + } + } + DisplayMode.PEOPLE -> StateView.State.Empty() + DisplayMode.ROOMS -> StateView.State.Empty() + } + stateView.state = emptyState + } + private fun renderLoading() { stateView.state = StateView.State.Loading } 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 68b158e4..585d8572 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 @@ -87,6 +87,12 @@ class RoomListViewModel(initialState: RoomListViewState, private fun observeRoomSummaries() { + homeRoomListObservableSource + .observe() + .execute { asyncRooms -> + copy(asyncRooms = asyncRooms) + } + homeRoomListObservableSource .observe() .flatMapSingle { @@ -96,7 +102,7 @@ class RoomListViewModel(initialState: RoomListViewState, } .map { buildRoomSummaries(it) } .execute { async -> - copy(asyncRooms = async) + copy(asyncFilteredRooms = async) } } 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 dfe0b7b6..476dfac3 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,15 +17,12 @@ 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 isInviteExpanded: Boolean = true, val isFavouriteRoomsExpanded: Boolean = true, val isDirectRoomsExpanded: Boolean = true, @@ -71,5 +68,5 @@ enum class RoomCategory(@StringRes val titleRes: Int) { } fun RoomSummaries?.isNullOrEmpty(): Boolean { - return this == null || isEmpty() + return this == null || this.values.flatten().isEmpty() } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt index e25560d7..633deb90 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt @@ -31,7 +31,7 @@ class RoomSummaryController(private val stringProvider: StringProvider, var callback: Callback? = null override fun buildModels(viewState: RoomListViewState) { - val roomSummaries = viewState.asyncRooms() + val roomSummaries = viewState.asyncFilteredRooms() roomSummaries?.forEach { (category, summaries) -> if (summaries.isEmpty()) { return@forEach 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 0177757b..f9ada8a5 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,11 +46,13 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { holder.titleView.text = roomName holder.lastEventTimeView.text = lastEventTime holder.lastEventView.text = lastFormattedEvent + holder.unreadCounterBadgeView.render(unreadCount, showHighlighted) AvatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView) } class Holder : VectorEpoxyHolder() { val titleView by bind(R.id.roomNameView) + val unreadCounterBadgeView by bind(R.id.roomUnreadCounterBadgeView) val lastEventView by bind(R.id.roomLastEventView) val lastEventTimeView by bind(R.id.roomLastEventTimeView) val avatarImageView by bind(R.id.roomAvatarImageView) diff --git a/vector/src/main/res/layout/item_room.xml b/vector/src/main/res/layout/item_room.xml index e53dc949..677d2742 100644 --- a/vector/src/main/res/layout/item_room.xml +++ b/vector/src/main/res/layout/item_room.xml @@ -9,12 +9,12 @@ android:background="?attr/selectableItemBackground" android:clickable="true" android:focusable="true" - android:paddingBottom="16dp" - android:paddingTop="16dp" android:paddingStart="8dp" android:paddingLeft="8dp" + android:paddingTop="8dp" android:paddingEnd="16dp" - android:paddingRight="16dp"> + android:paddingRight="16dp" + android:paddingBottom="8dp"> - + android:layout_marginRight="8dp" + android:gravity="center" + android:minWidth="16dp" + android:minHeight="16dp" + android:paddingLeft="4dp" + android:paddingRight="4dp" + android:textColor="@android:color/white" + android:textSize="10sp" + app:layout_constraintEnd_toStartOf="@+id/roomLastEventTimeView" + app:layout_constraintStart_toEndOf="@+id/roomNameView" + app:layout_constraintTop_toTopOf="@+id/roomAvatarImageView" + tools:background="@drawable/bg_unread_highlight" + tools:text="4" /> + + + diff --git a/vector/src/main/res/layout/item_room_category.xml b/vector/src/main/res/layout/item_room_category.xml index ecb6141f..5fda2366 100644 --- a/vector/src/main/res/layout/item_room_category.xml +++ b/vector/src/main/res/layout/item_room_category.xml @@ -1,6 +1,5 @@ - + tools:text="@string/room_recents_favourites" /> - - + tools:background="@drawable/bg_unread_highlight" + tools:text="24" /> - \ No newline at end of file + \ No newline at end of file diff --git a/vector/src/main/res/layout/view_state.xml b/vector/src/main/res/layout/view_state.xml index 2441893b..91803d86 100644 --- a/vector/src/main/res/layout/view_state.xml +++ b/vector/src/main/res/layout/view_state.xml @@ -2,8 +2,7 @@ @@ -11,13 +10,14 @@ android:id="@+id/progressBar" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" /> + android:layout_gravity="center" /> @@ -48,9 +48,26 @@ android:id="@+id/emptyView" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_gravity="center" android:orientation="vertical" android:padding="8dp"> + + + + - - - \ No newline at end of file diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 5a37c393..a115f102 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -7,6 +7,10 @@ "Sent you an invitation" Invited by %s + You’re all caught up! + You have no more unread messages + Welcome home! + Catch up on unread messages here Reactions Agree From 1691537a1e3b56e98d1be2440724e5f870bf34d3 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 20 May 2019 14:46:14 +0200 Subject: [PATCH 05/34] Room list : add chronological and alphabetical comparators --- build.gradle | 2 +- .../vector/riotredesign/core/di/AppModule.kt | 9 +++- .../room/list/AlphabeticalRoomComparator.kt | 32 +++++++++++++ .../room/list/ChronologicalRoomComparator.kt | 47 +++++++++++++++++++ .../home/room/list/RoomListViewModel.kt | 26 ++++++---- 5 files changed, 104 insertions(+), 12 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/list/AlphabeticalRoomComparator.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/list/ChronologicalRoomComparator.kt diff --git a/build.gradle b/build.gradle index c54c0f59..ca7654c4 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:3.3.2' + classpath 'com.android.tools.build:gradle:3.4.1' classpath 'com.google.gms:google-services:4.2.0' classpath "com.airbnb.okreplay:gradle-plugin:1.4.0" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" diff --git a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt index 8b957d00..c0a4f9cf 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt @@ -25,7 +25,8 @@ import im.vector.riotredesign.core.resources.StringArrayProvider import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.features.home.HomeRoomListObservableStore import im.vector.riotredesign.features.home.group.SelectedGroupStore -import im.vector.riotredesign.features.home.room.list.RoomSummaryComparator +import im.vector.riotredesign.features.home.room.list.AlphabeticalRoomComparator +import im.vector.riotredesign.features.home.room.list.ChronologicalRoomComparator import im.vector.riotredesign.features.notifications.NotificationDrawerManager import org.koin.dsl.module.module @@ -58,7 +59,11 @@ class AppModule(private val context: Context) { } single { - RoomSummaryComparator() + ChronologicalRoomComparator() + } + + single { + AlphabeticalRoomComparator() } single { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/AlphabeticalRoomComparator.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/AlphabeticalRoomComparator.kt new file mode 100644 index 00000000..c1031c45 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/AlphabeticalRoomComparator.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.RoomSummary + +class AlphabeticalRoomComparator + : Comparator { + + override fun compare(leftRoomSummary: RoomSummary?, rightRoomSummary: RoomSummary?): Int { + return when { + rightRoomSummary?.displayName == null -> -1 + leftRoomSummary?.displayName == null -> 1 + else -> leftRoomSummary.displayName.compareTo(rightRoomSummary.displayName) + } + + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/ChronologicalRoomComparator.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/ChronologicalRoomComparator.kt new file mode 100644 index 00000000..2ffe559d --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/ChronologicalRoomComparator.kt @@ -0,0 +1,47 @@ +/* + * 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.RoomSummary + +class ChronologicalRoomComparator : Comparator { + + override fun compare(leftRoomSummary: RoomSummary?, rightRoomSummary: RoomSummary?): Int { + var rightTimestamp = 0L + var leftTimestamp = 0L + if (null != leftRoomSummary) { + leftTimestamp = leftRoomSummary.lastMessage?.originServerTs ?: 0 + } + if (null != rightRoomSummary) { + rightTimestamp = rightRoomSummary.lastMessage?.originServerTs ?: 0 + } + return if (rightRoomSummary?.lastMessage == null) { + -1 + } else if (leftRoomSummary?.lastMessage == null) { + 1 + } else { + val deltaTimestamp = rightTimestamp - leftTimestamp + if (deltaTimestamp > 0) { + 1 + } else if (deltaTimestamp < 0) { + -1 + } else { + 0 + } + } + } +} \ 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 585d8572..6c640d30 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 @@ -37,7 +37,8 @@ typealias RoomListFilterName = CharSequence class RoomListViewModel(initialState: RoomListViewState, private val session: Session, private val homeRoomListObservableSource: HomeRoomListObservableStore, - private val roomSummaryComparator: RoomSummaryComparator) + private val alphabeticalRoomComparator: AlphabeticalRoomComparator, + private val chronologicalRoomComparator: ChronologicalRoomComparator) : VectorViewModel(initialState) { companion object : MvRxViewModelFactory { @@ -46,8 +47,9 @@ class RoomListViewModel(initialState: RoomListViewState, override fun create(viewModelContext: ViewModelContext, state: RoomListViewState): RoomListViewModel? { val currentSession = viewModelContext.activity.get() val homeRoomListObservableSource = viewModelContext.activity.get() - val roomSummaryComparator = viewModelContext.activity.get() - return RoomListViewModel(state, currentSession, homeRoomListObservableSource, roomSummaryComparator) + val chronologicalRoomComparator = viewModelContext.activity.get() + val alphabeticalRoomComparator = viewModelContext.activity.get() + return RoomListViewModel(state, currentSession, homeRoomListObservableSource, alphabeticalRoomComparator, chronologicalRoomComparator) } } @@ -135,13 +137,19 @@ class RoomListViewModel(initialState: RoomListViewState, } } + val roomComparator = when (displayMode) { + RoomListFragment.DisplayMode.HOME -> chronologicalRoomComparator + RoomListFragment.DisplayMode.PEOPLE -> chronologicalRoomComparator + RoomListFragment.DisplayMode.ROOMS -> alphabeticalRoomComparator + } + return RoomSummaries().apply { - put(RoomCategory.INVITE, invites.sortedWith(roomSummaryComparator)) - put(RoomCategory.FAVOURITE, favourites.sortedWith(roomSummaryComparator)) - put(RoomCategory.DIRECT, directChats.sortedWith(roomSummaryComparator)) - put(RoomCategory.GROUP, groupRooms.sortedWith(roomSummaryComparator)) - put(RoomCategory.LOW_PRIORITY, lowPriorities.sortedWith(roomSummaryComparator)) - put(RoomCategory.SERVER_NOTICE, serverNotices.sortedWith(roomSummaryComparator)) + put(RoomCategory.INVITE, invites.sortedWith(roomComparator)) + put(RoomCategory.FAVOURITE, favourites.sortedWith(roomComparator)) + put(RoomCategory.DIRECT, directChats.sortedWith(roomComparator)) + put(RoomCategory.GROUP, groupRooms.sortedWith(roomComparator)) + put(RoomCategory.LOW_PRIORITY, lowPriorities.sortedWith(roomComparator)) + put(RoomCategory.SERVER_NOTICE, serverNotices.sortedWith(roomComparator)) } } From 9f9f4c0755c75bcd597445302bbd9b5a65e705a1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 20 May 2019 17:13:12 +0200 Subject: [PATCH 06/34] Home: change some UI in room list --- ...GroupFragment.kt => HomeDetailFragment.kt} | 78 +++++++++++-------- .../features/home/HomeNavigator.kt | 8 +- .../home/HomeRoomListObservableStore.kt | 15 +++- .../home/room/detail/RoomDetailFragment.kt | 6 +- .../home/room/detail/RoomDetailViewModel.kt | 2 +- .../home/room/detail/RoomDetailViewState.kt | 2 +- .../home/room/list/RoomCategoryItem.kt | 2 +- .../room/list/RoomListDisplayModeFilter.kt | 32 ++++++++ .../home/room/list/RoomListViewModel.kt | 18 +---- .../home/room/list/RoomListViewState.kt | 4 + .../home/room/list/RoomSummaryItem.kt | 2 +- .../home/room/list/UnreadCounterBadgeView.kt | 16 ++-- ...ted_group.xml => fragment_home_detail.xml} | 1 + vector/src/main/res/layout/item_room.xml | 40 ++++++---- .../main/res/layout/vector_unread_layout.xml | 26 +++++++ 15 files changed, 166 insertions(+), 86 deletions(-) rename vector/src/main/java/im/vector/riotredesign/features/home/{group/SelectedGroupFragment.kt => HomeDetailFragment.kt} (54%) create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListDisplayModeFilter.kt rename vector/src/main/res/layout/{fragment_selected_group.xml => fragment_home_detail.xml} (98%) create mode 100644 vector/src/main/res/layout/vector_unread_layout.xml 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 From b9d76f50476543af5b20e4a2e624a30ee2427076 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 21 May 2019 13:20:11 +0200 Subject: [PATCH 07/34] Room list & event : decouple notice events formatting to be used within room controller --- .../android/api/session/events/model/Event.kt | 14 +- .../riotredesign/features/home/HomeModule.kt | 29 +-- .../features/home/group/GroupListFragment.kt | 1 - .../timeline/factory/CallItemFactory.kt | 59 ------ ...picItemFactory.kt => NoticeItemFactory.kt} | 24 +-- .../RoomHistoryVisibilityItemFactory.kt | 55 ------ .../timeline/factory/RoomMemberItemFactory.kt | 135 ------------- .../timeline/factory/RoomNameItemFactory.kt | 45 ----- .../timeline/factory/TimelineItemFactory.kt | 23 +-- .../timeline/format/NoticeEventFormatter.kt | 185 ++++++++++++++++++ .../timeline/helper/RoomMemberEventHelper.kt | 16 +- .../helper/TimelineDisplayableEvents.kt | 22 +++ .../home/room/list/RoomListFragment.kt | 1 - .../home/room/list/RoomSummaryController.kt | 2 + 14 files changed, 257 insertions(+), 354 deletions(-) delete mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/CallItemFactory.kt rename vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/{RoomTopicItemFactory.kt => NoticeItemFactory.kt} (55%) delete mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomHistoryVisibilityItemFactory.kt delete mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomMemberItemFactory.kt delete mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomNameItemFactory.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/format/NoticeEventFormatter.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt index 7c6ca39b..e173c0f2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt @@ -20,6 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import com.squareup.moshi.Types import im.vector.matrix.android.internal.di.MoshiProvider +import timber.log.Timber import java.lang.reflect.ParameterizedType typealias Content = Map @@ -27,11 +28,20 @@ typealias Content = Map /** * This methods is a facility method to map a json content to a model. */ -inline fun Content?.toModel(): T? { +inline fun Content?.toModel(catchError: Boolean = true): T? { return this?.let { val moshi = MoshiProvider.providesMoshi() val moshiAdapter = moshi.adapter(T::class.java) - return moshiAdapter.fromJsonValue(it) + return try { + moshiAdapter.fromJsonValue(it) + } catch (e: Exception) { + if (catchError) { + Timber.e(e, "To model failed : $e") + null + } else { + throw e + } + } } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt index ca17d855..c5779816 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt @@ -25,11 +25,16 @@ import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserControl import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserPresenter import im.vector.riotredesign.features.home.group.GroupSummaryController import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController -import im.vector.riotredesign.features.home.room.detail.timeline.factory.* +import im.vector.riotredesign.features.home.room.detail.timeline.factory.DefaultItemFactory +import im.vector.riotredesign.features.home.room.detail.timeline.factory.MessageItemFactory +import im.vector.riotredesign.features.home.room.detail.timeline.factory.NoticeItemFactory +import im.vector.riotredesign.features.home.room.detail.timeline.factory.TimelineItemFactory +import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotredesign.features.home.room.list.RoomSummaryController import im.vector.riotredesign.features.html.EventHtmlRenderer +import org.koin.core.parameter.parametersOf import org.koin.dsl.module.module class HomeModule { @@ -37,8 +42,6 @@ class HomeModule { companion object { const val HOME_SCOPE = "HOME_SCOPE" const val ROOM_DETAIL_SCOPE = "ROOM_DETAIL_SCOPE" - const val ROOM_LIST_SCOPE = "ROOM_LIST_SCOPE" - const val GROUP_LIST_SCOPE = "GROUP_LIST_SCOPE" } val definition = module { @@ -59,26 +62,26 @@ class HomeModule { TimelineDateFormatter(get()) } + factory { + NoticeEventFormatter(get()) + } + factory { (fragment: Fragment) -> val eventHtmlRenderer = EventHtmlRenderer(GlideApp.with(fragment), fragment.requireContext(), get()) + val noticeEventFormatter = get(parameters = { parametersOf(fragment) }) val timelineMediaSizeProvider = TimelineMediaSizeProvider() - val colorProvider = ColorProvider(fragment.requireContext()) + val colorProvider = get() val timelineDateFormatter = get() - val messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer) - val timelineItemFactory = TimelineItemFactory(messageItemFactory = messageItemFactory, - roomNameItemFactory = RoomNameItemFactory(get()), - roomTopicItemFactory = RoomTopicItemFactory(get()), - roomMemberItemFactory = RoomMemberItemFactory(get()), - roomHistoryVisibilityItemFactory = RoomHistoryVisibilityItemFactory(get()), - callItemFactory = CallItemFactory(get()), - defaultItemFactory = DefaultItemFactory() + val timelineItemFactory = TimelineItemFactory(messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer), + noticeItemFactory = NoticeItemFactory(noticeEventFormatter), + defaultItemFactory = DefaultItemFactory() ) TimelineEventController(timelineDateFormatter, timelineItemFactory, timelineMediaSizeProvider) } factory { - RoomSummaryController(get(), get()) + RoomSummaryController(get(), get(), get()) } factory { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListFragment.kt index 884b7a61..8970935e 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListFragment.kt @@ -48,7 +48,6 @@ class GroupListFragment : VectorBaseFragment(), GroupSummaryController.Callback override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - bindScope(getOrCreateScope(HomeModule.GROUP_LIST_SCOPE)) groupController.callback = this stateView.contentView = epoxyRecyclerView epoxyRecyclerView.setController(groupController) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/CallItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/CallItemFactory.kt deleted file mode 100644 index 42f6ba0e..00000000 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/CallItemFactory.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.detail.timeline.factory - -import im.vector.matrix.android.api.session.events.model.Event -import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.RoomMember -import im.vector.matrix.android.api.session.room.model.call.CallInviteContent -import im.vector.matrix.android.api.session.room.timeline.TimelineEvent -import im.vector.riotredesign.R -import im.vector.riotredesign.core.resources.StringProvider -import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem -import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_ - -class CallItemFactory(private val stringProvider: StringProvider) { - - fun create(event: TimelineEvent): NoticeItem? { - val text = buildNoticeText(event.root, event.senderName) ?: return null - return NoticeItem_() - .noticeText(text) - .avatarUrl(event.senderAvatar) - .memberName(event.senderName) - } - - private fun buildNoticeText(event: Event, senderName: String?): CharSequence? { - return when { - EventType.CALL_INVITE == event.type -> { - val content = event.content.toModel() ?: return null - val isVideoCall = content.offer.sdp == CallInviteContent.Offer.SDP_VIDEO - return if (isVideoCall) { - stringProvider.getString(R.string.notice_placed_video_call, senderName) - } else { - stringProvider.getString(R.string.notice_placed_voice_call, senderName) - } - } - EventType.CALL_ANSWER == event.type -> stringProvider.getString(R.string.notice_answered_call, senderName) - EventType.CALL_HANGUP == event.type -> stringProvider.getString(R.string.notice_ended_call, senderName) - else -> null - } - - } - - -} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomTopicItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/NoticeItemFactory.kt similarity index 55% rename from vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomTopicItemFactory.kt rename to vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/NoticeItemFactory.kt index 34e55897..fc756c9c 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomTopicItemFactory.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/NoticeItemFactory.kt @@ -16,28 +16,24 @@ package im.vector.riotredesign.features.home.room.detail.timeline.factory -import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.RoomTopicContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent -import im.vector.riotredesign.R -import im.vector.riotredesign.core.resources.StringProvider +import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter +import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderAvatar +import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_ -class RoomTopicItemFactory(private val stringProvider: StringProvider) { +class NoticeItemFactory(private val eventFormatter: NoticeEventFormatter) { fun create(event: TimelineEvent): NoticeItem? { + val formattedText = eventFormatter.format(event) ?: return null + val senderName = event.senderName() + val senderAvatar = event.senderAvatar() - val content: RoomTopicContent = event.root.content.toModel() ?: return null - val text = if (content.topic.isNullOrEmpty()) { - stringProvider.getString(R.string.notice_room_topic_removed, event.senderName) - } else { - stringProvider.getString(R.string.notice_room_topic_changed, event.senderName, content.topic) - } return NoticeItem_() - .noticeText(text) - .avatarUrl(event.senderAvatar) - .memberName(event.senderName) + .noticeText(formattedText) + .avatarUrl(senderAvatar) + .memberName(senderName) } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomHistoryVisibilityItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomHistoryVisibilityItemFactory.kt deleted file mode 100644 index 3a3a91f1..00000000 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomHistoryVisibilityItemFactory.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.detail.timeline.factory - -import im.vector.matrix.android.api.session.events.model.Event -import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility -import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent -import im.vector.matrix.android.api.session.room.model.RoomMember -import im.vector.matrix.android.api.session.room.timeline.TimelineEvent -import im.vector.riotredesign.R -import im.vector.riotredesign.core.resources.StringProvider -import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem -import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_ - - -class RoomHistoryVisibilityItemFactory(private val stringProvider: StringProvider) { - - fun create(event: TimelineEvent): NoticeItem? { - val noticeText = buildNoticeText(event.root, event.senderName) ?: return null - return NoticeItem_() - .noticeText(noticeText) - .avatarUrl(event.senderAvatar) - .memberName(event.senderName) - } - - private fun buildNoticeText(event: Event, senderName: String?): CharSequence? { - val content = event.content.toModel() ?: return null - val formattedVisibility = when (content.historyVisibility) { - RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared) - RoomHistoryVisibility.INVITED -> stringProvider.getString(R.string.notice_room_visibility_invited) - RoomHistoryVisibility.JOINED -> stringProvider.getString(R.string.notice_room_visibility_joined) - RoomHistoryVisibility.WORLD_READABLE -> stringProvider.getString(R.string.notice_room_visibility_world_readable) - } - return stringProvider.getString(R.string.notice_made_future_room_visibility, senderName, formattedVisibility) - } - - -} - - diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomMemberItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomMemberItemFactory.kt deleted file mode 100644 index 77e5920c..00000000 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomMemberItemFactory.kt +++ /dev/null @@ -1,135 +0,0 @@ -/* - * 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.detail.timeline.factory - -import android.text.TextUtils -import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.Membership -import im.vector.matrix.android.api.session.room.model.RoomMember -import im.vector.matrix.android.api.session.room.timeline.TimelineEvent -import im.vector.riotredesign.R -import im.vector.riotredesign.core.resources.StringProvider -import im.vector.riotredesign.features.home.room.detail.timeline.helper.RoomMemberEventHelper -import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem -import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_ - - -//TODO : complete with call membership events¬ -class RoomMemberItemFactory(private val stringProvider: StringProvider) { - - fun create(event: TimelineEvent): NoticeItem? { - val eventContent: RoomMember? = event.root.content.toModel() - val prevEventContent: RoomMember? = event.root.prevContent.toModel() - val noticeText = buildRoomMemberNotice(event, eventContent, prevEventContent) ?: return null - val senderAvatar = RoomMemberEventHelper.senderAvatar(eventContent, prevEventContent, event) - val senderName = RoomMemberEventHelper.senderName(eventContent, prevEventContent, event) - - return NoticeItem_() - .userId(event.root.sender ?: "") - .noticeText(noticeText) - .avatarUrl(senderAvatar) - .memberName(senderName) - } - - private fun buildRoomMemberNotice(event: TimelineEvent, eventContent: RoomMember?, prevEventContent: RoomMember?): String? { - val isMembershipEvent = prevEventContent?.membership != eventContent?.membership - return if (isMembershipEvent) { - buildMembershipNotice(event, eventContent, prevEventContent) - } else { - buildProfileNotice(event, eventContent, prevEventContent) - } - } - - private fun buildProfileNotice(event: TimelineEvent, eventContent: RoomMember?, prevEventContent: RoomMember?): String? { - val displayText = StringBuilder() - // Check display name has been changed - if (!TextUtils.equals(eventContent?.displayName, prevEventContent?.displayName)) { - val displayNameText = when { - prevEventContent?.displayName.isNullOrEmpty() -> - stringProvider.getString(R.string.notice_display_name_set, event.root.sender, eventContent?.displayName) - eventContent?.displayName.isNullOrEmpty() -> - stringProvider.getString(R.string.notice_display_name_removed, event.root.sender, prevEventContent?.displayName) - else -> - stringProvider.getString(R.string.notice_display_name_changed_from, - event.root.sender, prevEventContent?.displayName, eventContent?.displayName) - } - displayText.append(displayNameText) - } - // Check whether the avatar has been changed - if (!TextUtils.equals(eventContent?.avatarUrl, prevEventContent?.avatarUrl)) { - val displayAvatarText = if (displayText.isNotEmpty()) { - displayText.append(" ") - stringProvider.getString(R.string.notice_avatar_changed_too) - } else { - stringProvider.getString(R.string.notice_avatar_url_changed, event.senderName) - } - displayText.append(displayAvatarText) - } - return displayText.toString() - } - - private fun buildMembershipNotice(event: TimelineEvent, eventContent: RoomMember?, prevEventContent: RoomMember?): String? { - val senderDisplayName = event.senderName ?: event.root.sender - val targetDisplayName = eventContent?.displayName ?: event.root.sender - return when { - Membership.INVITE == eventContent?.membership -> { - // TODO get userId - val selfUserId = "" - when { - eventContent.thirdPartyInvite != null -> - stringProvider.getString(R.string.notice_room_third_party_registered_invite, - targetDisplayName, eventContent.thirdPartyInvite?.displayName) - TextUtils.equals(event.root.stateKey, selfUserId) -> - stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName) - event.root.stateKey.isNullOrEmpty() -> - stringProvider.getString(R.string.notice_room_invite_no_invitee, senderDisplayName) - else -> - stringProvider.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName) - } - } - Membership.JOIN == eventContent?.membership -> - stringProvider.getString(R.string.notice_room_join, senderDisplayName) - Membership.LEAVE == eventContent?.membership -> - // 2 cases here: this member may have left voluntarily or they may have been "left" by someone else ie. kicked - return if (TextUtils.equals(event.root.sender, event.root.stateKey)) { - if (prevEventContent?.membership == Membership.INVITE) { - stringProvider.getString(R.string.notice_room_reject, senderDisplayName) - } else { - val leftDisplayName = RoomMemberEventHelper.senderName(eventContent, prevEventContent, event) - stringProvider.getString(R.string.notice_room_leave, leftDisplayName) - } - } else if (prevEventContent?.membership == Membership.INVITE) { - stringProvider.getString(R.string.notice_room_withdraw, senderDisplayName, targetDisplayName) - } else if (prevEventContent?.membership == Membership.JOIN) { - stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) - } else if (prevEventContent?.membership == Membership.BAN) { - stringProvider.getString(R.string.notice_room_unban, senderDisplayName, targetDisplayName) - } else { - null - } - Membership.BAN == eventContent?.membership -> - stringProvider.getString(R.string.notice_room_ban, senderDisplayName, targetDisplayName) - Membership.KNOCK == eventContent?.membership -> - stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) - else -> null - } - } - - -} - - diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomNameItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomNameItemFactory.kt deleted file mode 100644 index be33c44e..00000000 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/RoomNameItemFactory.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.detail.timeline.factory - -import android.text.TextUtils -import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.RoomNameContent -import im.vector.matrix.android.api.session.room.timeline.TimelineEvent -import im.vector.riotredesign.R -import im.vector.riotredesign.core.resources.StringProvider -import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem -import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_ - -class RoomNameItemFactory(private val stringProvider: StringProvider) { - - fun create(event: TimelineEvent): NoticeItem? { - - val content: RoomNameContent = event.root.content.toModel() ?: return null - val text = if (!TextUtils.isEmpty(content.name)) { - stringProvider.getString(R.string.notice_room_name_changed, event.senderName, content.name) - } else { - stringProvider.getString(R.string.notice_room_name_removed, event.senderName) - } - return NoticeItem_() - .noticeText(text) - .avatarUrl(event.senderAvatar) - .memberName(event.senderName) - } - - -} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index d5354434..db5e3e54 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -23,11 +23,7 @@ import im.vector.riotredesign.core.epoxy.VectorEpoxyModel import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController class TimelineItemFactory(private val messageItemFactory: MessageItemFactory, - private val roomNameItemFactory: RoomNameItemFactory, - private val roomTopicItemFactory: RoomTopicItemFactory, - private val roomMemberItemFactory: RoomMemberItemFactory, - private val roomHistoryVisibilityItemFactory: RoomHistoryVisibilityItemFactory, - private val callItemFactory: CallItemFactory, + private val noticeItemFactory: NoticeItemFactory, private val defaultItemFactory: DefaultItemFactory) { fun create(event: TimelineEvent, @@ -36,23 +32,22 @@ class TimelineItemFactory(private val messageItemFactory: MessageItemFactory, val computedModel = try { when (event.root.type) { - EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, callback) - EventType.STATE_ROOM_NAME -> roomNameItemFactory.create(event) - EventType.STATE_ROOM_TOPIC -> roomTopicItemFactory.create(event) - EventType.STATE_ROOM_MEMBER -> roomMemberItemFactory.create(event) - EventType.STATE_HISTORY_VISIBILITY -> roomHistoryVisibilityItemFactory.create(event) + EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, callback) + EventType.STATE_ROOM_NAME, + EventType.STATE_ROOM_TOPIC, + EventType.STATE_ROOM_MEMBER, + EventType.STATE_HISTORY_VISIBILITY, EventType.CALL_INVITE, EventType.CALL_HANGUP, - EventType.CALL_ANSWER -> callItemFactory.create(event) + EventType.CALL_ANSWER -> noticeItemFactory.create(event) EventType.ENCRYPTED, EventType.ENCRYPTION, EventType.STATE_ROOM_THIRD_PARTY_INVITE, EventType.STICKER, - EventType.STATE_ROOM_CREATE -> defaultItemFactory.create(event) - - else -> null + EventType.STATE_ROOM_CREATE -> defaultItemFactory.create(event) + else -> null } } catch (e: Exception) { defaultItemFactory.create(event, e) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/format/NoticeEventFormatter.kt new file mode 100644 index 00000000..ce4598f6 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -0,0 +1,185 @@ +/* + * 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.detail.timeline.format + +import android.text.TextUtils +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility +import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent +import im.vector.matrix.android.api.session.room.model.RoomMember +import im.vector.matrix.android.api.session.room.model.RoomNameContent +import im.vector.matrix.android.api.session.room.model.RoomTopicContent +import im.vector.matrix.android.api.session.room.model.call.CallInviteContent +import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.riotredesign.R +import im.vector.riotredesign.core.resources.StringProvider +import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName +import timber.log.Timber + +class NoticeEventFormatter(private val stringProvider: StringProvider) { + + fun format(timelineEvent: TimelineEvent): CharSequence? { + return when (timelineEvent.root.type) { + EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.senderName) + EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.senderName) + EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.senderName()) + EventType.STATE_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.senderName) + EventType.CALL_INVITE, + EventType.CALL_HANGUP, + EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.senderName) + else -> { + Timber.v("Type ${timelineEvent.root.type} not handled by this formatter") + null + } + } + } + + private fun formatRoomNameEvent(event: Event, senderName: String?): CharSequence? { + val content = event.content.toModel() ?: return null + return if (!TextUtils.isEmpty(content.name)) { + stringProvider.getString(R.string.notice_room_name_changed, senderName, content.name) + } else { + stringProvider.getString(R.string.notice_room_name_removed, senderName) + } + } + + private fun formatRoomTopicEvent(event: Event, senderName: String?): CharSequence? { + val content = event.content.toModel() ?: return null + return if (content.topic.isNullOrEmpty()) { + stringProvider.getString(R.string.notice_room_topic_removed, senderName) + } else { + stringProvider.getString(R.string.notice_room_topic_changed, senderName, content.topic) + } + } + + private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? { + val content = event.content.toModel() ?: return null + val formattedVisibility = when (content.historyVisibility) { + RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared) + RoomHistoryVisibility.INVITED -> stringProvider.getString(R.string.notice_room_visibility_invited) + RoomHistoryVisibility.JOINED -> stringProvider.getString(R.string.notice_room_visibility_joined) + RoomHistoryVisibility.WORLD_READABLE -> stringProvider.getString(R.string.notice_room_visibility_world_readable) + } + return stringProvider.getString(R.string.notice_made_future_room_visibility, senderName, formattedVisibility) + } + + private fun formatCallEvent(event: Event, senderName: String?): CharSequence? { + return when { + EventType.CALL_INVITE == event.type -> { + val content = event.content.toModel() ?: return null + val isVideoCall = content.offer.sdp == CallInviteContent.Offer.SDP_VIDEO + return if (isVideoCall) { + stringProvider.getString(R.string.notice_placed_video_call, senderName) + } else { + stringProvider.getString(R.string.notice_placed_voice_call, senderName) + } + } + EventType.CALL_ANSWER == event.type -> stringProvider.getString(R.string.notice_answered_call, senderName) + EventType.CALL_HANGUP == event.type -> stringProvider.getString(R.string.notice_ended_call, senderName) + else -> null + } + } + + private fun formatRoomMemberEvent(event: Event, senderName: String?): String? { + val eventContent: RoomMember? = event.content.toModel() + val prevEventContent: RoomMember? = event.prevContent.toModel() + val isMembershipEvent = prevEventContent?.membership != eventContent?.membership + return if (isMembershipEvent) { + buildMembershipNotice(event, senderName, eventContent, prevEventContent) + } else { + buildProfileNotice(event, senderName, eventContent, prevEventContent) + } + } + + private fun buildProfileNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String? { + val displayText = StringBuilder() + // Check display name has been changed + if (!TextUtils.equals(eventContent?.displayName, prevEventContent?.displayName)) { + val displayNameText = when { + prevEventContent?.displayName.isNullOrEmpty() -> + stringProvider.getString(R.string.notice_display_name_set, event.sender, eventContent?.displayName) + eventContent?.displayName.isNullOrEmpty() -> + stringProvider.getString(R.string.notice_display_name_removed, event.sender, prevEventContent?.displayName) + else -> + stringProvider.getString(R.string.notice_display_name_changed_from, + event.sender, prevEventContent?.displayName, eventContent?.displayName) + } + displayText.append(displayNameText) + } + // Check whether the avatar has been changed + if (!TextUtils.equals(eventContent?.avatarUrl, prevEventContent?.avatarUrl)) { + val displayAvatarText = if (displayText.isNotEmpty()) { + displayText.append(" ") + stringProvider.getString(R.string.notice_avatar_changed_too) + } else { + stringProvider.getString(R.string.notice_avatar_url_changed, senderName) + } + displayText.append(displayAvatarText) + } + return displayText.toString() + } + + private fun buildMembershipNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String? { + val senderDisplayName = senderName ?: event.sender + val targetDisplayName = eventContent?.displayName ?: event.sender + return when { + Membership.INVITE == eventContent?.membership -> { + // TODO get userId + val selfUserId = "" + when { + eventContent.thirdPartyInvite != null -> + stringProvider.getString(R.string.notice_room_third_party_registered_invite, + targetDisplayName, eventContent.thirdPartyInvite?.displayName) + TextUtils.equals(event.stateKey, selfUserId) -> + stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName) + event.stateKey.isNullOrEmpty() -> + stringProvider.getString(R.string.notice_room_invite_no_invitee, senderDisplayName) + else -> + stringProvider.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName) + } + } + Membership.JOIN == eventContent?.membership -> + stringProvider.getString(R.string.notice_room_join, senderDisplayName) + Membership.LEAVE == eventContent?.membership -> + // 2 cases here: this member may have left voluntarily or they may have been "left" by someone else ie. kicked + return if (TextUtils.equals(event.sender, event.stateKey)) { + if (prevEventContent?.membership == Membership.INVITE) { + stringProvider.getString(R.string.notice_room_reject, senderDisplayName) + } else { + stringProvider.getString(R.string.notice_room_leave, senderDisplayName) + } + } else if (prevEventContent?.membership == Membership.INVITE) { + stringProvider.getString(R.string.notice_room_withdraw, senderDisplayName, targetDisplayName) + } else if (prevEventContent?.membership == Membership.JOIN) { + stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) + } else if (prevEventContent?.membership == Membership.BAN) { + stringProvider.getString(R.string.notice_room_unban, senderDisplayName, targetDisplayName) + } else { + null + } + Membership.BAN == eventContent?.membership -> + stringProvider.getString(R.string.notice_room_ban, senderDisplayName, targetDisplayName) + Membership.KNOCK == eventContent?.membership -> + stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) + else -> null + } + } + +} diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/RoomMemberEventHelper.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/RoomMemberEventHelper.kt index 499f92f7..83b1fa6c 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/RoomMemberEventHelper.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/RoomMemberEventHelper.kt @@ -16,25 +16,11 @@ package im.vector.riotredesign.features.home.room.detail.timeline.helper -import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.timeline.TimelineEvent object RoomMemberEventHelper { - fun senderAvatar(eventContent: RoomMember?, prevEventContent: RoomMember?, event: TimelineEvent): String? { - return if (eventContent?.membership == Membership.LEAVE && eventContent.avatarUrl == null && prevEventContent?.avatarUrl != null) { - prevEventContent.avatarUrl - } else { - event.senderAvatar - } - } - fun senderName(eventContent: RoomMember?, prevEventContent: RoomMember?, event: TimelineEvent): String? { - return if (eventContent?.membership == Membership.LEAVE && eventContent.displayName == null && prevEventContent?.displayName != null) { - prevEventContent.displayName - } else { - event.senderName - } - } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index 53d63f58..282d8a14 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -17,6 +17,8 @@ package im.vector.riotredesign.features.home.room.detail.timeline.helper import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotredesign.core.extensions.localDateTime @@ -49,6 +51,26 @@ fun List.filterDisplayableEvents(): List { } } +fun TimelineEvent.senderAvatar(): String? { + // We might have no avatar when user leave, so we try to get it from prevContent + return senderAvatar + ?: if (root.type == EventType.STATE_ROOM_MEMBER) { + root.prevContent.toModel()?.avatarUrl + } else { + null + } +} + +fun TimelineEvent.senderName(): String? { + // We might have no senderName when user leave, so we try to get it from prevContent + return senderName + ?: if (root.type == EventType.STATE_ROOM_MEMBER) { + root.prevContent.toModel()?.displayName + } else { + null + } +} + fun TimelineEvent.canBeMerged(): Boolean { return root.type == EventType.STATE_ROOM_MEMBER } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt index 38463337..3286d540 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt @@ -71,7 +71,6 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - bindScope(getOrCreateScope(HomeModule.ROOM_LIST_SCOPE)) setupRecyclerView() setupCreateRoomButton() roomListViewModel.subscribe { renderState(it) } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt index 633deb90..1845b1e1 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomSummaryController.kt @@ -22,9 +22,11 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.core.resources.DateProvider import im.vector.riotredesign.core.resources.StringProvider +import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter class RoomSummaryController(private val stringProvider: StringProvider, + private val eventFormatter: NoticeEventFormatter, private val timelineDateFormatter: TimelineDateFormatter ) : TypedEpoxyController() { From 02555fcbac561348fae034a42a057cde06002f24 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 27 May 2019 15:43:26 +0200 Subject: [PATCH 08/34] Fix compilation issues after rebase --- .../home/room/detail/timeline/TimelineEventController.kt | 8 ++------ vector/src/main/res/layout/item_public_room.xml | 1 - vector/src/main/res/layout/item_room_directory.xml | 1 - vector/src/main/res/values/colors.xml | 3 --- 4 files changed, 2 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt index 59a296a7..84f97047 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt @@ -24,9 +24,7 @@ import androidx.recyclerview.widget.ListUpdateCallback import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyModel -import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary -import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent @@ -229,10 +227,8 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter, } else { val mergedEvents = (prevSameTypeEvents + listOf(event)).asReversed() val mergedData = mergedEvents.map { mergedEvent -> - val eventContent: RoomMember? = mergedEvent.root.content.toModel() - val prevEventContent: RoomMember? = mergedEvent.root.prevContent.toModel() - val senderAvatar = RoomMemberEventHelper.senderAvatar(eventContent, prevEventContent, mergedEvent) - val senderName = RoomMemberEventHelper.senderName(eventContent, prevEventContent, mergedEvent) + val senderAvatar = mergedEvent.senderAvatar() + val senderName = mergedEvent.senderName() MergedHeaderItem.Data( userId = mergedEvent.root.sender ?: "", avatarUrl = senderAvatar, diff --git a/vector/src/main/res/layout/item_public_room.xml b/vector/src/main/res/layout/item_public_room.xml index 2c84698b..5950ebac 100644 --- a/vector/src/main/res/layout/item_public_room.xml +++ b/vector/src/main/res/layout/item_public_room.xml @@ -6,7 +6,6 @@ android:id="@+id/itemPublicRoomLayout" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@drawable/bg_room_item" android:clickable="true" android:focusable="true" android:foreground="?attr/selectableItemBackground"> diff --git a/vector/src/main/res/layout/item_room_directory.xml b/vector/src/main/res/layout/item_room_directory.xml index a2f74643..edaea71d 100644 --- a/vector/src/main/res/layout/item_room_directory.xml +++ b/vector/src/main/res/layout/item_room_directory.xml @@ -6,7 +6,6 @@ android:id="@+id/itemRoomDirectoryLayout" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@drawable/bg_room_item" android:clickable="true" android:focusable="true" android:foreground="?attr/selectableItemBackground"> diff --git a/vector/src/main/res/values/colors.xml b/vector/src/main/res/values/colors.xml index a863247f..3147966e 100644 --- a/vector/src/main/res/values/colors.xml +++ b/vector/src/main/res/values/colors.xml @@ -21,8 +21,5 @@ #5d000000 #000000 - #de000000 - #61000000 - #5d000000 From 02a81dd9e1f50cc3da57b607624a43270d62f11c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 27 May 2019 16:03:53 +0200 Subject: [PATCH 09/34] Fix issue --- .../riotredesign/features/home/HomeModule.kt | 9 +++++---- .../features/home/group/GroupListViewModel.kt | 17 +++++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt index c5779816..8e06637f 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt @@ -70,12 +70,13 @@ class HomeModule { val eventHtmlRenderer = EventHtmlRenderer(GlideApp.with(fragment), fragment.requireContext(), get()) val noticeEventFormatter = get(parameters = { parametersOf(fragment) }) val timelineMediaSizeProvider = TimelineMediaSizeProvider() - val colorProvider = get() + val colorProvider = ColorProvider(fragment.requireContext()) val timelineDateFormatter = get() - val timelineItemFactory = TimelineItemFactory(messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer), - noticeItemFactory = NoticeItemFactory(noticeEventFormatter), - defaultItemFactory = DefaultItemFactory() + val timelineItemFactory = TimelineItemFactory( + messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer), + noticeItemFactory = NoticeItemFactory(noticeEventFormatter), + defaultItemFactory = DefaultItemFactory() ) TimelineEventController(timelineDateFormatter, timelineItemFactory, timelineMediaSizeProvider) } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt index 47534b8b..38fff5c5 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt @@ -85,16 +85,13 @@ class GroupListViewModel(initialState: GroupListViewState, session .rx().liveGroupSummaries() .map { - if (it.isEmpty()) { - it - } else { - val myUser = session.getUser(session.sessionParams.credentials.userId) - val allCommunityGroup = GroupSummary( - groupId = ALL_COMMUNITIES_GROUP_ID, - displayName = "All Communities", - avatarUrl = myUser?.avatarUrl ?: "") - listOf(allCommunityGroup) + it - } + val myUser = session.getUser(session.sessionParams.credentials.userId) + val allCommunityGroup = GroupSummary( + groupId = ALL_COMMUNITIES_GROUP_ID, + // TODO i18n + displayName = "All Communities", + avatarUrl = myUser?.avatarUrl ?: "") + listOf(allCommunityGroup) + it } .execute { async -> val newSelectedGroup = selectedGroup ?: async()?.firstOrNull() From dde94c0d0f4b49056913686664e657a0a1df1101 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 27 May 2019 17:28:18 +0200 Subject: [PATCH 10/34] Plug screens together --- .../core/platform/VectorBaseActivity.kt | 4 ++-- .../features/home/HomeActivity.kt | 6 ----- .../features/home/group/GroupListViewModel.kt | 4 ++-- .../home/room/list/RoomListFragment.kt | 14 +++++------- .../roomdirectory/PublicRoomsController.kt | 18 +++++++++------ .../roomdirectory/PublicRoomsFragment.kt | 22 +++++++++++++++++-- .../picker/RoomDirectoryPickerController.kt | 2 +- .../main/res/layout/fragment_home_detail.xml | 2 +- .../main/res/layout/fragment_room_detail.xml | 1 - .../main/res/layout/vector_invite_view.xml | 6 ++++- vector/src/main/res/menu/home.xml | 5 ----- vector/src/main/res/values/strings_riotX.xml | 2 ++ 12 files changed, 49 insertions(+), 37 deletions(-) diff --git a/vector/src/main/java/im/vector/riotredesign/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/riotredesign/core/platform/VectorBaseActivity.kt index 3ae8f3a8..b4b7609e 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/platform/VectorBaseActivity.kt @@ -296,8 +296,8 @@ abstract class VectorBaseActivity : BaseMvRxActivity() { * Temporary method * ========================================================================================== */ - fun notImplemented() { - toast(getString(R.string.not_implemented)) + fun notImplemented(message: String = "") { + toast(getString(R.string.not_implemented) + message.takeIf { message.isNotBlank() }.run { ": $message" }) } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt index a61412df..ed1fd844 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt @@ -123,12 +123,6 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { SignOutUiWorker(this).perform(Matrix.getInstance().currentSession!!) return true } - // TODO Temporary code here to create a room - R.id.tmp_menu_create_room -> { - // Start Activity for now - startActivity(Intent(this, RoomDirectoryActivity::class.java)) - return true - } } return true diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt index 38fff5c5..c06cbe5e 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/group/GroupListViewModel.kt @@ -24,6 +24,7 @@ import com.airbnb.mvrx.ViewModelContext import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.rx.rx +import im.vector.riotredesign.R import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.utils.LiveEvent @@ -88,8 +89,7 @@ class GroupListViewModel(initialState: GroupListViewState, val myUser = session.getUser(session.sessionParams.credentials.userId) val allCommunityGroup = GroupSummary( groupId = ALL_COMMUNITIES_GROUP_ID, - // TODO i18n - displayName = "All Communities", + displayName = stringProvider.getString(R.string.group_all_communities), avatarUrl = myUser?.avatarUrl ?: "") listOf(allCommunityGroup) + it } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt index 3286d540..0ac135d8 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt @@ -16,15 +16,12 @@ package im.vector.riotredesign.features.home.room.list +import android.content.Intent import android.os.Bundle import android.os.Parcelable import androidx.annotation.StringRes import androidx.recyclerview.widget.LinearLayoutManager -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Incomplete -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.args -import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.* import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotredesign.R @@ -32,13 +29,11 @@ import im.vector.riotredesign.core.epoxy.LayoutManagerStateRestorer import im.vector.riotredesign.core.extensions.observeEvent import im.vector.riotredesign.core.platform.StateView import im.vector.riotredesign.core.platform.VectorBaseFragment -import im.vector.riotredesign.features.home.HomeModule import im.vector.riotredesign.features.home.HomeNavigator +import im.vector.riotredesign.features.roomdirectory.RoomDirectoryActivity import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_room_list.* import org.koin.android.ext.android.inject -import org.koin.android.scope.ext.android.bindScope -import org.koin.android.scope.ext.android.getOrCreateScope @Parcelize data class RoomListParams( @@ -82,7 +77,8 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback { private fun setupCreateRoomButton() { createRoomButton.setImageResource(R.drawable.ic_add_white) createRoomButton.setOnClickListener { - vectorBaseActivity.notImplemented() + // Start Activity for now + startActivity(Intent(requireActivity(), RoomDirectoryActivity::class.java)) } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsController.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsController.kt index 50baf6d6..6f3725f1 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsController.kt @@ -83,23 +83,27 @@ class PublicRoomsController(private val stringProvider: StringProvider, avatarUrl(publicRoom.avatarUrl) roomName(publicRoom.name) nbOfMembers(publicRoom.numJoinedMembers) - when { - viewState.joinedRoomsIds.contains(publicRoom.roomId) -> joinState(PublicRoomItem.JoinState.JOINED) - viewState.joiningRoomsIds.contains(publicRoom.roomId) -> joinState(PublicRoomItem.JoinState.JOINING) - viewState.joiningErrorRoomsIds.contains(publicRoom.roomId) -> joinState(PublicRoomItem.JoinState.JOINING_ERROR) - else -> joinState(PublicRoomItem.JoinState.NOT_JOINED) + + val joinState = when { + viewState.joinedRoomsIds.contains(publicRoom.roomId) -> PublicRoomItem.JoinState.JOINED + viewState.joiningRoomsIds.contains(publicRoom.roomId) -> PublicRoomItem.JoinState.JOINING + viewState.joiningErrorRoomsIds.contains(publicRoom.roomId) -> PublicRoomItem.JoinState.JOINING_ERROR + else -> PublicRoomItem.JoinState.NOT_JOINED } + + joinState(joinState) + joinListener { callback?.onPublicRoomJoin(publicRoom) } globalListener { - callback?.onPublicRoomClicked(publicRoom) + callback?.onPublicRoomClicked(publicRoom, joinState) } } } interface Callback { - fun onPublicRoomClicked(publicRoom: PublicRoom) + fun onPublicRoomClicked(publicRoom: PublicRoom, joinState: PublicRoomItem.JoinState) fun onPublicRoomJoin(publicRoom: PublicRoom) fun loadMore() } diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt index aca1e30c..2497a1fd 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt @@ -30,6 +30,8 @@ import im.vector.riotredesign.R import im.vector.riotredesign.core.error.ErrorFormatter import im.vector.riotredesign.core.extensions.addFragmentToBackstack import im.vector.riotredesign.core.platform.VectorBaseFragment +import im.vector.riotredesign.features.home.room.detail.RoomDetailActivity +import im.vector.riotredesign.features.home.room.detail.RoomDetailArgs import im.vector.riotredesign.features.roomdirectory.picker.RoomDirectoryPickerFragment import io.reactivex.rxkotlin.subscribeBy import kotlinx.android.synthetic.main.fragment_public_rooms.* @@ -114,9 +116,25 @@ class PublicRoomsFragment : VectorBaseFragment(), PublicRoomsController.Callback publicRoomsList.setController(publicRoomsController) } - override fun onPublicRoomClicked(publicRoom: PublicRoom) { + override fun onPublicRoomClicked(publicRoom: PublicRoom, joinState: PublicRoomItem.JoinState) { Timber.v("PublicRoomClicked: $publicRoom") - vectorBaseActivity.notImplemented() + + when (joinState) { + PublicRoomItem.JoinState.JOINED -> { + val args = RoomDetailArgs(publicRoom.roomId) + val roomDetailIntent = RoomDetailActivity.newIntent(requireActivity(), args) + requireActivity().startActivity(roomDetailIntent) + } + PublicRoomItem.JoinState.NOT_JOINED, + PublicRoomItem.JoinState.JOINING_ERROR -> { + // ROOM PREVIEW + vectorBaseActivity.notImplemented("Opening room preview") + } + else -> { + Snackbar.make(publicRoomsCoordinator, getString(R.string.please_wait), Snackbar.LENGTH_SHORT) + .show() + } + } } override fun onPublicRoomJoin(publicRoom: PublicRoom) { diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/picker/RoomDirectoryPickerController.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/picker/RoomDirectoryPickerController.kt index 5b758e28..52d22c3b 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/picker/RoomDirectoryPickerController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/picker/RoomDirectoryPickerController.kt @@ -88,7 +88,7 @@ class RoomDirectoryPickerController(private val stringProvider: StringProvider, } interface Callback { - fun onRoomDirectoryClicked(roomDirectory: RoomDirectoryData) + fun onRoomDirectoryClicked(roomDirectoryData: RoomDirectoryData) fun retry() } diff --git a/vector/src/main/res/layout/fragment_home_detail.xml b/vector/src/main/res/layout/fragment_home_detail.xml index 9ef12b15..7b4a59bf 100644 --- a/vector/src/main/res/layout/fragment_home_detail.xml +++ b/vector/src/main/res/layout/fragment_home_detail.xml @@ -57,9 +57,9 @@ 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:labelVisibilityMode="unlabeled" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml index 345f1189..d414b01b 100644 --- a/vector/src/main/res/layout/fragment_room_detail.xml +++ b/vector/src/main/res/layout/fragment_room_detail.xml @@ -149,7 +149,6 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/roomToolbar" - app:layout_constraintVertical_bias="1.0" tools:visibility="visible" /> \ No newline at end of file diff --git a/vector/src/main/res/layout/vector_invite_view.xml b/vector/src/main/res/layout/vector_invite_view.xml index 1cb2c0c6..80f90eda 100644 --- a/vector/src/main/res/layout/vector_invite_view.xml +++ b/vector/src/main/res/layout/vector_invite_view.xml @@ -40,7 +40,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/inviteNameView" - tools:text="\@matthew:matrix.org" /> + tools:text=" @matthew:matrix.org" /> - - - \ No newline at end of file diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index a115f102..82c0f77e 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -27,5 +27,7 @@ No network. Please check your Internet connection. "Change" + "Please wait…" + "All Communities" \ No newline at end of file From 8f2754493c5951ce66c43410340a5852302ea988 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 27 May 2019 17:42:29 +0200 Subject: [PATCH 11/34] Fix issue with Avatar URL --- .../session/content/DefaultContentUrlResolver.kt | 16 ++++++++++++++-- .../internal/session/content/FileUploader.kt | 12 ++++-------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUrlResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUrlResolver.kt index 8845c372..0fad40d0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUrlResolver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUrlResolver.kt @@ -21,10 +21,19 @@ import im.vector.matrix.android.api.session.content.ContentUrlResolver private const val MATRIX_CONTENT_URI_SCHEME = "mxc://" -internal const val URI_PREFIX_CONTENT_API = "_matrix/media/v1/" +private const val URI_PREFIX_CONTENT_API = "_matrix/media/v1/" internal class DefaultContentUrlResolver(private val homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver { + companion object { + fun getUploadUrl(homeServerConnectionConfig: HomeServerConnectionConfig): String { + val baseUrl = homeServerConnectionConfig.homeServerUri.toString() + val sep = if (baseUrl.endsWith("/")) "" else "/" + + return baseUrl + sep + URI_PREFIX_CONTENT_API + "upload" + } + } + override fun resolveFullSize(contentUrl: String?): String? { if (contentUrl?.isValidMatrixContentUrl() == true) { val baseUrl = homeServerConnectionConfig.homeServerUri.toString() @@ -57,7 +66,10 @@ internal class DefaultContentUrlResolver(private val homeServerConnectionConfig: fragment = serverAndMediaId.substring(fragmentOffset) serverAndMediaId = serverAndMediaId.substring(0, fragmentOffset) } - return baseUrl + prefix + serverAndMediaId + (params ?: "") + fragment + + val sep = if (baseUrl.endsWith("/")) "" else "/" + + return baseUrl + sep + prefix + serverAndMediaId + (params ?: "") + fragment } private fun String.isValidMatrixContentUrl(): Boolean { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt index 1ec18127..e8133070 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt @@ -21,19 +21,15 @@ import arrow.core.Try.Companion.raise import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.network.ProgressRequestBody -import okhttp3.HttpUrl -import okhttp3.MediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody +import okhttp3.* import java.io.File import java.io.IOException internal class FileUploader(private val okHttpClient: OkHttpClient, - private val sessionParams: SessionParams) { + sessionParams: SessionParams) { - private val uploadUrl = sessionParams.homeServerConnectionConfig.homeServerUri.toString() + URI_PREFIX_CONTENT_API + "upload" + private val uploadUrl = DefaultContentUrlResolver.getUploadUrl(sessionParams.homeServerConnectionConfig) private val moshi = MoshiProvider.providesMoshi() private val responseAdapter = moshi.adapter(ContentUploadResponse::class.java) @@ -82,7 +78,7 @@ internal class FileUploader(private val okHttpClient: OkHttpClient, response.body()?.source()?.let { responseAdapter.fromJson(it) } - ?: throw IOException() + ?: throw IOException() } } } From 33fbcc8ba36229a0f6298ee07bbae90e752e33ba Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 May 2019 15:58:30 +0200 Subject: [PATCH 12/34] RoomPreview when the room is not world readable --- vector/src/main/AndroidManifest.xml | 1 + .../core/platform/VectorBaseActivity.kt | 17 ++-- .../core/platform/VectorBaseFragment.kt | 15 ++++ .../features/home/HomeActivity.kt | 4 +- .../home/room/detail/RoomDetailActivity.kt | 6 +- .../home/room/detail/RoomDetailFragment.kt | 9 +- .../features/invite/VectorInviteView.kt | 1 - .../features/rageshake/BugReportActivity.kt | 3 +- .../reactions/EmojiReactionPickerActivity.kt | 4 +- .../roomdirectory/PublicRoomsFragment.kt | 3 +- .../roompreview/RoomPreviewActivity.kt | 87 +++++++++++++++++++ .../RoomPreviewNoPreviewFragment.kt | 61 +++++++++++++ .../settings/VectorSettingsActivity.kt | 3 +- .../main/res/layout/activity_bug_report.xml | 2 +- .../layout/activity_emoji_reaction_picker.xml | 2 +- .../res/layout/activity_vector_settings.xml | 2 +- .../fragment_room_preview_no_preview.xml | 83 ++++++++++++++++++ vector/src/main/res/values/strings_riotX.xml | 2 + 18 files changed, 270 insertions(+), 35 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewActivity.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt create mode 100644 vector/src/main/res/layout/fragment_room_preview_no_preview.xml diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 5d8d3dc7..1c3e843b 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -44,6 +44,7 @@ android:label="@string/title_activity_emoji_reaction_picker" /> + { // ROOM PREVIEW - vectorBaseActivity.notImplemented("Opening room preview") + requireActivity().startActivity(RoomPreviewActivity.getIntent(requireActivity(), publicRoom)) } else -> { Snackbar.make(publicRoomsCoordinator, getString(R.string.please_wait), Snackbar.LENGTH_SHORT) diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewActivity.kt new file mode 100644 index 00000000..5b489183 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewActivity.kt @@ -0,0 +1,87 @@ +/* + * 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.roomdirectory.roompreview + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.os.Parcelable +import androidx.appcompat.widget.Toolbar +import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom +import im.vector.riotredesign.R +import im.vector.riotredesign.core.extensions.addFragment +import im.vector.riotredesign.core.platform.ToolbarConfigurable +import im.vector.riotredesign.core.platform.VectorBaseActivity +import im.vector.riotredesign.features.roomdirectory.RoomDirectoryModule +import kotlinx.android.parcel.Parcelize +import org.koin.android.scope.ext.android.bindScope +import org.koin.android.scope.ext.android.getOrCreateScope + +@Parcelize +data class RoomPreviewData( + val roomId: String, + val roomName: String?, + val topic: String?, + val worldReadable: Boolean, + val avatarUrl: String? +) : Parcelable + + +class RoomPreviewActivity : VectorBaseActivity(), ToolbarConfigurable { + + companion object { + private const val ARG = "ARG" + + fun getIntent(context: Context, publicRoom: PublicRoom): Intent { + return Intent(context, RoomPreviewActivity::class.java).apply { + putExtra(ARG, RoomPreviewData( + roomId = publicRoom.roomId, + roomName = publicRoom.name, + topic = publicRoom.topic, + worldReadable = publicRoom.worldReadable, + avatarUrl = publicRoom.avatarUrl + )) + } + } + } + + override fun getLayoutRes() = R.layout.activity_simple + + override fun configure(toolbar: Toolbar) { + configureToolbar(toolbar) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + bindScope(getOrCreateScope(RoomDirectoryModule.ROOM_DIRECTORY_SCOPE)) + } + + override fun initUiAndData() { + if (isFirstCreation()) { + val args = intent.getParcelableExtra(ARG) + + if (args.worldReadable) { + // TODO Room preview + notImplemented("Room preview of world readable room") + } else { + addFragment(RoomPreviewNoPreviewFragment.newInstance(args), R.id.simpleFragmentContainer) + } + } + } + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt new file mode 100644 index 00000000..5b6b902f --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt @@ -0,0 +1,61 @@ +/* + * 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.roomdirectory.roompreview + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import com.airbnb.mvrx.args +import im.vector.riotredesign.R +import im.vector.riotredesign.core.extensions.setTextOrHide +import im.vector.riotredesign.core.platform.VectorBaseFragment +import im.vector.riotredesign.features.home.AvatarRenderer +import im.vector.riotredesign.features.roomdirectory.RoomDirectoryModule +import kotlinx.android.synthetic.main.fragment_room_preview_no_preview.* +import org.koin.android.scope.ext.android.bindScope +import org.koin.android.scope.ext.android.getOrCreateScope + +class RoomPreviewNoPreviewFragment : VectorBaseFragment() { + + companion object { + fun newInstance(arg: RoomPreviewData): Fragment { + return RoomPreviewNoPreviewFragment().apply { setArguments(arg) } + } + } + + private val roomPreviewData: RoomPreviewData by args() + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + bindScope(getOrCreateScope(RoomDirectoryModule.ROOM_DIRECTORY_SCOPE)) + setupToolbar(roomPreviewNoPreviewToolbar) + } + + override fun getLayoutResId() = R.layout.fragment_room_preview_no_preview + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + AvatarRenderer.render(roomPreviewData.avatarUrl, roomPreviewData.roomId, roomPreviewData.roomName, roomPreviewNoPreviewAvatar) + roomPreviewNoPreviewName.text = roomPreviewData.roomName + roomPreviewNoPreviewTopic.setTextOrHide(roomPreviewData.topic) + + roomPreviewNoPreviewJoin.setOnClickListener { + vectorBaseActivity.notImplemented("Join from preview") + } + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsActivity.kt index a657e110..6208c75e 100755 --- a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsActivity.kt @@ -24,6 +24,7 @@ import androidx.preference.PreferenceFragmentCompat import im.vector.matrix.android.api.session.Session import im.vector.riotredesign.R import im.vector.riotredesign.core.platform.VectorBaseActivity +import kotlinx.android.synthetic.main.activity_vector_settings.* import org.koin.android.ext.android.inject /** @@ -45,7 +46,7 @@ class VectorSettingsActivity : VectorBaseActivity(), private val session by inject() override fun initUiAndData() { - configureToolbar() + configureToolbar(settingsToolbar) if (isFirstCreation()) { vectorSettingsPreferencesFragment = VectorSettingsPreferencesFragment.newInstance(session.sessionParams.credentials.userId) diff --git a/vector/src/main/res/layout/activity_bug_report.xml b/vector/src/main/res/layout/activity_bug_report.xml index 82916ee6..4d87ced7 100644 --- a/vector/src/main/res/layout/activity_bug_report.xml +++ b/vector/src/main/res/layout/activity_bug_report.xml @@ -6,7 +6,7 @@ android:orientation="vertical"> diff --git a/vector/src/main/res/layout/activity_emoji_reaction_picker.xml b/vector/src/main/res/layout/activity_emoji_reaction_picker.xml index 8e1edab3..4744931b 100644 --- a/vector/src/main/res/layout/activity_emoji_reaction_picker.xml +++ b/vector/src/main/res/layout/activity_emoji_reaction_picker.xml @@ -19,7 +19,7 @@ android:layout_height="wrap_content"> diff --git a/vector/src/main/res/layout/fragment_room_preview_no_preview.xml b/vector/src/main/res/layout/fragment_room_preview_no_preview.xml new file mode 100644 index 00000000..30a378ed --- /dev/null +++ b/vector/src/main/res/layout/fragment_room_preview_no_preview.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + +