From df4df81ef3b2a9a2085626eff375d7510d9eb99a Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 14 Jan 2020 17:08:21 +0100 Subject: [PATCH] Profile: handle ignore/unignore action + adjust UI --- .../MatrixItemAppBarStateChangeListener.kt | 12 +- .../vector/riotx/core/platform/StateView.kt | 2 +- .../riotx/core/platform/VectorBaseFragment.kt | 19 +++ .../im/vector/riotx/core/utils/DataSource.kt | 5 +- .../RoomMemberProfileAction.kt | 8 +- .../RoomMemberProfileController.kt | 48 +++--- .../RoomMemberProfileFragment.kt | 89 ++++++----- .../RoomMemberProfileViewEvents.kt | 27 ++++ .../RoomMemberProfileViewModel.kt | 150 +++++++++++++----- .../RoomMemberProfileViewState.kt | 20 +-- .../roomprofile/RoomProfileActivity.kt | 26 +-- .../roomprofile/RoomProfileFragment.kt | 32 ++-- .../roomprofile/RoomProfileViewEvents.kt | 2 +- .../roomprofile/RoomProfileViewModel.kt | 5 +- .../members/RoomMemberListFragment.kt | 6 +- .../res/layout/fragment_matrix_profile.xml | 9 +- .../main/res/layout/item_profile_action.xml | 8 +- .../res/layout/item_profile_matrix_item.xml | 8 +- .../view_stub_room_member_profile_header.xml | 121 +++++++------- vector/src/main/res/values/strings_riotX.xml | 2 + 20 files changed, 352 insertions(+), 247 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewEvents.kt diff --git a/vector/src/main/java/im/vector/riotx/core/animations/MatrixItemAppBarStateChangeListener.kt b/vector/src/main/java/im/vector/riotx/core/animations/MatrixItemAppBarStateChangeListener.kt index 6e34983018..427f37ba49 100644 --- a/vector/src/main/java/im/vector/riotx/core/animations/MatrixItemAppBarStateChangeListener.kt +++ b/vector/src/main/java/im/vector/riotx/core/animations/MatrixItemAppBarStateChangeListener.kt @@ -20,16 +20,18 @@ package im.vector.riotx.core.animations import android.view.View import com.google.android.material.appbar.AppBarLayout -class MatrixItemAppBarStateChangeListener(private val animationDuration: Long, private val views: List) : AppBarStateChangeListener() { +class MatrixItemAppBarStateChangeListener(private val headerView: View, private val toolbarViews: List) : AppBarStateChangeListener() { override fun onStateChanged(appBarLayout: AppBarLayout, state: State) { if (state == State.COLLAPSED) { - views.forEach { - it.animate().alpha(1f).duration = animationDuration + 100 + headerView.visibility = View.INVISIBLE + toolbarViews.forEach { + it.animate().alpha(1f).duration = 150 } } else { - views.forEach { - it.animate().alpha(0f).duration = animationDuration - 100 + headerView.visibility = View.VISIBLE + toolbarViews.forEach { + it.animate().alpha(0f).duration = 150 } } } diff --git a/vector/src/main/java/im/vector/riotx/core/platform/StateView.kt b/vector/src/main/java/im/vector/riotx/core/platform/StateView.kt index f674478724..4c5a987b4b 100755 --- a/vector/src/main/java/im/vector/riotx/core/platform/StateView.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/StateView.kt @@ -34,7 +34,7 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? data class Error(val message: CharSequence? = null) : State() } - private var eventCallback: EventCallback? = null + var eventCallback: EventCallback? = null var contentView: View? = null diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt index 91ecf188dd..a77361126a 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt @@ -14,8 +14,11 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") + package im.vector.riotx.core.platform +import android.app.ProgressDialog import android.content.Context import android.os.Bundle import android.os.Parcelable @@ -59,6 +62,9 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector { protected lateinit var navigator: Navigator protected lateinit var errorFormatter: ErrorFormatter + private var progress: ProgressDialog? = null + + /* ========================================================================================== * View model * ========================================================================================== */ @@ -177,6 +183,19 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector { } } + protected fun showLoadingDialog(message: CharSequence, cancelable: Boolean = false) { + progress = ProgressDialog(requireContext()).apply { + setCancelable(cancelable) + setMessage(message) + setProgressStyle(ProgressDialog.STYLE_SPINNER) + show() + } + } + + protected fun dismissLoadingDialog(){ + progress?.dismiss() + } + /* ========================================================================================== * Toolbar * ========================================================================================== */ diff --git a/vector/src/main/java/im/vector/riotx/core/utils/DataSource.kt b/vector/src/main/java/im/vector/riotx/core/utils/DataSource.kt index 726d2ea697..232a164b57 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/DataSource.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/DataSource.kt @@ -19,6 +19,7 @@ package im.vector.riotx.core.utils import com.jakewharton.rxrelay2.BehaviorRelay import com.jakewharton.rxrelay2.PublishRelay import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers interface DataSource { @@ -37,7 +38,7 @@ open class BehaviorDataSource(private val defaultValue: T? = null) : MutableD private val behaviorRelay = createRelay() override fun observe(): Observable { - return behaviorRelay.hide().observeOn(Schedulers.computation()) + return behaviorRelay.hide().observeOn(AndroidSchedulers.mainThread()) } override fun post(value: T) { @@ -61,7 +62,7 @@ open class PublishDataSource : MutableDataSource { private val publishRelay = PublishRelay.create() override fun observe(): Observable { - return publishRelay.hide() + return publishRelay.hide().observeOn(AndroidSchedulers.mainThread()) } override fun post(value: T) { diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileAction.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileAction.kt index f4e27b5bc0..a38d4967bc 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileAction.kt @@ -21,11 +21,7 @@ import im.vector.riotx.core.platform.VectorViewModelAction sealed class RoomMemberProfileAction : VectorViewModelAction { - sealed class Displayable : RoomMemberProfileAction() { - object JumpToReadReceipt : Displayable() - object Ignore : Displayable() - object Mention : Displayable() - } - + object RetryFetchingInfo: RoomMemberProfileAction() + object IgnoreUser: RoomMemberProfileAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileController.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileController.kt index ee3a7ccebd..98e422cb0f 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileController.kt @@ -37,36 +37,33 @@ class RoomMemberProfileController @Inject constructor(private val stringProvider } override fun buildModels(data: RoomMemberProfileViewState?) { - if (data == null) { + if (data?.userMatrixItem?.invoke() == null) { return } - val roomMemberSummary = data.roomMemberSummary() - val profileInfo = data.profileInfo() - if (roomMemberSummary == null && profileInfo != null) { - buildUserActions() - } else if (roomMemberSummary != null) { + if (data.showAsMember) { buildRoomMemberActions(data) + } else { + buildUserActions(data) } } - private fun buildUserActions() { + private fun buildUserActions(state: RoomMemberProfileViewState) { + val ignoreActionTitle = state.buildIgnoreActionTitle() ?: return // More buildProfileSection(stringProvider.getString(R.string.room_profile_section_more)) buildProfileAction( id = "ignore", - title = stringProvider.getString(R.string.ignore), + title = ignoreActionTitle, destructive = true, editable = false, action = { callback?.onIgnoreClicked() } ) } - private fun buildRoomMemberActions(data: RoomMemberProfileViewState) { - val roomSummaryEntity = data.roomSummary() ?: return - + private fun buildRoomMemberActions(state: RoomMemberProfileViewState) { // Security buildProfileSection(stringProvider.getString(R.string.room_profile_section_security)) - val learnMoreSubtitle = if (roomSummaryEntity.isEncrypted) { + val learnMoreSubtitle = if (state.isRoomEncrypted) { R.string.room_profile_encrypted_subtitle } else { R.string.room_profile_not_encrypted_subtitle @@ -80,7 +77,7 @@ class RoomMemberProfileController @Inject constructor(private val stringProvider ) // More - if (!data.isMine) { + if (!state.isMine) { buildProfileSection(stringProvider.getString(R.string.room_profile_section_more)) buildProfileAction( id = "read_receipt", @@ -94,15 +91,26 @@ class RoomMemberProfileController @Inject constructor(private val stringProvider editable = false, action = { callback?.onMentionClicked() } ) - buildProfileAction( - id = "ignore", - title = stringProvider.getString(R.string.ignore), - destructive = true, - editable = false, - action = { callback?.onIgnoreClicked() } - ) + val ignoreActionTitle = state.buildIgnoreActionTitle() + if (ignoreActionTitle != null) { + buildProfileAction( + id = "ignore", + title = ignoreActionTitle, + destructive = true, + editable = false, + action = { callback?.onIgnoreClicked() } + ) + } } + } + private fun RoomMemberProfileViewState.buildIgnoreActionTitle(): String? { + val isIgnored = isIgnored() ?: return null + return if (isIgnored) { + stringProvider.getString(R.string.unignore) + } else { + stringProvider.getString(R.string.ignore) + } } diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt index d27e35bb1f..423cc86a62 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -15,28 +15,26 @@ * */ +@file:Suppress("DEPRECATION") + package im.vector.riotx.features.roommemberprofile import android.os.Bundle import android.os.Parcelable import android.view.View -import com.airbnb.mvrx.args -import com.airbnb.mvrx.fragmentViewModel -import com.airbnb.mvrx.withState -import im.vector.matrix.android.api.session.room.powerlevers.PowerLevelsConstants -import im.vector.matrix.android.api.session.room.powerlevers.PowerLevelsHelper -import im.vector.matrix.android.api.util.toMatrixItem +import com.airbnb.mvrx.* +import im.vector.matrix.android.api.util.MatrixItem import im.vector.riotx.R import im.vector.riotx.core.animations.AppBarStateChangeListener import im.vector.riotx.core.animations.MatrixItemAppBarStateChangeListener import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.extensions.setTextOrHide +import im.vector.riotx.core.platform.StateView import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.features.home.AvatarRenderer import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_matrix_profile.* -import kotlinx.android.synthetic.main.fragment_matrix_profile.matrixProfileHeaderView import kotlinx.android.synthetic.main.view_stub_room_member_profile_header.* import javax.inject.Inject @@ -62,14 +60,32 @@ class RoomMemberProfileFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupToolbar(matrixProfileToolbar) - matrixProfileHeaderView.apply { - layoutResource = R.layout.view_stub_room_member_profile_header - inflate() + val headerView = matrixProfileHeaderView.let { + it.layoutResource = R.layout.view_stub_room_member_profile_header + it.inflate() } + memberProfileStateView.eventCallback = object : StateView.EventCallback { + override fun onRetryClicked() { + viewModel.handle(RoomMemberProfileAction.RetryFetchingInfo) + } + } + memberProfileStateView.contentView = memberProfileInfoContainer matrixProfileRecyclerView.configureWith(roomMemberProfileController, hasFixedSize = true) roomMemberProfileController.callback = this - appBarStateChangeListener = MatrixItemAppBarStateChangeListener(matrixProfileCollapsingToolbarLayout.scrimAnimationDuration, listOf(matrixProfileToolbarAvatarImageView, matrixProfileToolbarTitleView)) + appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView, listOf(matrixProfileToolbarAvatarImageView, + matrixProfileToolbarTitleView)) matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) + viewModel.viewEvents + .observe() + .subscribe { + dismissLoadingDialog() + when (it) { + is RoomMemberProfileViewEvents.Loading -> showLoadingDialog(it.message) + is RoomMemberProfileViewEvents.Failure -> showErrorInSnackbar(it.throwable) + } + } + .disposeOnDestroyView() + } override fun onDestroyView() { @@ -81,42 +97,37 @@ class RoomMemberProfileFragment @Inject constructor( override fun invalidate() = withState(viewModel) { state -> - val memberMatrixItem = state.memberAsMatrixItem() - if (memberMatrixItem != null) { - memberProfileIdView.text = memberMatrixItem.id - val bestName = memberMatrixItem.getBestName() - memberProfileNameView.text = bestName - matrixProfileToolbarTitleView.text = bestName - avatarRenderer.render(memberMatrixItem, memberProfileAvatarView) - avatarRenderer.render(memberMatrixItem, matrixProfileToolbarAvatarImageView) - } - - val roomSummary = state.roomSummary() - val powerLevelsContent = state.powerLevelsContent() - if (powerLevelsContent == null || roomSummary == null) { - memberProfilePowerLevelView.visibility = View.GONE - } else { - val roomName = roomSummary.toMatrixItem().getBestName() - val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) - val userPowerLevel = powerLevelsHelper.getUserPowerLevel(state.userId) - val powerLevelText = if (userPowerLevel == PowerLevelsConstants.DEFAULT_ROOM_ADMIN_LEVEL) { - getString(R.string.room_member_power_level_admin_in, roomName) - } else if (userPowerLevel == PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL) { - getString(R.string.room_member_power_level_moderator_in, roomName) - } else if (userPowerLevel == PowerLevelsConstants.DEFAULT_ROOM_USER_LEVEL) { - null - } else { - getString(R.string.room_member_power_level_custom_in, userPowerLevel, roomName) + when (val asyncUserMatrixItem = state.userMatrixItem) { + is Incomplete -> { + matrixProfileToolbarTitleView.text = state.userId + avatarRenderer.render(MatrixItem.UserItem(state.userId, null, null), matrixProfileToolbarAvatarImageView) + memberProfileStateView.state = StateView.State.Loading + } + is Fail -> { + avatarRenderer.render(MatrixItem.UserItem(state.userId, null, null), matrixProfileToolbarAvatarImageView) + matrixProfileToolbarTitleView.text = state.userId + val failureMessage = errorFormatter.toHumanReadable(asyncUserMatrixItem.error) + memberProfileStateView.state = StateView.State.Error(failureMessage) + } + is Success -> { + val userMatrixItem = asyncUserMatrixItem() + memberProfileStateView.state = StateView.State.Content + memberProfileIdView.text = userMatrixItem.id + val bestName = userMatrixItem.getBestName() + memberProfileNameView.text = bestName + matrixProfileToolbarTitleView.text = bestName + avatarRenderer.render(userMatrixItem, memberProfileAvatarView) + avatarRenderer.render(userMatrixItem, matrixProfileToolbarAvatarImageView) } - memberProfilePowerLevelView.setTextOrHide(powerLevelText) } + memberProfilePowerLevelView.setTextOrHide(state.userPowerLevelString()) roomMemberProfileController.setData(state) } // RoomMemberProfileController.Callback override fun onIgnoreClicked() { - vectorBaseActivity.notImplemented("Ignore") + viewModel.handle(RoomMemberProfileAction.IgnoreUser) } override fun onLearnMoreClicked() { diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewEvents.kt new file mode 100644 index 0000000000..b4f526370b --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewEvents.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2020 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.riotx.features.roommemberprofile + +/** + * Transient events for RoomProfile + */ +sealed class RoomMemberProfileViewEvents { + data class Loading(val message: CharSequence) : RoomMemberProfileViewEvents() + object OnIgnoreActionSuccess : RoomMemberProfileViewEvents() + data class Failure(val throwable: Throwable) : RoomMemberProfileViewEvents() + +} diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt index e23db1a177..7cb33a2d5a 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -17,25 +17,43 @@ package im.vector.riotx.features.roommemberprofile +import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject +import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.query.QueryStringValue import im.vector.matrix.android.api.session.Session 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.profile.ProfileService +import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.members.roomMemberQueryParams import im.vector.matrix.android.api.session.room.model.PowerLevelsContent +import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.android.api.session.room.powerlevers.PowerLevelsConstants +import im.vector.matrix.android.api.session.room.powerlevers.PowerLevelsHelper +import im.vector.matrix.android.api.util.MatrixItem +import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.api.util.toOptional import im.vector.matrix.rx.mapOptional import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap +import im.vector.riotx.R import im.vector.riotx.core.platform.VectorViewModel -import timber.log.Timber +import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.core.utils.DataSource +import im.vector.riotx.core.utils.PublishDataSource +import io.reactivex.Observable +import io.reactivex.functions.BiFunction +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private val initialState: RoomMemberProfileViewState, + private val stringProvider: StringProvider, private val session: Session) : VectorViewModel(initialState) { @@ -53,6 +71,9 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v } } + private val _viewEvents = PublishDataSource() + val viewEvents: DataSource = _viewEvents + private val room = if (initialState.roomId != null) { session.getRoom(initialState.roomId) } else { @@ -61,65 +82,118 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v init { setState { copy(isMine = session.myUserId == this.userId) } - observeRoomSummary() - observeRoomMemberSummary() - observePowerLevel() - fetchProfileInfoIfRequired() + observeIgnoredState() + viewModelScope.launch(Dispatchers.Main) { + // Do we have a room member for this id. + val roomMember = withContext(Dispatchers.Default) { + room?.getRoomMember(initialState.userId) + } + // If not, we look for profile info on the server + if (room == null || roomMember == null) { + fetchProfileInfo() + } else { + // otherwise we just start listening to db + setState { copy(showAsMember = true) } + observeRoomMemberSummary(room) + observeRoomSummaryAndPowerLevels(room) + } + } } - private fun fetchProfileInfoIfRequired() { - val roomMember = room?.getRoomMember(initialState.userId) - if (roomMember != null) { - return - } - session.rx().getProfileInfo(initialState.userId) + private fun observeIgnoredState() { + session.rx().liveIgnoredUsers() + .map { ignored -> + ignored.find { + it.userId == initialState.userId + } != null + } .execute { - copy(profileInfo = it) + copy(isIgnored = it) } } override fun handle(action: RoomMemberProfileAction) { - Timber.v("Handle $action") + when (action) { + RoomMemberProfileAction.RetryFetchingInfo -> fetchProfileInfo() + is RoomMemberProfileAction.IgnoreUser -> handleIgnoreAction() + } } - private fun observeRoomSummary() { - if (room == null) { - return - } - room.rx().liveRoomSummary() - .unwrap() - .execute { - copy(roomSummary = it) - } - } - - private fun observeRoomMemberSummary() { - if (room == null) { - return - } + private fun observeRoomMemberSummary(room: Room) { val queryParams = roomMemberQueryParams { this.userId = QueryStringValue.Equals(initialState.userId, QueryStringValue.Case.SENSITIVE) } room.rx().liveRoomMembers(queryParams) - .map { it.firstOrNull().toOptional() } + .map { it.firstOrNull()?.toMatrixItem().toOptional() } .unwrap() .execute { - copy(roomMemberSummary = it) + copy(userMatrixItem = it) } } - private fun observePowerLevel() { - if (room == null) { - return - } - room.rx() - .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS) + private fun fetchProfileInfo() { + session.rx().getProfileInfo(initialState.userId) + .map { + MatrixItem.UserItem( + id = initialState.userId, + displayName = it[ProfileService.DISPLAY_NAME_KEY] as? String, + avatarUrl = it[ProfileService.AVATAR_URL_KEY] as? String + ) + } + .execute { + copy(userMatrixItem = it) + } + } + + private fun observeRoomSummaryAndPowerLevels(room: Room) { + val roomSummaryLive = room.rx().liveRoomSummary().unwrap() + val powerLevelsContentLive = room.rx().liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS) .mapOptional { it.content.toModel() } .unwrap() - .execute { - copy(powerLevelsContent = it) + + roomSummaryLive.execute { + copy(isRoomEncrypted = it.invoke()?.isEncrypted == true) + } + Observable + .combineLatest( + roomSummaryLive, + powerLevelsContentLive, + BiFunction { roomSummary, powerLevelsContent -> + val roomName = roomSummary.toMatrixItem().getBestName() + val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) + val userPowerLevel = powerLevelsHelper.getUserPowerLevel(initialState.userId) + if (userPowerLevel == PowerLevelsConstants.DEFAULT_ROOM_ADMIN_LEVEL) { + stringProvider.getString(R.string.room_member_power_level_admin_in, roomName) + } else if (userPowerLevel == PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL) { + stringProvider.getString(R.string.room_member_power_level_moderator_in, roomName) + } else if (userPowerLevel == PowerLevelsConstants.DEFAULT_ROOM_USER_LEVEL) { + "" + } else { + stringProvider.getString(R.string.room_member_power_level_custom_in, userPowerLevel, roomName) + } + } + ).execute { + copy(userPowerLevelString = it) } } + private fun handleIgnoreAction() = withState { state -> + val isIgnored = state.isIgnored() ?: return@withState + _viewEvents.post(RoomMemberProfileViewEvents.Loading(stringProvider.getString(R.string.please_wait))) + val ignoreActionCallback = object : MatrixCallback { + override fun onSuccess(data: Unit) { + _viewEvents.post(RoomMemberProfileViewEvents.OnIgnoreActionSuccess) + } + + override fun onFailure(failure: Throwable) { + _viewEvents.post(RoomMemberProfileViewEvents.Failure(failure)) + } + } + if (isIgnored) { + session.unIgnoreUserIds(listOf(state.userId), ignoreActionCallback) + } else { + session.ignoreUserIds(listOf(initialState.userId), ignoreActionCallback) + } + } } diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewState.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewState.kt index f22751a27b..aa95f58fc1 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewState.kt @@ -20,33 +20,23 @@ package im.vector.riotx.features.roommemberprofile import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized -import im.vector.matrix.android.api.session.profile.ProfileService import im.vector.matrix.android.api.session.room.model.PowerLevelsContent -import im.vector.matrix.android.api.session.room.model.RoomMemberSummary import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.MatrixItem -import im.vector.matrix.android.api.util.toMatrixItem - -typealias ProfileInfo = JsonDict data class RoomMemberProfileViewState( val userId: String, val roomId: String?, + val showAsMember: Boolean = false, val isMine: Boolean = false, - val roomSummary: Async = Uninitialized, - val roomMemberSummary: Async = Uninitialized, - val profileInfo: Async = Uninitialized, - val powerLevelsContent: Async = Uninitialized + val isIgnored: Async = Uninitialized, + val isRoomEncrypted: Boolean = false, + val userPowerLevelString: Async = Uninitialized, + val userMatrixItem: Async = Uninitialized ) : MvRxState { constructor(args: RoomMemberProfileArgs) : this(roomId = args.roomId, userId = args.userId) - fun memberAsMatrixItem(): MatrixItem? { - return roomMemberSummary()?.toMatrixItem() ?: profileInfo()?.let { - MatrixItem.UserItem(userId, it[ProfileService.DISPLAY_NAME_KEY] as? String, it[ProfileService.AVATAR_URL_KEY] as? String) - } - } - } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileActivity.kt index bc8bc2a959..14c3421e7f 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileActivity.kt @@ -21,8 +21,8 @@ import android.content.Context import android.content.Intent import androidx.appcompat.widget.Toolbar import im.vector.riotx.R -import im.vector.riotx.core.extensions.commitTransaction -import im.vector.riotx.core.extensions.commitTransactionNow +import im.vector.riotx.core.extensions.addFragment +import im.vector.riotx.core.extensions.addFragmentToBackstack import im.vector.riotx.core.platform.ToolbarConfigurable import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment @@ -32,9 +32,6 @@ class RoomProfileActivity : VectorBaseActivity(), ToolbarConfigurable { companion object { private const val EXTRA_ROOM_PROFILE_ARGS = "EXTRA_ROOM_PROFILE_ARGS" - private const val TAG_ROOM_PROFILE_FRAGMENT = "TAG_ROOM_PROFILE_FRAGMENT" - private const val TAG_ROOM_MEMBER_LIST_FRAGMENT = "TAG_ROOM_MEMBER_LIST_FRAGMENT" - fun newIntent(context: Context, roomId: String): Intent { val roomProfileArgs = RoomProfileArgs(roomId) @@ -53,14 +50,7 @@ class RoomProfileActivity : VectorBaseActivity(), ToolbarConfigurable { sharedActionViewModel = viewModelProvider.get(RoomProfileSharedActionViewModel::class.java) roomProfileArgs = intent?.extras?.getParcelable(EXTRA_ROOM_PROFILE_ARGS) ?: return if (isFirstCreation()) { - val argsBundle = roomProfileArgs.toMvRxBundle() - val roomProfileFragment = createFragment(RoomProfileFragment::class.java, argsBundle) - val roomMemberListFragment = createFragment(RoomMemberListFragment::class.java, argsBundle) - supportFragmentManager.commitTransactionNow { - add(R.id.simpleFragmentContainer, roomProfileFragment, TAG_ROOM_PROFILE_FRAGMENT) - add(R.id.simpleFragmentContainer, roomMemberListFragment, TAG_ROOM_MEMBER_LIST_FRAGMENT) - detach(roomMemberListFragment) - } + addFragment(R.id.simpleFragmentContainer, RoomProfileFragment::class.java, roomProfileArgs) } sharedActionViewModel .observe() @@ -83,15 +73,7 @@ class RoomProfileActivity : VectorBaseActivity(), ToolbarConfigurable { } private fun openRoomMembers() { - val roomProfileFragment = supportFragmentManager.findFragmentByTag(TAG_ROOM_PROFILE_FRAGMENT) - ?: throw IllegalStateException("You should have a RoomProfileFragment") - val roomMemberListFragment = supportFragmentManager.findFragmentByTag(TAG_ROOM_MEMBER_LIST_FRAGMENT) - ?: throw IllegalStateException("You should have a RoomMemberListFragment") - supportFragmentManager.commitTransaction { - hide(roomProfileFragment) - attach(roomMemberListFragment) - addToBackStack(null) - } + addFragmentToBackstack(R.id.simpleFragmentContainer, RoomMemberListFragment::class.java, roomProfileArgs) } override fun configure(toolbar: Toolbar) { diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt index b596c9725d..852b0f1f37 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt @@ -19,7 +19,6 @@ package im.vector.riotx.features.roomprofile -import android.app.ProgressDialog import android.os.Bundle import android.os.Parcelable import android.view.View @@ -58,7 +57,6 @@ class RoomProfileFragment @Inject constructor( val roomProfileViewModelFactory: RoomProfileViewModel.Factory ) : VectorBaseFragment(), RoomProfileController.Callback { - private var progress: ProgressDialog? = null private val roomProfileArgs: RoomProfileArgs by args() private lateinit var roomListQuickActionsSharedActionViewModel: RoomListQuickActionsSharedActionViewModel private lateinit var roomProfileSharedActionViewModel: RoomProfileSharedActionViewModel @@ -73,36 +71,32 @@ class RoomProfileFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) roomListQuickActionsSharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) roomProfileSharedActionViewModel = activityViewModelProvider.get(RoomProfileSharedActionViewModel::class.java) - matrixProfileHeaderView.apply { - layoutResource = R.layout.view_stub_room_profile_header - inflate() + val headerView = matrixProfileHeaderView.let { + it.layoutResource = R.layout.view_stub_room_profile_header + it.inflate() } + setupToolbar(matrixProfileToolbar) setupRecyclerView() - appBarStateChangeListener = MatrixItemAppBarStateChangeListener(matrixProfileCollapsingToolbarLayout.scrimAnimationDuration, listOf(matrixProfileToolbarAvatarImageView, matrixProfileToolbarTitleView)) + appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView, listOf(matrixProfileToolbarAvatarImageView, + matrixProfileToolbarTitleView)) matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) roomProfileViewModel.viewEvents .observe() .subscribe { - progress?.dismiss() + dismissLoadingDialog() when (it) { - RoomProfileViewEvents.Loading -> showLoading() + is RoomProfileViewEvents.Loading -> showLoadingDialog(it.message) RoomProfileViewEvents.OnLeaveRoomSuccess -> onLeaveRoom() is RoomProfileViewEvents.Failure -> showError(it.throwable) } } .disposeOnDestroyView() - roomListQuickActionsSharedActionViewModel .observe() .subscribe { handleQuickActions(it) } .disposeOnDestroyView() } - override fun onResume() { - super.onResume() - setupToolbar(matrixProfileToolbar) - } - private fun handleQuickActions(action: RoomListQuickActionsSharedAction) = when (action) { is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> { roomProfileViewModel.handle(RoomProfileAction.ChangeRoomNotificationState(RoomNotificationState.ALL_MESSAGES_NOISY)) @@ -124,15 +118,7 @@ class RoomProfileFragment @Inject constructor( } private fun showError(throwable: Throwable) { - vectorBaseActivity.showSnackbar(errorFormatter.toHumanReadable(throwable)) - } - - private fun showLoading() { - progress = ProgressDialog(requireContext()).apply { - setMessage(getString(R.string.room_profile_leaving_room)) - setProgressStyle(ProgressDialog.STYLE_SPINNER) - show() - } + showErrorInSnackbar(throwable) } private fun setupRecyclerView() { diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewEvents.kt index 4b483d51a4..e100f77b17 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewEvents.kt @@ -19,7 +19,7 @@ package im.vector.riotx.features.roomprofile * Transient events for RoomProfile */ sealed class RoomProfileViewEvents { - object Loading: RoomProfileViewEvents() + data class Loading(val message: CharSequence): RoomProfileViewEvents() object OnLeaveRoomSuccess: RoomProfileViewEvents() data class Failure(val throwable: Throwable) : RoomProfileViewEvents() diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt index 43ed7a5576..6d8db33ea0 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt @@ -26,13 +26,16 @@ import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap +import im.vector.riotx.R import im.vector.riotx.core.platform.VectorViewModel +import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.utils.DataSource import im.vector.riotx.core.utils.PublishDataSource import im.vector.riotx.features.home.room.list.RoomListAction import im.vector.riotx.features.home.room.list.RoomListViewEvents class RoomProfileViewModel @AssistedInject constructor(@Assisted initialState: RoomProfileViewState, + private val stringProvider: StringProvider, private val session: Session) : VectorViewModel(initialState) { @@ -84,7 +87,7 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted initialState: R } private fun handleLeaveRoom() { - _viewEvents.post(RoomProfileViewEvents.Loading) + _viewEvents.post(RoomProfileViewEvents.Loading(stringProvider.getString(R.string.room_profile_leaving_room))) room.leave(null, object : MatrixCallback { override fun onSuccess(data: Unit) { _viewEvents.post(RoomProfileViewEvents.OnLeaveRoomSuccess) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt index b2318259f9..0c18509af0 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt @@ -48,12 +48,8 @@ class RoomMemberListFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) roomMemberListController.callback = this - recyclerView.configureWith(roomMemberListController, hasFixedSize = true) - } - - override fun onResume() { - super.onResume() setupToolbar(roomMemberListToolbar) + recyclerView.configureWith(roomMemberListController, hasFixedSize = true) } override fun onDestroyView() { diff --git a/vector/src/main/res/layout/fragment_matrix_profile.xml b/vector/src/main/res/layout/fragment_matrix_profile.xml index 384c87cccc..7bd6d8bfb0 100644 --- a/vector/src/main/res/layout/fragment_matrix_profile.xml +++ b/vector/src/main/res/layout/fragment_matrix_profile.xml @@ -2,6 +2,7 @@ @@ -19,6 +20,7 @@ android:layout_height="match_parent" app:contentScrim="?riotx_background" app:layout_scrollFlags="scroll|exitUntilCollapsed|snap" + app:scrimVisibleHeightTrigger="80dp" app:scrimAnimationDuration="250" app:titleEnabled="false" app:toolbarId="@+id/matrixProfileToolbar"> @@ -40,6 +42,7 @@ app:layout_collapseMode="pin"> @@ -50,10 +53,10 @@ android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:alpha="0" - tools:alpha="1" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" + tools:alpha="1" tools:src="@tools:sample/avatars" /> diff --git a/vector/src/main/res/layout/item_profile_action.xml b/vector/src/main/res/layout/item_profile_action.xml index 98a9d15ba3..d063025379 100644 --- a/vector/src/main/res/layout/item_profile_action.xml +++ b/vector/src/main/res/layout/item_profile_action.xml @@ -30,7 +30,7 @@ diff --git a/vector/src/main/res/layout/item_profile_matrix_item.xml b/vector/src/main/res/layout/item_profile_matrix_item.xml index aa5d9e44cf..cdca2341e8 100644 --- a/vector/src/main/res/layout/item_profile_matrix_item.xml +++ b/vector/src/main/res/layout/item_profile_matrix_item.xml @@ -27,7 +27,7 @@ diff --git a/vector/src/main/res/layout/view_stub_room_member_profile_header.xml b/vector/src/main/res/layout/view_stub_room_member_profile_header.xml index af4eb42790..82bf0e7d7b 100644 --- a/vector/src/main/res/layout/view_stub_room_member_profile_header.xml +++ b/vector/src/main/res/layout/view_stub_room_member_profile_header.xml @@ -1,70 +1,75 @@ - + android:padding="16dp"> - - - - + android:orientation="vertical"> - + - + - + - + + + + + + \ 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 4fa6c5bbd4..e8cc1c7360 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -47,5 +47,7 @@ Jump to read receipt + Unignore +