Merge pull request #3279 from vector-im/feature/bca/space_beta_people

Spaces | beta Browsing Member Directory In Space
This commit is contained in:
Benoit Marty 2021-05-06 10:38:40 +02:00 committed by GitHub
commit 096e95f9da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 926 additions and 36 deletions

View File

@ -295,6 +295,7 @@
<activity android:name=".features.spaces.SpaceExploreActivity" /> <activity android:name=".features.spaces.SpaceExploreActivity" />
<activity android:name=".features.spaces.SpaceCreationActivity" /> <activity android:name=".features.spaces.SpaceCreationActivity" />
<activity android:name=".features.spaces.manage.SpaceManageActivity" /> <activity android:name=".features.spaces.manage.SpaceManageActivity" />
<activity android:name=".features.spaces.people.SpacePeopleActivity" />
<!-- Services --> <!-- Services -->
<service <service

View File

@ -125,6 +125,7 @@ import im.vector.app.features.spaces.create.CreateSpaceDefaultRoomsFragment
import im.vector.app.features.spaces.create.CreateSpaceDetailsFragment import im.vector.app.features.spaces.create.CreateSpaceDetailsFragment
import im.vector.app.features.spaces.explore.SpaceDirectoryFragment import im.vector.app.features.spaces.explore.SpaceDirectoryFragment
import im.vector.app.features.spaces.manage.SpaceAddRoomFragment import im.vector.app.features.spaces.manage.SpaceAddRoomFragment
import im.vector.app.features.spaces.people.SpacePeopleFragment
import im.vector.app.features.spaces.preview.SpacePreviewFragment import im.vector.app.features.spaces.preview.SpacePreviewFragment
import im.vector.app.features.terms.ReviewTermsFragment import im.vector.app.features.terms.ReviewTermsFragment
import im.vector.app.features.usercode.ShowUserCodeFragment import im.vector.app.features.usercode.ShowUserCodeFragment
@ -678,4 +679,9 @@ interface FragmentModule {
@IntoMap @IntoMap
@FragmentKey(SpaceAddRoomFragment::class) @FragmentKey(SpaceAddRoomFragment::class)
fun bindSpaceAddRoomFragment(fragment: SpaceAddRoomFragment): Fragment fun bindSpaceAddRoomFragment(fragment: SpaceAddRoomFragment): Fragment
@Binds
@IntoMap
@FragmentKey(SpacePeopleFragment::class)
fun bindSpacePeopleFragment(fragment: SpacePeopleFragment): Fragment
} }

View File

@ -39,6 +39,7 @@ import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetShare
import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilitySharedActionViewModel import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilitySharedActionViewModel
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel
import im.vector.app.features.spaces.SpacePreviewSharedActionViewModel import im.vector.app.features.spaces.SpacePreviewSharedActionViewModel
import im.vector.app.features.spaces.people.SpacePeopleSharedActionViewModel
import im.vector.app.features.userdirectory.UserListSharedActionViewModel import im.vector.app.features.userdirectory.UserListSharedActionViewModel
@Module @Module
@ -148,4 +149,9 @@ interface ViewModelModule {
@IntoMap @IntoMap
@ViewModelKey(SpacePreviewSharedActionViewModel::class) @ViewModelKey(SpacePreviewSharedActionViewModel::class)
fun bindSpacePreviewSharedActionViewModel(viewModel: SpacePreviewSharedActionViewModel): ViewModel fun bindSpacePreviewSharedActionViewModel(viewModel: SpacePreviewSharedActionViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(SpacePeopleSharedActionViewModel::class)
fun bindSpacePeopleSharedActionViewModel(viewModel: SpacePeopleSharedActionViewModel): ViewModel
} }

View File

