Profile: handle ignore/unignore action + adjust UI

This commit is contained in:
ganfra 2020-01-14 17:08:21 +01:00
parent 162f0949fa
commit df4df81ef3
20 changed files with 352 additions and 247 deletions

View File

@ -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<View>) : AppBarStateChangeListener() {
class MatrixItemAppBarStateChangeListener(private val headerView: View, private val toolbarViews: List<View>) : 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
}
}
}

View File

@ -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

View File

@ -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
* ========================================================================================== */

View File

@ -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<T> {
@ -37,7 +38,7 @@ open class BehaviorDataSource<T>(private val defaultValue: T? = null) : MutableD
private val behaviorRelay = createRelay()
override fun observe(): Observable<T> {
return behaviorRelay.hide().observeOn(Schedulers.computation())
return behaviorRelay.hide().observeOn(AndroidSchedulers.mainThread())
}
override fun post(value: T) {
@ -61,7 +62,7 @@ open class PublishDataSource<T> : MutableDataSource<T> {
private val publishRelay = PublishRelay.create<T>()
override fun observe(): Observable<T> {
return publishRelay.hide()
return publishRelay.hide().observeOn(AndroidSchedulers.mainThread())
}
override fun post(value: T) {

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -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() {

View File

@ -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()
}

View File

@ -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<RoomMemberProfileViewState, RoomMemberProfileAction>(initialState) {
@ -53,6 +71,9 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
}
}
private val _viewEvents = PublishDataSource<RoomMemberProfileViewEvents>()
val viewEvents: DataSource<RoomMemberProfileViewEvents> = _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<PowerLevelsContent>() }
.unwrap()
.execute {
copy(powerLevelsContent = it)
roomSummaryLive.execute {
copy(isRoomEncrypted = it.invoke()?.isEncrypted == true)
}
Observable
.combineLatest(
roomSummaryLive,
powerLevelsContentLive,
BiFunction<RoomSummary, PowerLevelsContent, String> { 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<Unit> {
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)
}
}
}

View File

@ -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<RoomSummary> = Uninitialized,
val roomMemberSummary: Async<RoomMemberSummary> = Uninitialized,
val profileInfo: Async<ProfileInfo> = Uninitialized,
val powerLevelsContent: Async<PowerLevelsContent> = Uninitialized
val isIgnored: Async<Boolean> = Uninitialized,
val isRoomEncrypted: Boolean = false,
val userPowerLevelString: Async<String> = Uninitialized,
val userMatrixItem: Async<MatrixItem> = 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)
}
}
}

View File

@ -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) {

View File

@ -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() {

View File

@ -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()

View File

@ -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<RoomProfileViewState, RoomProfileAction>(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<Unit> {
override fun onSuccess(data: Unit) {
_viewEvents.post(RoomProfileViewEvents.OnLeaveRoomSuccess)

View File

@ -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() {

View File

@ -2,6 +2,7 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/vector_coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_header_panel_background">
@ -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">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/matrixProfileToolbarContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -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" />
<TextView
@ -62,9 +65,8 @@
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:alpha="0"
tools:alpha="1"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?vctr_toolbar_primary_text_color"
android:textSize="18sp"
@ -72,6 +74,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/matrixProfileToolbarAvatarImageView"
app:layout_constraintTop_toTopOf="parent"
tools:alpha="1"
tools:text="@sample/matrix.json/data/roomName" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -30,7 +30,7 @@
<TextView
android:id="@+id/actionTitle"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
@ -44,13 +44,13 @@
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/actionIcon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="wrap"
app:layout_constrainedWidth="true"
app:layout_goneMarginStart="0dp"
tools:text="Learn more" />
<TextView
android:id="@+id/actionSubtitle"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
@ -64,7 +64,7 @@
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/actionIcon"
app:layout_constraintTop_toBottomOf="@id/actionTitle"
app:layout_constraintWidth_default="wrap"
app:layout_constrainedWidth="true"
app:layout_goneMarginStart="0dp"
tools:text="Messages in this room are not end-to-end encrypted" />

View File

@ -27,7 +27,7 @@
<TextView
android:id="@+id/matrixItemTitle"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
@ -41,13 +41,13 @@
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/matrixItemAvatar"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="wrap"
app:layout_constrainedWidth="true"
app:layout_goneMarginStart="0dp"
tools:text="@sample/matrix.json/data/displayName" />
<TextView
android:id="@+id/matrixItemSubtitle"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
@ -61,7 +61,7 @@
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/matrixItemAvatar"
app:layout_constraintTop_toBottomOf="@id/matrixItemTitle"
app:layout_constraintWidth_default="wrap"
app:layout_constrainedWidth="true"
app:layout_goneMarginStart="0dp"
tools:text="@sample/matrix.json/data/mxid" />

View File

@ -1,70 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<im.vector.riotx.core.platform.StateView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/matrixProfileHeaderView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/memberProfileStateView"
android:background="?riotx_background"
android:padding="16dp"
android:orientation="vertical">
android:padding="16dp">
<ImageView
android:id="@+id/memberProfileAvatarView"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="16dp"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/memberProfileNameView"
android:layout_width="wrap_content"
<LinearLayout
android:id="@+id/memberProfileInfoContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_vertical"
android:singleLine="true"
android:textAppearance="@style/Vector.Toolbar.Title"
android:textSize="20sp"
android:textStyle="bold"
tools:text="@sample/matrix.json/data/displayName" />
android:orientation="vertical">
<TextView
android:id="@+id/memberProfileIdView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:singleLine="true"
android:textStyle="bold"
android:textAppearance="@style/Vector.Toolbar.Title"
android:textSize="14sp"
tools:text="@sample/matrix.json/data/mxid" />
<ImageView
android:id="@+id/memberProfileAvatarView"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="16dp"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/memberProfilePowerLevelView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginStart="40dp"
android:layout_marginEnd="40dp"
android:layout_marginBottom="16dp"
android:gravity="center"
android:textSize="12sp"
tools:text="Admin in Matrix" />
<TextView
android:id="@+id/memberProfileNameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_vertical"
android:singleLine="true"
android:textAppearance="@style/Vector.Toolbar.Title"
android:textSize="20sp"
android:textStyle="bold"
tools:text="@sample/matrix.json/data/displayName" />
<TextView
android:id="@+id/memberProfileStatusView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginStart="40dp"
android:layout_marginEnd="40dp"
android:gravity="center"
android:textSize="14sp"
android:visibility="gone"
tools:text="Here is a room topic, it can be multi-line but should always be displayed in full 🍱" />
<TextView
android:id="@+id/memberProfileIdView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:singleLine="true"
android:textAppearance="@style/Vector.Toolbar.Title"
android:textSize="14sp"
android:textStyle="bold"
tools:text="@sample/matrix.json/data/mxid" />
</LinearLayout>
<TextView
android:id="@+id/memberProfilePowerLevelView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginStart="40dp"
android:layout_marginEnd="40dp"
android:layout_marginBottom="16dp"
android:gravity="center"
android:textSize="12sp"
tools:text="Admin in Matrix" />
<TextView
android:id="@+id/memberProfileStatusView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginStart="40dp"
android:layout_marginEnd="40dp"
android:gravity="center"
android:textSize="14sp"
android:visibility="gone"
tools:text="Here is a room topic, it can be multi-line but should always be displayed in full 🍱" />
</LinearLayout>
</im.vector.riotx.core.platform.StateView>

View File

@ -47,5 +47,7 @@
<string name="room_member_jump_to_read_receipt">Jump to read receipt</string>
<string name="unignore">Unignore</string>
</resources>