@ -17,6 +17,7 @@
package im.vector.app.core.epoxy.profiles package im.vector.app.core.epoxy.profiles
import android.view.View import android.view.View
import androidx.annotation.CallSuper
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.VectorEpoxyModel
@ -34,6 +35,7 @@ abstract class BaseProfileMatrixItem<T : ProfileMatrixItem.Holder> : VectorEpoxy
var userEncryptionTrustLevel: RoomEncryptionTrustLevel? = null var userEncryptionTrustLevel: RoomEncryptionTrustLevel? = null
@EpoxyAttribute var clickListener: View.OnClickListener? = null @EpoxyAttribute var clickListener: View.OnClickListener? = null
@CallSuper
override fun bind(holder: T) { override fun bind(holder: T) {
super.bind(holder) super.bind(holder)
val bestName = matrixItem.getBestName() val bestName = matrixItem.getBestName()

View File

@ -0,0 +1,41 @@
/*
* 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.app.core.epoxy.profiles
import android.widget.TextView
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.extensions.setTextOrHide
@EpoxyModelClass(layout = R.layout.item_profile_matrix_item)
abstract class ProfileMatrixItemWithPowerLevel : BaseProfileMatrixItem<ProfileMatrixItemWithPowerLevel.Holder>() {
@EpoxyAttribute var powerLevelLabel: CharSequence? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.editableView.isVisible = false
holder.powerLabel.setTextOrHide(powerLevelLabel)
}
class Holder : ProfileMatrixItem.Holder() {
val powerLabel by bind<TextView>(R.id.matrixItemPowerLevelLabel)
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 2021 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.app.core.platform
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
/**
* Generic argument with one String. Can be an id (ex: roomId, spaceId, callId, etc.), or anything else
*/
@Parcelize
data class GenericIdArgs(
val id: String
) : Parcelable

View File

@ -75,6 +75,7 @@ import im.vector.app.features.spaces.InviteRoomSpaceChooserBottomSheet
import im.vector.app.features.spaces.SpaceExploreActivity import im.vector.app.features.spaces.SpaceExploreActivity
import im.vector.app.features.spaces.SpacePreviewActivity import im.vector.app.features.spaces.SpacePreviewActivity
import im.vector.app.features.spaces.manage.SpaceManageActivity import im.vector.app.features.spaces.manage.SpaceManageActivity
import im.vector.app.features.spaces.people.SpacePeopleActivity
import im.vector.app.features.terms.ReviewTermsActivity import im.vector.app.features.terms.ReviewTermsActivity
import im.vector.app.features.widgets.WidgetActivity import im.vector.app.features.widgets.WidgetActivity
import im.vector.app.features.widgets.WidgetArgsBuilder import im.vector.app.features.widgets.WidgetArgsBuilder
@ -283,7 +284,19 @@ class DefaultNavigator @Inject constructor(
} }
override fun openCreateDirectRoom(context: Context) { override fun openCreateDirectRoom(context: Context) {
val intent = CreateDirectRoomActivity.getIntent(context) val intent = when (val currentGroupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) {
is RoomGroupingMethod.ByLegacyGroup -> {
CreateDirectRoomActivity.getIntent(context)
}
is RoomGroupingMethod.BySpace -> {
if (currentGroupingMethod.spaceSummary != null) {
SpacePeopleActivity.newIntent(context, currentGroupingMethod.spaceSummary.roomId)
} else {
CreateDirectRoomActivity.getIntent(context)
}
}
else -> null
} ?: return
context.startActivity(intent) context.startActivity(intent)
} }

View File

@ -47,11 +47,16 @@ class RoomMemberListFragment @Inject constructor(
private val roomMemberListController: RoomMemberListController, private val roomMemberListController: RoomMemberListController,
private val avatarRenderer: AvatarRenderer private val avatarRenderer: AvatarRenderer
) : VectorBaseFragment<FragmentRoomMemberListBinding>(), ) : VectorBaseFragment<FragmentRoomMemberListBinding>(),
RoomMemberListController.Callback { RoomMemberListController.Callback,
RoomMemberListViewModel.Factory {
private val viewModel: RoomMemberListViewModel by fragmentViewModel() private val viewModel: RoomMemberListViewModel by fragmentViewModel()
private val roomProfileArgs: RoomProfileArgs by args() private val roomProfileArgs: RoomProfileArgs by args()
override fun create(initialState: RoomMemberListViewState): RoomMemberListViewModel {
return viewModelFactory.create(initialState)
}
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomMemberListBinding { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomMemberListBinding {
return FragmentRoomMemberListBinding.inflate(inflater, container, false) return FragmentRoomMemberListBinding.inflate(inflater, container, false)
} }

View File

@ -17,12 +17,13 @@
package im.vector.app.features.roomprofile.members package im.vector.app.features.roomprofile.members
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
@ -62,8 +63,11 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
@JvmStatic @JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomMemberListViewState): RoomMemberListViewModel? { override fun create(viewModelContext: ViewModelContext, state: RoomMemberListViewState): RoomMemberListViewModel? {
val fragment: RoomMemberListFragment = (viewModelContext as FragmentViewModelContext).fragment() val factory = when (viewModelContext) {
return fragment.viewModelFactory.create(state) is FragmentViewModelContext -> viewModelContext.fragment as? Factory
is ActivityViewModelContext -> viewModelContext.activity as? Factory
}
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
} }
} }
@ -188,7 +192,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
override fun handle(action: RoomMemberListAction) { override fun handle(action: RoomMemberListAction) {
when (action) { when (action) {
is RoomMemberListAction.RevokeThreePidInvite -> handleRevokeThreePidInvite(action) is RoomMemberListAction.RevokeThreePidInvite -> handleRevokeThreePidInvite(action)
is RoomMemberListAction.FilterMemberList -> handleFilterMemberList(action) is RoomMemberListAction.FilterMemberList -> handleFilterMemberList(action)
}.exhaustive }.exhaustive
} }

View File

@ -21,6 +21,7 @@ import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.platform.GenericIdArgs
import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.RoomProfileArgs
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
@ -38,6 +39,8 @@ data class RoomMemberListViewState(
) : MvRxState { ) : MvRxState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId) constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
constructor(args: GenericIdArgs) : this(roomId = args.id)
} }
data class ActionPermissions( data class ActionPermissions(

View File

@ -29,9 +29,11 @@ class RoomMemberSummaryFilter @Inject constructor() : Predicate<RoomMemberSummar
// No filter // No filter
return true return true
} }
// if filter is "Jo Do", it should match "John Doe"
return roomMemberSummary.displayName?.contains(filter, ignoreCase = true).orFalse() return filter.split(" ").all {
// We should maybe exclude the domain from the userId roomMemberSummary.displayName?.contains(it, ignoreCase = true).orFalse()
|| roomMemberSummary.userId.contains(filter, ignoreCase = true) // We should maybe exclude the domain from the userId
|| roomMemberSummary.userId.contains(it, ignoreCase = true)
}
} }
} }

View File

@ -0,0 +1,104 @@
/*
* Copyright (c) 2021 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.app.features.spaces.people
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.airbnb.mvrx.MvRx
import im.vector.app.R
import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.platform.GenericIdArgs
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleLoadingBinding
import im.vector.app.features.spaces.ShareSpaceBottomSheet
class SpacePeopleActivity : VectorBaseActivity<ActivitySimpleLoadingBinding>() {
override fun getBinding() = ActivitySimpleLoadingBinding.inflate(layoutInflater)
private lateinit var sharedActionViewModel: SpacePeopleSharedActionViewModel
override fun initUiAndData() {
super.initUiAndData()
waitingView = views.waitingView.waitingView
}
override fun showWaitingView(text: String?) {
hideKeyboard()
views.waitingView.waitingStatusText.isGone = views.waitingView.waitingStatusText.text.isNullOrBlank()
super.showWaitingView(text)
}
override fun hideWaitingView() {
views.waitingView.waitingStatusText.text = null
views.waitingView.waitingStatusText.isGone = true
views.waitingView.waitingHorizontalProgress.progress = 0
views.waitingView.waitingHorizontalProgress.isVisible = false
super.hideWaitingView()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val args = intent?.getParcelableExtra<GenericIdArgs>(MvRx.KEY_ARG)
if (isFirstCreation()) {
val simpleName = SpacePeopleFragment::class.java.simpleName
if (supportFragmentManager.findFragmentByTag(simpleName) == null) {
supportFragmentManager.commitTransaction {
replace(R.id.simpleFragmentContainer,
SpacePeopleFragment::class.java,
Bundle().apply { this.putParcelable(MvRx.KEY_ARG, args) },
simpleName
)
}
}
}
sharedActionViewModel = viewModelProvider.get(SpacePeopleSharedActionViewModel::class.java)
sharedActionViewModel
.observe()
.subscribe { sharedAction ->
when (sharedAction) {
SpacePeopleSharedAction.Dismiss -> finish()
is SpacePeopleSharedAction.NavigateToRoom -> navigateToRooms(sharedAction)
SpacePeopleSharedAction.HideModalLoading -> hideWaitingView()
SpacePeopleSharedAction.ShowModalLoading -> {
showWaitingView()
}
is SpacePeopleSharedAction.NavigateToInvite -> {
ShareSpaceBottomSheet.show(supportFragmentManager, sharedAction.spaceId)
}
}
}.disposeOnDestroy()
}
private fun navigateToRooms(action: SpacePeopleSharedAction.NavigateToRoom) {
navigator.openRoom(this, action.roomId)
finish()
}
companion object {
fun newIntent(context: Context, spaceId: String): Intent {
return Intent(context, SpacePeopleActivity::class.java).apply {
putExtra(MvRx.KEY_ARG, GenericIdArgs(spaceId))
}
}
}
}

View File

@ -0,0 +1,158 @@
/*
* Copyright (c) 2021 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.app.features.spaces.people
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.appcompat.queryTextChanges
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.DrawableProvider
import im.vector.app.databinding.FragmentRecyclerviewWithSearchBinding
import im.vector.app.features.roomprofile.members.RoomMemberListAction
import im.vector.app.features.roomprofile.members.RoomMemberListViewModel
import im.vector.app.features.roomprofile.members.RoomMemberListViewState
import io.reactivex.rxkotlin.subscribeBy
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class SpacePeopleFragment @Inject constructor(
private val viewModelFactory: SpacePeopleViewModel.Factory,
private val roomMemberModelFactory: RoomMemberListViewModel.Factory,
private val drawableProvider: DrawableProvider,
private val colorProvider: ColorProvider,
private val epoxyController: SpacePeopleListController
) : VectorBaseFragment<FragmentRecyclerviewWithSearchBinding>(),
SpacePeopleViewModel.Factory,
RoomMemberListViewModel.Factory,
OnBackPressed, SpacePeopleListController.InteractionListener {
private val viewModel by fragmentViewModel(SpacePeopleViewModel::class)
private val membersViewModel by fragmentViewModel(RoomMemberListViewModel::class)
private lateinit var sharedActionViewModel: SpacePeopleSharedActionViewModel
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
FragmentRecyclerviewWithSearchBinding.inflate(inflater, container, false)
override fun onBackPressed(toolbarButton: Boolean): Boolean {
sharedActionViewModel.post(SpacePeopleSharedAction.Dismiss)
return true
}
override fun create(initialState: SpacePeopleViewState): SpacePeopleViewModel {
return viewModelFactory.create(initialState)
}
override fun create(initialState: RoomMemberListViewState): RoomMemberListViewModel {
return roomMemberModelFactory.create(initialState)
}
override fun invalidate() = withState(membersViewModel) { memberListState ->
views.appBarTitle.text = getString(R.string.bottom_action_people)
val memberCount = (memberListState.roomSummary.invoke()?.otherMemberIds?.size ?: 0) + 1
views.appBarSpaceInfo.text = resources.getQuantityString(R.plurals.room_title_members, memberCount, memberCount)
// views.listBuildingProgress.isVisible = true
epoxyController.setData(memberListState)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(SpacePeopleSharedActionViewModel::class.java)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
setupSearchView()
views.addRoomToSpaceToolbar.navigationIcon = drawableProvider.getDrawable(
R.drawable.ic_close_24dp,
colorProvider.getColorFromAttribute(R.attr.riot_primary_text_color)
)
views.addRoomToSpaceToolbar.setNavigationOnClickListener {
sharedActionViewModel.post(SpacePeopleSharedAction.Dismiss)
}
viewModel.observeViewEvents {
handleViewEvents(it)
}
viewModel.subscribe(this) {
when (it.createAndInviteState) {
is Loading -> sharedActionViewModel.post(SpacePeopleSharedAction.ShowModalLoading)
Uninitialized,
is Fail -> sharedActionViewModel.post(SpacePeopleSharedAction.HideModalLoading)
is Success -> {
// don't hide on success, it will navigate out. If not the loading goes out before navigation
}
}
}
}
override fun onDestroyView() {
epoxyController.listener = null
views.roomList.cleanup()
super.onDestroyView()
}
private fun setupRecyclerView() {
views.roomList.configureWith(epoxyController, hasFixedSize = false, disableItemAnimation = false)
epoxyController.listener = this
}
private fun setupSearchView() {
views.memberNameFilter.queryHint = getString(R.string.search_members_hint)
views.memberNameFilter.queryTextChanges()
.debounce(100, TimeUnit.MILLISECONDS)
.subscribeBy {
membersViewModel.handle(RoomMemberListAction.FilterMemberList(it.toString()))
}
.disposeOnDestroyView()
}
private fun handleViewEvents(events: SpacePeopleViewEvents) {
when (events) {
is SpacePeopleViewEvents.OpenRoom -> {
sharedActionViewModel.post(SpacePeopleSharedAction.NavigateToRoom(events.roomId))
}
is SpacePeopleViewEvents.InviteToSpace -> {
sharedActionViewModel.post(SpacePeopleSharedAction.NavigateToInvite(events.spaceId))
}
}
}
override fun onSpaceMemberClicked(roomMemberSummary: RoomMemberSummary) {
viewModel.handle(SpacePeopleViewAction.ChatWith(roomMemberSummary))
}
override fun onInviteToSpaceSelected() {
viewModel.handle(SpacePeopleViewAction.InviteToSpace)
}
}

View File

@ -0,0 +1,167 @@
/*
* Copyright (c) 2021 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.app.features.spaces.people
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.app.R
import im.vector.app.core.epoxy.dividerItem
import im.vector.app.core.epoxy.loadingItem
import im.vector.app.core.epoxy.profiles.profileMatrixItemWithPowerLevel
import im.vector.app.core.extensions.join
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.GenericItem
import im.vector.app.core.ui.list.genericItem
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.roomprofile.members.RoomMemberListCategories
import im.vector.app.features.roomprofile.members.RoomMemberListViewState
import im.vector.app.features.roomprofile.members.RoomMemberSummaryFilter
import me.gujun.android.span.span
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
class SpacePeopleListController @Inject constructor(
private val avatarRenderer: AvatarRenderer,
private val colorProvider: ColorProvider,
private val stringProvider: StringProvider,
private val dimensionConverter: DimensionConverter,
private val roomMemberSummaryFilter: RoomMemberSummaryFilter
) : TypedEpoxyController<RoomMemberListViewState>() {
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
interface InteractionListener {
fun onSpaceMemberClicked(roomMemberSummary: RoomMemberSummary)
fun onInviteToSpaceSelected()
}
var listener: InteractionListener? = null
init {
setData(null)
}
override fun buildModels(data: RoomMemberListViewState?) {
val memberSummaries = data?.roomMemberSummaries?.invoke()
if (memberSummaries == null) {
loadingItem { id("loading") }
return
}
roomMemberSummaryFilter.filter = data.filter
var foundCount = 0
memberSummaries.forEach { memberEntry ->
val filtered = memberEntry.second
.filter { roomMemberSummaryFilter.test(it) }
if (filtered.isNotEmpty()) {
dividerItem {
id("divider_type_${memberEntry.first.titleRes}")
color(dividerColor)
}
}
foundCount += filtered.size
filtered
.join(
each = { _, roomMember ->
profileMatrixItemWithPowerLevel {
id(roomMember.userId)
matrixItem(roomMember.toMatrixItem())
avatarRenderer(avatarRenderer)
userEncryptionTrustLevel(data.trustLevelMap.invoke()?.get(roomMember.userId))
.apply {
val pl = memberEntry.first.toPowerLevelLabel()
if (memberEntry.first == RoomMemberListCategories.INVITE) {
powerLevelLabel(
span {
span(stringProvider.getString(R.string.invited)) {
textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary)
textStyle = "bold"
// fontFamily = "monospace"
}
}
)
} else if (pl != null) {
powerLevelLabel(
span {
span(" $pl ") {
backgroundColor = colorProvider.getColor(R.color.notification_accent_color)
paddingTop = dimensionConverter.dpToPx(2)
paddingBottom = dimensionConverter.dpToPx(2)
textColor = colorProvider.getColor(R.color.white)
textStyle = "bold"
// fontFamily = "monospace"
}
}
)
} else {
powerLevelLabel(null)
}
}
clickListener { _ ->
listener?.onSpaceMemberClicked(roomMember)
}
}
},
between = { _, roomMemberBefore ->
dividerItem {
id("divider_${roomMemberBefore.userId}")
color(dividerColor)
}
}
)
}
if (foundCount == 0 && data.filter.isNotEmpty()) {
// add the footer thing
genericItem {
id("not_found")
title(
span {
+"\n"
+stringProvider.getString(R.string.no_result_placeholder)
}
)
description(
span {
+stringProvider.getString(R.string.looking_for_someone_not_in_space, data.roomSummary.invoke()?.displayName ?: "")
+"\n"
span("Invite them") {
textColor = colorProvider.getColorFromAttribute(R.attr.colorAccent)
textStyle = "bold"
}
}
)
itemClickAction(GenericItem.Action("invite").apply {
perform = Runnable {
listener?.onInviteToSpaceSelected()
}
})
}
}
}
private fun RoomMemberListCategories.toPowerLevelLabel(): String? {
return when (this) {
RoomMemberListCategories.ADMIN -> stringProvider.getString(R.string.power_level_admin)
RoomMemberListCategories.MODERATOR -> stringProvider.getString(R.string.power_level_moderator)
else -> null
}
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2021 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.app.features.spaces.people
import im.vector.app.core.platform.VectorSharedAction
sealed class SpacePeopleSharedAction : VectorSharedAction {
object Dismiss : SpacePeopleSharedAction()
object ShowModalLoading : SpacePeopleSharedAction()
object HideModalLoading : SpacePeopleSharedAction()
data class NavigateToRoom(val roomId: String) : SpacePeopleSharedAction()
data class NavigateToInvite(val spaceId: String) : SpacePeopleSharedAction()
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (c) 2021 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.app.features.spaces.people
import im.vector.app.core.platform.VectorSharedActionViewModel
import javax.inject.Inject
class SpacePeopleSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<SpacePeopleSharedAction>()

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2021 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.app.features.spaces.people
import im.vector.app.core.platform.VectorViewModelAction
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
sealed class SpacePeopleViewAction : VectorViewModelAction {
data class ChatWith(val member: RoomMemberSummary) : SpacePeopleViewAction()
object InviteToSpace : SpacePeopleViewAction()
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (c) 2021 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.app.features.spaces.people
import im.vector.app.core.platform.VectorViewEvents
sealed class SpacePeopleViewEvents : VectorViewEvents {
data class OpenRoom(val roomId: String) : SpacePeopleViewEvents()
data class InviteToSpace(val spaceId: String) : SpacePeopleViewEvents()
}

View File

@ -0,0 +1,104 @@
/*
* Copyright (c) 2021 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.app.features.spaces.people
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.raw.wellknown.getElementWellknown
import im.vector.app.features.raw.wellknown.isE2EByDefault
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
class SpacePeopleViewModel @AssistedInject constructor(
@Assisted val initialState: SpacePeopleViewState,
private val rawService: RawService,
private val session: Session
) : VectorViewModel<SpacePeopleViewState, SpacePeopleViewAction, SpacePeopleViewEvents>(initialState) {
@AssistedFactory
interface Factory {
fun create(initialState: SpacePeopleViewState): SpacePeopleViewModel
}
companion object : MvRxViewModelFactory<SpacePeopleViewModel, SpacePeopleViewState> {
override fun create(viewModelContext: ViewModelContext, state: SpacePeopleViewState): SpacePeopleViewModel? {
val factory = when (viewModelContext) {
is FragmentViewModelContext -> viewModelContext.fragment as? Factory
is ActivityViewModelContext -> viewModelContext.activity as? Factory
}
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
}
}
override fun handle(action: SpacePeopleViewAction) {
when (action) {
is SpacePeopleViewAction.ChatWith -> handleChatWith(action)
SpacePeopleViewAction.InviteToSpace -> handleInviteToSpace()
}.exhaustive
}
private fun handleInviteToSpace() {
_viewEvents.post(SpacePeopleViewEvents.InviteToSpace(initialState.spaceId))
}
private fun handleChatWith(action: SpacePeopleViewAction.ChatWith) {
val otherUserId = action.member.userId
if (otherUserId == session.myUserId) return
val existingRoomId = session.getExistingDirectRoomWithUser(otherUserId)
if (existingRoomId != null) {
// just open it
_viewEvents.post(SpacePeopleViewEvents.OpenRoom(existingRoomId))
return
}
setState { copy(createAndInviteState = Loading()) }
viewModelScope.launch(Dispatchers.IO) {
val adminE2EByDefault = rawService.getElementWellknown(session.myUserId)
?.isE2EByDefault()
?: true
val roomParams = CreateRoomParams()
.apply {
invitedUserIds.add(otherUserId)
setDirectMessage()
enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault
}
try {
val roomId = session.createRoom(roomParams)
_viewEvents.post(SpacePeopleViewEvents.OpenRoom(roomId))
setState { copy(createAndInviteState = Success(roomId)) }
} catch (failure: Throwable) {
setState { copy(createAndInviteState = Fail(failure)) }
}
}
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2021 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.app.features.spaces.people
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.app.core.platform.GenericIdArgs
data class SpacePeopleViewState(
val spaceId: String,
val createAndInviteState: Async<String> = Uninitialized
) : MvRxState {
constructor(args: GenericIdArgs) : this(
spaceId = args.id
)
}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/simpleFragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<include
android:id="@+id/waiting_view"
layout="@layout/merge_overlay_waiting_view" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<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/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_background">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/roomList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_background"
android:overScrollMode="always"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/item_profile_matrix_item" />
<com.google.android.material.appbar.AppBarLayout
style="@style/VectorAppBarLayoutStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="4dp">
<!-- minHeight="0dp" is important to collapse on scroll -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/addRoomToSpaceToolbar"
style="@style/VectorToolbarStyle"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:elevation="4dp"
android:minHeight="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:orientation="vertical">
<TextView
android:id="@+id/appBarTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:gravity="start|center"
android:maxLines="1"
android:textColor="?riotx_text_primary"
android:textSize="18sp"
android:textStyle="bold"
tools:text="@tools:sample/lorem/random" />
<TextView
android:id="@+id/appBarSpaceInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:gravity="start|center"
android:maxLines="1"
android:textColor="?riotx_text_secondary"
android:textSize="16sp"
tools:text="@tools:sample/lorem/random" />
</LinearLayout>
</androidx.appcompat.widget.Toolbar>
<androidx.appcompat.widget.SearchView
android:id="@+id/memberNameFilter"
style="@style/VectorSearchView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/addRoomToSpaceToolbar"
app:queryHint="@string/search_hint_room_name" />
<!-- <ProgressBar-->
<!-- android:id="@+id/listBuildingProgress"-->
<!-- android:indeterminate="true"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="8dp"-->
<!-- style="@style/Widget.AppCompat.ProgressBar.Horizontal"-->
<!-- />-->
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -37,44 +37,53 @@
<TextView <TextView
android:id="@+id/matrixItemTitle" android:id="@+id/matrixItemTitle"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="8dp"
android:drawablePadding="16dp"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textColor="?riotx_text_primary" android:textColor="?riotx_text_primary"
android:textSize="16sp" android:textSize="16sp"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/matrixItemSubtitle" app:layout_constraintBottom_toTopOf="@+id/matrixItemSubtitle"
app:layout_constraintEnd_toStartOf="@+id/matrixItemEditable" app:layout_constraintEnd_toStartOf="@+id/matrixItemPowerLevelLabel"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/matrixItemAvatar" app:layout_constraintStart_toEndOf="@id/matrixItemAvatar"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginStart="0dp" app:layout_goneMarginEnd="80dp"
tools:text="@sample/matrix.json/data/displayName" /> tools:text="@sample/matrix.json/data/displayName" />
<TextView <TextView
android:id="@+id/matrixItemSubtitle" android:id="@+id/matrixItemSubtitle"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="8dp"
android:drawablePadding="16dp"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textColor="?riotx_text_secondary" android:textColor="?riotx_text_secondary"
android:textSize="12sp" android:textSize="12sp"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/matrixItemEditable" app:layout_constraintEnd_toStartOf="@+id/matrixItemPowerLevelLabel"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/matrixItemAvatar" app:layout_constraintStart_toEndOf="@id/matrixItemAvatar"
app:layout_constraintTop_toBottomOf="@id/matrixItemTitle" app:layout_constraintTop_toBottomOf="@id/matrixItemTitle"
app:layout_goneMarginStart="0dp" app:layout_goneMarginEnd="8dp"
tools:text="@sample/matrix.json/data/mxid" /> tools:text="@sample/matrix.json/data/mxid" />
<TextView
android:id="@+id/matrixItemPowerLevelLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:textSize="12sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/matrixItemEditable"
app:layout_constraintTop_toTopOf="parent"
tools:text="Admin"
tools:visibility="visible" />
<ImageView <ImageView
android:id="@+id/matrixItemEditable" android:id="@+id/matrixItemEditable"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -87,5 +96,4 @@
app:tint="?riotx_text_secondary" app:tint="?riotx_text_secondary"
tools:ignore="MissingPrefix" /> tools:ignore="MissingPrefix" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -37,11 +37,10 @@
<TextView <TextView
android:id="@+id/matrixItemTitle" android:id="@+id/matrixItemTitle"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="8dp"
android:drawablePadding="16dp"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textColor="?riotx_text_primary" android:textColor="?riotx_text_primary"
@ -49,19 +48,17 @@
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/matrixItemSubtitle" app:layout_constraintBottom_toTopOf="@+id/matrixItemSubtitle"
app:layout_constraintEnd_toStartOf="@+id/matrixItemProgress" app:layout_constraintEnd_toStartOf="@+id/matrixItemProgress"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/matrixItemAvatar" app:layout_constraintStart_toEndOf="@id/matrixItemAvatar"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginStart="0dp" app:layout_goneMarginEnd="80dp"
tools:text="@sample/matrix.json/data/displayName" /> tools:text="@sample/matrix.json/data/displayName" />
<TextView <TextView
android:id="@+id/matrixItemSubtitle" android:id="@+id/matrixItemSubtitle"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="8dp"
android:drawablePadding="16dp"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textColor="?riotx_text_secondary" android:textColor="?riotx_text_secondary"
@ -69,10 +66,9 @@
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/matrixItemProgress" app:layout_constraintEnd_toStartOf="@+id/matrixItemProgress"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/matrixItemAvatar" app:layout_constraintStart_toEndOf="@id/matrixItemAvatar"
app:layout_constraintTop_toBottomOf="@id/matrixItemTitle" app:layout_constraintTop_toBottomOf="@id/matrixItemTitle"
app:layout_goneMarginStart="0dp" app:layout_goneMarginEnd="8dp"
tools:text="@sample/matrix.json/data/mxid" /> tools:text="@sample/matrix.json/data/mxid" />
<ProgressBar <ProgressBar
@ -93,12 +89,10 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:src="@drawable/ic_arrow_right" android:src="@drawable/ic_arrow_right"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:tint="?riotx_text_secondary" app:tint="?riotx_text_secondary"
tools:ignore="MissingPrefix" tools:ignore="MissingPrefix" />
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -3353,4 +3353,6 @@
<string name="labs_use_restricted_join_rule">Experimental Space - Restricted Room.</string> <string name="labs_use_restricted_join_rule">Experimental Space - Restricted Room.</string>
<string name="labs_use_restricted_join_rule_desc">Warning requires server support and experimental room version</string> <string name="labs_use_restricted_join_rule_desc">Warning requires server support and experimental room version</string>
<string name="user_invites_you">%s invites you</string> <string name="user_invites_you">%s invites you</string>
<string name="looking_for_someone_not_in_space">Looking for someone not in %s?</string>
</resources> </resources>