mirror of
https://github.com/element-hq/element-android
synced 2024-11-25 02:45:37 +03:00
Make a generic user directory search & selection views.
This commit is contained in:
parent
f25c981173
commit
a4eba653a3
24 changed files with 780 additions and 269 deletions
|
@ -23,8 +23,6 @@ import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.multibindings.IntoMap
|
import dagger.multibindings.IntoMap
|
||||||
import im.vector.riotx.features.attachments.preview.AttachmentsPreviewFragment
|
import im.vector.riotx.features.attachments.preview.AttachmentsPreviewFragment
|
||||||
import im.vector.riotx.features.createdirect.CreateDirectRoomDirectoryUsersFragment
|
|
||||||
import im.vector.riotx.features.createdirect.CreateDirectRoomKnownUsersFragment
|
|
||||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment
|
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment
|
||||||
import im.vector.riotx.features.crypto.quads.SharedSecuredStorageKeyFragment
|
import im.vector.riotx.features.crypto.quads.SharedSecuredStorageKeyFragment
|
||||||
import im.vector.riotx.features.crypto.quads.SharedSecuredStoragePassphraseFragment
|
import im.vector.riotx.features.crypto.quads.SharedSecuredStoragePassphraseFragment
|
||||||
|
@ -63,6 +61,8 @@ import im.vector.riotx.features.login.LoginSplashFragment
|
||||||
import im.vector.riotx.features.login.LoginWaitForEmailFragment
|
import im.vector.riotx.features.login.LoginWaitForEmailFragment
|
||||||
import im.vector.riotx.features.login.LoginWebFragment
|
import im.vector.riotx.features.login.LoginWebFragment
|
||||||
import im.vector.riotx.features.login.terms.LoginTermsFragment
|
import im.vector.riotx.features.login.terms.LoginTermsFragment
|
||||||
|
import im.vector.riotx.features.userdirectory.KnownUsersFragment
|
||||||
|
import im.vector.riotx.features.userdirectory.UserDirectoryFragment
|
||||||
import im.vector.riotx.features.qrcode.QrCodeScannerFragment
|
import im.vector.riotx.features.qrcode.QrCodeScannerFragment
|
||||||
import im.vector.riotx.features.reactions.EmojiChooserFragment
|
import im.vector.riotx.features.reactions.EmojiChooserFragment
|
||||||
import im.vector.riotx.features.reactions.EmojiSearchResultFragment
|
import im.vector.riotx.features.reactions.EmojiSearchResultFragment
|
||||||
|
@ -226,13 +226,13 @@ interface FragmentModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(CreateDirectRoomDirectoryUsersFragment::class)
|
@FragmentKey(UserDirectoryFragment::class)
|
||||||
fun bindCreateDirectRoomDirectoryUsersFragment(fragment: CreateDirectRoomDirectoryUsersFragment): Fragment
|
fun bindUserDirectoryFragment(fragment: UserDirectoryFragment): Fragment
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(CreateDirectRoomKnownUsersFragment::class)
|
@FragmentKey(KnownUsersFragment::class)
|
||||||
fun bindCreateDirectRoomKnownUsersFragment(fragment: CreateDirectRoomKnownUsersFragment): Fragment
|
fun bindKnownUsersFragment(fragment: KnownUsersFragment): Fragment
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
|
|
|
@ -22,7 +22,6 @@ import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.multibindings.IntoMap
|
import dagger.multibindings.IntoMap
|
||||||
import im.vector.riotx.core.platform.ConfigurationViewModel
|
import im.vector.riotx.core.platform.ConfigurationViewModel
|
||||||
import im.vector.riotx.features.createdirect.CreateDirectRoomSharedActionViewModel
|
|
||||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyViewModel
|
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyViewModel
|
||||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
|
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
|
||||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
|
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
|
||||||
|
@ -32,6 +31,7 @@ import im.vector.riotx.features.home.room.detail.RoomDetailSharedActionViewModel
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.MessageSharedActionViewModel
|
import im.vector.riotx.features.home.room.detail.timeline.action.MessageSharedActionViewModel
|
||||||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
||||||
import im.vector.riotx.features.login.LoginSharedActionViewModel
|
import im.vector.riotx.features.login.LoginSharedActionViewModel
|
||||||
|
import im.vector.riotx.features.userdirectory.UserDirectorySharedActionViewModel
|
||||||
import im.vector.riotx.features.reactions.EmojiChooserViewModel
|
import im.vector.riotx.features.reactions.EmojiChooserViewModel
|
||||||
import im.vector.riotx.features.roomdirectory.RoomDirectorySharedActionViewModel
|
import im.vector.riotx.features.roomdirectory.RoomDirectorySharedActionViewModel
|
||||||
import im.vector.riotx.features.roomprofile.RoomProfileSharedActionViewModel
|
import im.vector.riotx.features.roomprofile.RoomProfileSharedActionViewModel
|
||||||
|
@ -87,8 +87,8 @@ interface ViewModelModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ViewModelKey(CreateDirectRoomSharedActionViewModel::class)
|
@ViewModelKey(UserDirectorySharedActionViewModel::class)
|
||||||
fun bindCreateDirectRoomSharedActionViewModel(viewModel: CreateDirectRoomSharedActionViewModel): ViewModel
|
fun bindUserDirectorySharedActionViewModel(viewModel: UserDirectorySharedActionViewModel): ViewModel
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -20,10 +20,5 @@ import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
sealed class CreateDirectRoomAction : VectorViewModelAction {
|
sealed class CreateDirectRoomAction : VectorViewModelAction {
|
||||||
object CreateRoomAndInviteSelectedUsers : CreateDirectRoomAction()
|
data class CreateRoomAndInviteSelectedUsers(val selectedUsers: Set<User>) : CreateDirectRoomAction()
|
||||||
data class FilterKnownUsers(val value: String) : CreateDirectRoomAction()
|
|
||||||
data class SearchDirectoryUsers(val value: String) : CreateDirectRoomAction()
|
|
||||||
object ClearFilterKnownUsers : CreateDirectRoomAction()
|
|
||||||
data class SelectUser(val user: User) : CreateDirectRoomAction()
|
|
||||||
data class RemoveSelectedUser(val user: User) : CreateDirectRoomAction()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,12 @@ import im.vector.riotx.core.extensions.addFragment
|
||||||
import im.vector.riotx.core.extensions.addFragmentToBackstack
|
import im.vector.riotx.core.extensions.addFragmentToBackstack
|
||||||
import im.vector.riotx.core.platform.SimpleFragmentActivity
|
import im.vector.riotx.core.platform.SimpleFragmentActivity
|
||||||
import im.vector.riotx.core.platform.WaitingViewData
|
import im.vector.riotx.core.platform.WaitingViewData
|
||||||
|
import im.vector.riotx.features.userdirectory.KnownUsersFragment
|
||||||
|
import im.vector.riotx.features.userdirectory.KnownUsersFragmentArgs
|
||||||
|
import im.vector.riotx.features.userdirectory.UserDirectoryFragment
|
||||||
|
import im.vector.riotx.features.userdirectory.UserDirectorySharedAction
|
||||||
|
import im.vector.riotx.features.userdirectory.UserDirectorySharedActionViewModel
|
||||||
|
import im.vector.riotx.features.userdirectory.UserDirectoryViewModel
|
||||||
import kotlinx.android.synthetic.main.activity.*
|
import kotlinx.android.synthetic.main.activity.*
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -44,7 +50,8 @@ import javax.inject.Inject
|
||||||
class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
||||||
|
|
||||||
private val viewModel: CreateDirectRoomViewModel by viewModel()
|
private val viewModel: CreateDirectRoomViewModel by viewModel()
|
||||||
private lateinit var sharedActionViewModel: CreateDirectRoomSharedActionViewModel
|
private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel
|
||||||
|
@Inject lateinit var userDirectoryViewModelFactory: UserDirectoryViewModel.Factory
|
||||||
@Inject lateinit var createDirectRoomViewModelFactory: CreateDirectRoomViewModel.Factory
|
@Inject lateinit var createDirectRoomViewModelFactory: CreateDirectRoomViewModel.Factory
|
||||||
@Inject lateinit var errorFormatter: ErrorFormatter
|
@Inject lateinit var errorFormatter: ErrorFormatter
|
||||||
|
|
||||||
|
@ -56,26 +63,40 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
toolbar.visibility = View.GONE
|
toolbar.visibility = View.GONE
|
||||||
sharedActionViewModel = viewModelProvider.get(CreateDirectRoomSharedActionViewModel::class.java)
|
sharedActionViewModel = viewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
|
||||||
sharedActionViewModel
|
sharedActionViewModel
|
||||||
.observe()
|
.observe()
|
||||||
.subscribe { sharedAction ->
|
.subscribe { sharedAction ->
|
||||||
when (sharedAction) {
|
when (sharedAction) {
|
||||||
CreateDirectRoomSharedAction.OpenUsersDirectory ->
|
UserDirectorySharedAction.OpenUsersDirectory ->
|
||||||
addFragmentToBackstack(R.id.container, CreateDirectRoomDirectoryUsersFragment::class.java)
|
addFragmentToBackstack(R.id.container, UserDirectoryFragment::class.java)
|
||||||
CreateDirectRoomSharedAction.Close -> finish()
|
UserDirectorySharedAction.Close -> finish()
|
||||||
CreateDirectRoomSharedAction.GoBack -> onBackPressed()
|
UserDirectorySharedAction.GoBack -> onBackPressed()
|
||||||
|
is UserDirectorySharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.disposeOnDestroy()
|
.disposeOnDestroy()
|
||||||
if (isFirstCreation()) {
|
if (isFirstCreation()) {
|
||||||
addFragment(R.id.container, CreateDirectRoomKnownUsersFragment::class.java)
|
addFragment(
|
||||||
|
R.id.container,
|
||||||
|
KnownUsersFragment::class.java,
|
||||||
|
KnownUsersFragmentArgs(
|
||||||
|
title = getString(R.string.fab_menu_create_chat),
|
||||||
|
menuResId = R.menu.vector_create_direct_room
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
viewModel.selectSubscribe(this, CreateDirectRoomViewState::createAndInviteState) {
|
viewModel.selectSubscribe(this, CreateDirectRoomViewState::createAndInviteState) {
|
||||||
renderCreateAndInviteState(it)
|
renderCreateAndInviteState(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onMenuItemSelected(action: UserDirectorySharedAction.OnMenuItemSelected) {
|
||||||
|
if (action.itemId == R.id.action_create_direct_room) {
|
||||||
|
viewModel.handle(CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(action.selectedUsers))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun renderCreateAndInviteState(state: Async<String>) {
|
private fun renderCreateAndInviteState(state: Async<String>) {
|
||||||
when (state) {
|
when (state) {
|
||||||
is Loading -> renderCreationLoading()
|
is Loading -> renderCreationLoading()
|
||||||
|
|
|
@ -18,7 +18,4 @@ package im.vector.riotx.features.createdirect
|
||||||
|
|
||||||
import im.vector.riotx.core.platform.VectorViewEvents
|
import im.vector.riotx.core.platform.VectorViewEvents
|
||||||
|
|
||||||
/**
|
|
||||||
* Transient events for create direct room screen
|
|
||||||
*/
|
|
||||||
sealed class CreateDirectRoomViewEvents : VectorViewEvents
|
sealed class CreateDirectRoomViewEvents : VectorViewEvents
|
||||||
|
|
|
@ -1,42 +1,31 @@
|
||||||
/*
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* * Copyright 2019 New Vector Ltd
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* *
|
* you may not use this file except in compliance with the License.
|
||||||
* * Licensed under the Apache License, Version 2.0 (the "License");
|
* You may obtain a copy of the License at
|
||||||
* * 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.
|
|
||||||
*
|
*
|
||||||
|
* 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.createdirect
|
package im.vector.riotx.features.createdirect
|
||||||
|
|
||||||
import arrow.core.Option
|
|
||||||
import com.airbnb.mvrx.ActivityViewModelContext
|
import com.airbnb.mvrx.ActivityViewModelContext
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
import im.vector.matrix.android.api.util.toMatrixItem
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
import im.vector.riotx.core.extensions.toggle
|
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
import io.reactivex.Single
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
private typealias KnowUsersFilter = String
|
|
||||||
private typealias DirectoryUsersSearch = String
|
|
||||||
|
|
||||||
class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||||
initialState: CreateDirectRoomViewState,
|
initialState: CreateDirectRoomViewState,
|
||||||
|
@ -48,9 +37,6 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||||
fun create(initialState: CreateDirectRoomViewState): CreateDirectRoomViewModel
|
fun create(initialState: CreateDirectRoomViewState): CreateDirectRoomViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
private val knownUsersFilter = BehaviorRelay.createDefault<Option<KnowUsersFilter>>(Option.empty())
|
|
||||||
private val directoryUsersSearch = BehaviorRelay.create<DirectoryUsersSearch>()
|
|
||||||
|
|
||||||
companion object : MvRxViewModelFactory<CreateDirectRoomViewModel, CreateDirectRoomViewState> {
|
companion object : MvRxViewModelFactory<CreateDirectRoomViewModel, CreateDirectRoomViewState> {
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@ -60,25 +46,15 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
|
||||||
observeKnownUsers()
|
|
||||||
observeDirectoryUsers()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handle(action: CreateDirectRoomAction) {
|
override fun handle(action: CreateDirectRoomAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers -> createRoomAndInviteSelectedUsers()
|
is CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers -> createRoomAndInviteSelectedUsers(action.selectedUsers)
|
||||||
is CreateDirectRoomAction.FilterKnownUsers -> knownUsersFilter.accept(Option.just(action.value))
|
|
||||||
is CreateDirectRoomAction.ClearFilterKnownUsers -> knownUsersFilter.accept(Option.empty())
|
|
||||||
is CreateDirectRoomAction.SearchDirectoryUsers -> directoryUsersSearch.accept(action.value)
|
|
||||||
is CreateDirectRoomAction.SelectUser -> handleSelectUser(action)
|
|
||||||
is CreateDirectRoomAction.RemoveSelectedUser -> handleRemoveSelectedUser(action)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createRoomAndInviteSelectedUsers() = withState { currentState ->
|
private fun createRoomAndInviteSelectedUsers(selectedUsers: Set<User>) {
|
||||||
val roomParams = CreateRoomParams(
|
val roomParams = CreateRoomParams(
|
||||||
invitedUserIds = currentState.selectedUsers.map { it.userId }
|
invitedUserIds = selectedUsers.map { it.userId }
|
||||||
)
|
)
|
||||||
.setDirectMessage()
|
.setDirectMessage()
|
||||||
.enableEncryptionIfInvitedUsersSupportIt()
|
.enableEncryptionIfInvitedUsersSupportIt()
|
||||||
|
@ -89,52 +65,4 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||||
copy(createAndInviteState = it)
|
copy(createAndInviteState = it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRemoveSelectedUser(action: CreateDirectRoomAction.RemoveSelectedUser) = withState { state ->
|
|
||||||
val selectedUsers = state.selectedUsers.minus(action.user)
|
|
||||||
setState { copy(selectedUsers = selectedUsers) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleSelectUser(action: CreateDirectRoomAction.SelectUser) = withState { state ->
|
|
||||||
// Reset the filter asap
|
|
||||||
directoryUsersSearch.accept("")
|
|
||||||
val selectedUsers = state.selectedUsers.toggle(action.user)
|
|
||||||
setState { copy(selectedUsers = selectedUsers) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun observeDirectoryUsers() {
|
|
||||||
directoryUsersSearch
|
|
||||||
.debounce(300, TimeUnit.MILLISECONDS)
|
|
||||||
.switchMapSingle { search ->
|
|
||||||
val stream = if (search.isBlank()) {
|
|
||||||
Single.just(emptyList())
|
|
||||||
} else {
|
|
||||||
session.rx()
|
|
||||||
.searchUsersDirectory(search, 50, emptySet())
|
|
||||||
.map { users ->
|
|
||||||
users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stream.toAsync {
|
|
||||||
copy(directoryUsers = it, directorySearchTerm = search)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.subscribe()
|
|
||||||
.disposeOnClear()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun observeKnownUsers() {
|
|
||||||
knownUsersFilter
|
|
||||||
.throttleLast(300, TimeUnit.MILLISECONDS)
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.switchMap {
|
|
||||||
session.rx().livePagedUsers(it.orNull())
|
|
||||||
}
|
|
||||||
.execute { async ->
|
|
||||||
copy(
|
|
||||||
knownUsers = async,
|
|
||||||
filterKnownUsersValue = knownUsersFilter.value ?: Option.empty()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,25 @@
|
||||||
/*
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* * Copyright 2019 New Vector Ltd
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* *
|
* you may not use this file except in compliance with the License.
|
||||||
* * Licensed under the Apache License, Version 2.0 (the "License");
|
* You may obtain a copy of the License at
|
||||||
* * 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.
|
|
||||||
*
|
*
|
||||||
|
* 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.createdirect
|
package im.vector.riotx.features.createdirect
|
||||||
|
|
||||||
import androidx.paging.PagedList
|
|
||||||
import arrow.core.Option
|
|
||||||
import com.airbnb.mvrx.Async
|
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.matrix.android.api.session.user.model.User
|
|
||||||
|
|
||||||
data class CreateDirectRoomViewState(
|
data class CreateDirectRoomViewState(
|
||||||
val knownUsers: Async<PagedList<User>> = Uninitialized,
|
val createAndInviteState: Async<String> = Uninitialized
|
||||||
val directoryUsers: Async<List<User>> = Uninitialized,
|
) : MvRxState
|
||||||
val selectedUsers: Set<User> = emptySet(),
|
|
||||||
val createAndInviteState: Async<String> = Uninitialized,
|
|
||||||
val directorySearchTerm: String = "",
|
|
||||||
val filterKnownUsersValue: Option<String> = Option.empty()
|
|
||||||
) : MvRxState {
|
|
||||||
|
|
||||||
enum class DisplayMode {
|
|
||||||
KNOWN_USERS,
|
|
||||||
DIRECTORY_USERS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,22 +1,20 @@
|
||||||
/*
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* * Copyright 2019 New Vector Ltd
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* *
|
* you may not use this file except in compliance with the License.
|
||||||
* * Licensed under the Apache License, Version 2.0 (the "License");
|
* You may obtain a copy of the License at
|
||||||
* * 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.
|
|
||||||
*
|
*
|
||||||
|
* 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.createdirect
|
package im.vector.riotx.features.userdirectory
|
||||||
|
|
||||||
import com.airbnb.epoxy.EpoxyController
|
import com.airbnb.epoxy.EpoxyController
|
||||||
import com.airbnb.mvrx.Fail
|
import com.airbnb.mvrx.Fail
|
||||||
|
@ -41,7 +39,7 @@ class DirectoryUsersController @Inject constructor(private val session: Session,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val errorFormatter: ErrorFormatter) : EpoxyController() {
|
private val errorFormatter: ErrorFormatter) : EpoxyController() {
|
||||||
|
|
||||||
private var state: CreateDirectRoomViewState? = null
|
private var state: UserDirectoryViewState? = null
|
||||||
|
|
||||||
var callback: Callback? = null
|
var callback: Callback? = null
|
||||||
|
|
||||||
|
@ -49,7 +47,7 @@ class DirectoryUsersController @Inject constructor(private val session: Session,
|
||||||
requestModelBuild()
|
requestModelBuild()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setData(state: CreateDirectRoomViewState) {
|
fun setData(state: UserDirectoryViewState) {
|
||||||
this.state = state
|
this.state = state
|
||||||
requestModelBuild()
|
requestModelBuild()
|
||||||
}
|
}
|
||||||
|
@ -110,7 +108,7 @@ class DirectoryUsersController @Inject constructor(private val session: Session,
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
val isSelected = selectedUsers.contains(user.userId)
|
val isSelected = selectedUsers.contains(user.userId)
|
||||||
createDirectRoomUserItem {
|
userDirectoryUserItem {
|
||||||
id(user.userId)
|
id(user.userId)
|
||||||
selected(isSelected)
|
selected(isSelected)
|
||||||
matrixItem(user.toMatrixItem())
|
matrixItem(user.toMatrixItem())
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.features.createdirect
|
package im.vector.riotx.features.userdirectory
|
||||||
|
|
||||||
import com.airbnb.epoxy.EpoxyModel
|
import com.airbnb.epoxy.EpoxyModel
|
||||||
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
||||||
|
@ -49,7 +49,7 @@ class KnownUsersController @Inject constructor(private val session: Session,
|
||||||
requestModelBuild()
|
requestModelBuild()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setData(state: CreateDirectRoomViewState) {
|
fun setData(state: UserDirectoryViewState) {
|
||||||
this.isFiltering = !state.filterKnownUsersValue.isEmpty()
|
this.isFiltering = !state.filterKnownUsersValue.isEmpty()
|
||||||
val newSelection = state.selectedUsers.map { it.userId }
|
val newSelection = state.selectedUsers.map { it.userId }
|
||||||
this.users = state.knownUsers
|
this.users = state.knownUsers
|
||||||
|
@ -65,7 +65,7 @@ class KnownUsersController @Inject constructor(private val session: Session,
|
||||||
EmptyItem_().id(currentPosition)
|
EmptyItem_().id(currentPosition)
|
||||||
} else {
|
} else {
|
||||||
val isSelected = selectedUsers.contains(item.userId)
|
val isSelected = selectedUsers.contains(item.userId)
|
||||||
CreateDirectRoomUserItem_()
|
UserDirectoryUserItem_()
|
||||||
.id(item.userId)
|
.id(item.userId)
|
||||||
.selected(isSelected)
|
.selected(isSelected)
|
||||||
.matrixItem(item.toMatrixItem())
|
.matrixItem(item.toMatrixItem())
|
||||||
|
@ -84,13 +84,13 @@ class KnownUsersController @Inject constructor(private val session: Session,
|
||||||
} else {
|
} else {
|
||||||
var lastFirstLetter: String? = null
|
var lastFirstLetter: String? = null
|
||||||
for (model in models) {
|
for (model in models) {
|
||||||
if (model is CreateDirectRoomUserItem) {
|
if (model is UserDirectoryUserItem) {
|
||||||
if (model.matrixItem.id == session.myUserId) continue
|
if (model.matrixItem.id == session.myUserId) continue
|
||||||
val currentFirstLetter = model.matrixItem.firstLetterOfDisplayName()
|
val currentFirstLetter = model.matrixItem.firstLetterOfDisplayName()
|
||||||
val showLetter = !isFiltering && currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter
|
val showLetter = !isFiltering && currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter
|
||||||
lastFirstLetter = currentFirstLetter
|
lastFirstLetter = currentFirstLetter
|
||||||
|
|
||||||
CreateDirectRoomLetterHeaderItem_()
|
UserDirectoryLetterHeaderItem_()
|
||||||
.id(currentFirstLetter)
|
.id(currentFirstLetter)
|
||||||
.letter(currentFirstLetter)
|
.letter(currentFirstLetter)
|
||||||
.addIf(showLetter, this)
|
.addIf(showLetter, this)
|
|
@ -1,28 +1,27 @@
|
||||||
/*
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* * Copyright 2019 New Vector Ltd
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* *
|
* you may not use this file except in compliance with the License.
|
||||||
* * Licensed under the Apache License, Version 2.0 (the "License");
|
* You may obtain a copy of the License at
|
||||||
* * 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.
|
|
||||||
*
|
*
|
||||||
|
* 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.createdirect
|
package im.vector.riotx.features.userdirectory
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ScrollView
|
import android.widget.ScrollView
|
||||||
|
import androidx.core.view.forEach
|
||||||
import com.airbnb.mvrx.activityViewModel
|
import com.airbnb.mvrx.activityViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
|
@ -35,30 +34,33 @@ import im.vector.riotx.core.extensions.hideKeyboard
|
||||||
import im.vector.riotx.core.extensions.setupAsSearch
|
import im.vector.riotx.core.extensions.setupAsSearch
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
import im.vector.riotx.core.utils.DimensionConverter
|
import im.vector.riotx.core.utils.DimensionConverter
|
||||||
import kotlinx.android.synthetic.main.fragment_create_direct_room.*
|
import kotlinx.android.synthetic.main.fragment_known_users.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class CreateDirectRoomKnownUsersFragment @Inject constructor(
|
class KnownUsersFragment @Inject constructor(
|
||||||
|
val userDirectoryViewModelFactory: UserDirectoryViewModel.Factory,
|
||||||
private val knownUsersController: KnownUsersController,
|
private val knownUsersController: KnownUsersController,
|
||||||
private val dimensionConverter: DimensionConverter
|
private val dimensionConverter: DimensionConverter
|
||||||
) : VectorBaseFragment(), KnownUsersController.Callback {
|
) : VectorBaseFragment(), KnownUsersController.Callback {
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_create_direct_room
|
override fun getLayoutResId() = R.layout.fragment_known_users
|
||||||
|
|
||||||
override fun getMenuRes() = R.menu.vector_create_direct_room
|
override fun getMenuRes() = withState(viewModel) {
|
||||||
|
return@withState it.menuResId ?: -1
|
||||||
|
}
|
||||||
|
|
||||||
private val viewModel: CreateDirectRoomViewModel by activityViewModel()
|
private val viewModel: UserDirectoryViewModel by activityViewModel()
|
||||||
private lateinit var sharedActionViewModel: CreateDirectRoomSharedActionViewModel
|
private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
sharedActionViewModel = activityViewModelProvider.get(CreateDirectRoomSharedActionViewModel::class.java)
|
sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
|
||||||
vectorBaseActivity.setSupportActionBar(createDirectRoomToolbar)
|
vectorBaseActivity.setSupportActionBar(knownUsersToolbar)
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
setupFilterView()
|
setupFilterView()
|
||||||
setupAddByMatrixIdView()
|
setupAddByMatrixIdView()
|
||||||
setupCloseView()
|
setupCloseView()
|
||||||
viewModel.selectSubscribe(this, CreateDirectRoomViewState::selectedUsers) {
|
viewModel.selectSubscribe(this, UserDirectoryViewState::selectedUsers) {
|
||||||
renderSelectedUsers(it)
|
renderSelectedUsers(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,27 +73,22 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor(
|
||||||
|
|
||||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||||
withState(viewModel) {
|
withState(viewModel) {
|
||||||
val createMenuItem = menu.findItem(R.id.action_create_direct_room)
|
|
||||||
val showMenuItem = it.selectedUsers.isNotEmpty()
|
val showMenuItem = it.selectedUsers.isNotEmpty()
|
||||||
createMenuItem.setVisible(showMenuItem)
|
menu.forEach { menuItem ->
|
||||||
|
menuItem.isVisible = showMenuItem
|
||||||
|
}
|
||||||
}
|
}
|
||||||
super.onPrepareOptionsMenu(menu)
|
super.onPrepareOptionsMenu(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean = withState(viewModel) {
|
||||||
return when (item.itemId) {
|
sharedActionViewModel.post(UserDirectorySharedAction.OnMenuItemSelected(item.itemId, it.selectedUsers))
|
||||||
R.id.action_create_direct_room -> {
|
return@withState true
|
||||||
viewModel.handle(CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else ->
|
|
||||||
super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupAddByMatrixIdView() {
|
private fun setupAddByMatrixIdView() {
|
||||||
addByMatrixId.setOnClickListener {
|
addByMatrixId.setOnClickListener {
|
||||||
sharedActionViewModel.post(CreateDirectRoomSharedAction.OpenUsersDirectory)
|
sharedActionViewModel.post(UserDirectorySharedAction.OpenUsersDirectory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,26 +99,26 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupFilterView() {
|
private fun setupFilterView() {
|
||||||
createDirectRoomFilter
|
knownUsersFilter
|
||||||
.textChanges()
|
.textChanges()
|
||||||
.startWith(createDirectRoomFilter.text)
|
.startWith(knownUsersFilter.text)
|
||||||
.subscribe { text ->
|
.subscribe { text ->
|
||||||
val filterValue = text.trim()
|
val filterValue = text.trim()
|
||||||
val action = if (filterValue.isBlank()) {
|
val action = if (filterValue.isBlank()) {
|
||||||
CreateDirectRoomAction.ClearFilterKnownUsers
|
UserDirectoryAction.ClearFilterKnownUsers
|
||||||
} else {
|
} else {
|
||||||
CreateDirectRoomAction.FilterKnownUsers(filterValue.toString())
|
UserDirectoryAction.FilterKnownUsers(filterValue.toString())
|
||||||
}
|
}
|
||||||
viewModel.handle(action)
|
viewModel.handle(action)
|
||||||
}
|
}
|
||||||
.disposeOnDestroyView()
|
.disposeOnDestroyView()
|
||||||
|
|
||||||
createDirectRoomFilter.setupAsSearch()
|
knownUsersFilter.setupAsSearch()
|
||||||
createDirectRoomFilter.requestFocus()
|
knownUsersFilter.requestFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupCloseView() {
|
private fun setupCloseView() {
|
||||||
createDirectRoomClose.setOnClickListener {
|
knownUsersClose.setOnClickListener {
|
||||||
requireActivity().finish()
|
requireActivity().finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,12 +154,12 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor(
|
||||||
chip.isCloseIconVisible = true
|
chip.isCloseIconVisible = true
|
||||||
chipGroup.addView(chip)
|
chipGroup.addView(chip)
|
||||||
chip.setOnCloseIconClickListener {
|
chip.setOnCloseIconClickListener {
|
||||||
viewModel.handle(CreateDirectRoomAction.RemoveSelectedUser(user))
|
viewModel.handle(UserDirectoryAction.RemoveSelectedUser(user))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemClick(user: User) {
|
override fun onItemClick(user: User) {
|
||||||
view?.hideKeyboard()
|
view?.hideKeyboard()
|
||||||
viewModel.handle(CreateDirectRoomAction.SelectUser(user))
|
viewModel.handle(UserDirectoryAction.SelectUser(user))
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,12 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.features.createdirect
|
package im.vector.riotx.features.userdirectory
|
||||||
|
|
||||||
import im.vector.riotx.core.platform.VectorSharedAction
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
sealed class CreateDirectRoomSharedAction : VectorSharedAction {
|
@Parcelize
|
||||||
object OpenUsersDirectory : CreateDirectRoomSharedAction()
|
data class KnownUsersFragmentArgs(
|
||||||
object Close : CreateDirectRoomSharedAction()
|
val title: String,
|
||||||
object GoBack : CreateDirectRoomSharedAction()
|
val menuResId: Int? = null
|
||||||
}
|
) : Parcelable
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 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.userdirectory
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
|
sealed class UserDirectoryAction : VectorViewModelAction {
|
||||||
|
data class FilterKnownUsers(val value: String) : UserDirectoryAction()
|
||||||
|
data class SearchDirectoryUsers(val value: String) : UserDirectoryAction()
|
||||||
|
object ClearFilterKnownUsers : UserDirectoryAction()
|
||||||
|
data class SelectUser(val user: User) : UserDirectoryAction()
|
||||||
|
data class RemoveSelectedUser(val user: User) : UserDirectoryAction()
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.features.createdirect
|
package im.vector.riotx.features.userdirectory
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -29,22 +29,22 @@ import im.vector.riotx.core.extensions.hideKeyboard
|
||||||
import im.vector.riotx.core.extensions.setupAsSearch
|
import im.vector.riotx.core.extensions.setupAsSearch
|
||||||
import im.vector.riotx.core.extensions.showKeyboard
|
import im.vector.riotx.core.extensions.showKeyboard
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
import kotlinx.android.synthetic.main.fragment_create_direct_room_directory_users.*
|
import kotlinx.android.synthetic.main.fragment_create_direct_room_directory_users.recyclerView
|
||||||
|
import kotlinx.android.synthetic.main.fragment_user_directory.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class CreateDirectRoomDirectoryUsersFragment @Inject constructor(
|
class UserDirectoryFragment @Inject constructor(
|
||||||
private val directRoomController: DirectoryUsersController
|
private val directRoomController: DirectoryUsersController
|
||||||
) : VectorBaseFragment(), DirectoryUsersController.Callback {
|
) : VectorBaseFragment(), DirectoryUsersController.Callback {
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_create_direct_room_directory_users
|
override fun getLayoutResId() = R.layout.fragment_user_directory
|
||||||
|
private val viewModel: UserDirectoryViewModel by activityViewModel()
|
||||||
|
|
||||||
private val viewModel: CreateDirectRoomViewModel by activityViewModel()
|
private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel
|
||||||
|
|
||||||
private lateinit var sharedActionViewModel: CreateDirectRoomSharedActionViewModel
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
sharedActionViewModel = activityViewModelProvider.get(CreateDirectRoomSharedActionViewModel::class.java)
|
sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
setupSearchByMatrixIdView()
|
setupSearchByMatrixIdView()
|
||||||
setupCloseView()
|
setupCloseView()
|
||||||
|
@ -62,19 +62,19 @@ class CreateDirectRoomDirectoryUsersFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupSearchByMatrixIdView() {
|
private fun setupSearchByMatrixIdView() {
|
||||||
createDirectRoomSearchById.setupAsSearch(searchIconRes = 0)
|
userDirectorySearchById.setupAsSearch(searchIconRes = 0)
|
||||||
createDirectRoomSearchById
|
userDirectorySearchById
|
||||||
.textChanges()
|
.textChanges()
|
||||||
.subscribe {
|
.subscribe {
|
||||||
viewModel.handle(CreateDirectRoomAction.SearchDirectoryUsers(it.toString()))
|
viewModel.handle(UserDirectoryAction.SearchDirectoryUsers(it.toString()))
|
||||||
}
|
}
|
||||||
.disposeOnDestroyView()
|
.disposeOnDestroyView()
|
||||||
createDirectRoomSearchById.showKeyboard(andRequestFocus = true)
|
userDirectorySearchById.showKeyboard(andRequestFocus = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupCloseView() {
|
private fun setupCloseView() {
|
||||||
createDirectRoomClose.setOnClickListener {
|
userDirectoryClose.setOnClickListener {
|
||||||
sharedActionViewModel.post(CreateDirectRoomSharedAction.GoBack)
|
sharedActionViewModel.post(UserDirectorySharedAction.GoBack)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,12 +84,12 @@ class CreateDirectRoomDirectoryUsersFragment @Inject constructor(
|
||||||
|
|
||||||
override fun onItemClick(user: User) {
|
override fun onItemClick(user: User) {
|
||||||
view?.hideKeyboard()
|
view?.hideKeyboard()
|
||||||
viewModel.handle(CreateDirectRoomAction.SelectUser(user))
|
viewModel.handle(UserDirectoryAction.SelectUser(user))
|
||||||
sharedActionViewModel.post(CreateDirectRoomSharedAction.GoBack)
|
sharedActionViewModel.post(UserDirectorySharedAction.GoBack)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun retryDirectoryUsersRequest() {
|
override fun retryDirectoryUsersRequest() {
|
||||||
val currentSearch = createDirectRoomSearchById.text.toString()
|
val currentSearch = userDirectorySearchById.text.toString()
|
||||||
viewModel.handle(CreateDirectRoomAction.SearchDirectoryUsers(currentSearch))
|
viewModel.handle(UserDirectoryAction.SearchDirectoryUsers(currentSearch))
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.features.createdirect
|
package im.vector.riotx.features.userdirectory
|
||||||
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
@ -23,8 +23,8 @@ import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
|
||||||
@EpoxyModelClass(layout = R.layout.item_create_direct_room_letter_header)
|
@EpoxyModelClass(layout = R.layout.item_user_directory_letter_header)
|
||||||
abstract class CreateDirectRoomLetterHeaderItem : VectorEpoxyModel<CreateDirectRoomLetterHeaderItem.Holder>() {
|
abstract class UserDirectoryLetterHeaderItem : VectorEpoxyModel<UserDirectoryLetterHeaderItem.Holder>() {
|
||||||
|
|
||||||
@EpoxyAttribute var letter: String = ""
|
@EpoxyAttribute var letter: String = ""
|
||||||
|
|
||||||
|
@ -33,6 +33,6 @@ abstract class CreateDirectRoomLetterHeaderItem : VectorEpoxyModel<CreateDirectR
|
||||||
}
|
}
|
||||||
|
|
||||||
class Holder : VectorEpoxyHolder() {
|
class Holder : VectorEpoxyHolder() {
|
||||||
val letterView by bind<TextView>(R.id.createDirectRoomLetterView)
|
val letterView by bind<TextView>(R.id.userDirectoryLetterView)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 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.userdirectory
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.riotx.core.platform.VectorSharedAction
|
||||||
|
|
||||||
|
sealed class UserDirectorySharedAction : VectorSharedAction {
|
||||||
|
object OpenUsersDirectory : UserDirectorySharedAction()
|
||||||
|
object Close : UserDirectorySharedAction()
|
||||||
|
object GoBack : UserDirectorySharedAction()
|
||||||
|
data class OnMenuItemSelected(val itemId: Int, val selectedUsers: Set<User>) : UserDirectorySharedAction()
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,9 +14,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.features.createdirect
|
package im.vector.riotx.features.userdirectory
|
||||||
|
|
||||||
import im.vector.riotx.core.platform.VectorSharedActionViewModel
|
import im.vector.riotx.core.platform.VectorSharedActionViewModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class CreateDirectRoomSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<CreateDirectRoomSharedAction>()
|
class UserDirectorySharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<UserDirectorySharedAction>()
|
|
@ -1,22 +1,20 @@
|
||||||
/*
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* * Copyright 2019 New Vector Ltd
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* *
|
* you may not use this file except in compliance with the License.
|
||||||
* * Licensed under the Apache License, Version 2.0 (the "License");
|
* You may obtain a copy of the License at
|
||||||
* * 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.
|
|
||||||
*
|
*
|
||||||
|
* 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.createdirect
|
package im.vector.riotx.features.userdirectory
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
@ -31,8 +29,8 @@ import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
|
||||||
@EpoxyModelClass(layout = R.layout.item_create_direct_room_user)
|
@EpoxyModelClass(layout = R.layout.item_known_user)
|
||||||
abstract class CreateDirectRoomUserItem : VectorEpoxyModel<CreateDirectRoomUserItem.Holder>() {
|
abstract class UserDirectoryUserItem : VectorEpoxyModel<UserDirectoryUserItem.Holder>() {
|
||||||
|
|
||||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||||
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||||
|
@ -66,9 +64,9 @@ abstract class CreateDirectRoomUserItem : VectorEpoxyModel<CreateDirectRoomUserI
|
||||||
}
|
}
|
||||||
|
|
||||||
class Holder : VectorEpoxyHolder() {
|
class Holder : VectorEpoxyHolder() {
|
||||||
val userIdView by bind<TextView>(R.id.createDirectRoomUserID)
|
val userIdView by bind<TextView>(R.id.knownUserID)
|
||||||
val nameView by bind<TextView>(R.id.createDirectRoomUserName)
|
val nameView by bind<TextView>(R.id.knownUserName)
|
||||||
val avatarImageView by bind<ImageView>(R.id.createDirectRoomUserAvatar)
|
val avatarImageView by bind<ImageView>(R.id.knownUserAvatar)
|
||||||
val avatarCheckedImageView by bind<ImageView>(R.id.createDirectRoomUserAvatarChecked)
|
val avatarCheckedImageView by bind<ImageView>(R.id.knownUserAvatarChecked)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 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.userdirectory
|
||||||
|
|
||||||
|
import im.vector.riotx.core.platform.VectorViewEvents
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transient events for create direct room screen
|
||||||
|
*/
|
||||||
|
sealed class UserDirectoryViewEvents : VectorViewEvents
|
|
@ -0,0 +1,132 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 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.userdirectory
|
||||||
|
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import arrow.core.Option
|
||||||
|
import com.airbnb.mvrx.ActivityViewModelContext
|
||||||
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
|
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||||
|
import com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
|
import im.vector.matrix.rx.rx
|
||||||
|
import im.vector.riotx.core.extensions.toggle
|
||||||
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
|
import im.vector.riotx.features.createdirect.CreateDirectRoomActivity
|
||||||
|
import io.reactivex.Single
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
private typealias KnowUsersFilter = String
|
||||||
|
private typealias DirectoryUsersSearch = String
|
||||||
|
|
||||||
|
class UserDirectoryViewModel @AssistedInject constructor(@Assisted
|
||||||
|
initialState: UserDirectoryViewState,
|
||||||
|
private val session: Session)
|
||||||
|
: VectorViewModel<UserDirectoryViewState, UserDirectoryAction, UserDirectoryViewEvents>(initialState) {
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(initialState: UserDirectoryViewState): UserDirectoryViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
private val knownUsersFilter = BehaviorRelay.createDefault<Option<KnowUsersFilter>>(Option.empty())
|
||||||
|
private val directoryUsersSearch = BehaviorRelay.create<DirectoryUsersSearch>()
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<UserDirectoryViewModel, UserDirectoryViewState> {
|
||||||
|
|
||||||
|
override fun create(viewModelContext: ViewModelContext, state: UserDirectoryViewState): UserDirectoryViewModel? {
|
||||||
|
return when (viewModelContext) {
|
||||||
|
is FragmentViewModelContext -> (viewModelContext.fragment() as KnownUsersFragment).userDirectoryViewModelFactory.create(state)
|
||||||
|
is ActivityViewModelContext -> {
|
||||||
|
when (viewModelContext.activity<FragmentActivity>()) {
|
||||||
|
is CreateDirectRoomActivity -> viewModelContext.activity<CreateDirectRoomActivity>().userDirectoryViewModelFactory.create(state)
|
||||||
|
else -> error("Wrong activity or fragment")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> error("Wrong activity or fragment")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
observeKnownUsers()
|
||||||
|
observeDirectoryUsers()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(action: UserDirectoryAction) {
|
||||||
|
when (action) {
|
||||||
|
is UserDirectoryAction.FilterKnownUsers -> knownUsersFilter.accept(Option.just(action.value))
|
||||||
|
is UserDirectoryAction.ClearFilterKnownUsers -> knownUsersFilter.accept(Option.empty())
|
||||||
|
is UserDirectoryAction.SearchDirectoryUsers -> directoryUsersSearch.accept(action.value)
|
||||||
|
is UserDirectoryAction.SelectUser -> handleSelectUser(action)
|
||||||
|
is UserDirectoryAction.RemoveSelectedUser -> handleRemoveSelectedUser(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleRemoveSelectedUser(action: UserDirectoryAction.RemoveSelectedUser) = withState { state ->
|
||||||
|
val selectedUsers = state.selectedUsers.minus(action.user)
|
||||||
|
setState { copy(selectedUsers = selectedUsers) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSelectUser(action: UserDirectoryAction.SelectUser) = withState { state ->
|
||||||
|
// Reset the filter asap
|
||||||
|
directoryUsersSearch.accept("")
|
||||||
|
val selectedUsers = state.selectedUsers.toggle(action.user)
|
||||||
|
setState { copy(selectedUsers = selectedUsers) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeDirectoryUsers() {
|
||||||
|
directoryUsersSearch
|
||||||
|
.debounce(300, TimeUnit.MILLISECONDS)
|
||||||
|
.switchMapSingle { search ->
|
||||||
|
val stream = if (search.isBlank()) {
|
||||||
|
Single.just(emptyList())
|
||||||
|
} else {
|
||||||
|
session.rx()
|
||||||
|
.searchUsersDirectory(search, 50, emptySet())
|
||||||
|
.map { users ->
|
||||||
|
users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.toAsync {
|
||||||
|
copy(directoryUsers = it, directorySearchTerm = search)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.subscribe()
|
||||||
|
.disposeOnClear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeKnownUsers() {
|
||||||
|
knownUsersFilter
|
||||||
|
.throttleLast(300, TimeUnit.MILLISECONDS)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.switchMap {
|
||||||
|
session.rx().livePagedUsers(it.orNull())
|
||||||
|
}
|
||||||
|
.execute { async ->
|
||||||
|
copy(
|
||||||
|
knownUsers = async,
|
||||||
|
filterKnownUsersValue = knownUsersFilter.value ?: Option.empty()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 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.userdirectory
|
||||||
|
|
||||||
|
import androidx.paging.PagedList
|
||||||
|
import arrow.core.Option
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.MvRxState
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
|
||||||
|
data class UserDirectoryViewState(
|
||||||
|
val knownUsers: Async<PagedList<User>> = Uninitialized,
|
||||||
|
val directoryUsers: Async<List<User>> = Uninitialized,
|
||||||
|
val selectedUsers: Set<User> = emptySet(),
|
||||||
|
val createAndInviteState: Async<String> = Uninitialized,
|
||||||
|
val directorySearchTerm: String = "",
|
||||||
|
val filterKnownUsersValue: Option<String> = Option.empty(),
|
||||||
|
val title: String,
|
||||||
|
val menuResId: Int?
|
||||||
|
) : MvRxState {
|
||||||
|
|
||||||
|
constructor(args: KnownUsersFragmentArgs) : this(title = args.title, menuResId = args.menuResId)
|
||||||
|
|
||||||
|
enum class DisplayMode {
|
||||||
|
KNOWN_USERS,
|
||||||
|
DIRECTORY_USERS
|
||||||
|
}
|
||||||
|
}
|
143
vector/src/main/res/layout/fragment_known_users.xml
Normal file
143
vector/src/main/res/layout/fragment_known_users.xml
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
<?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:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/knownUsersToolbar"
|
||||||
|
style="@style/VectorToolbarStyle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="?actionBarSize"
|
||||||
|
android:elevation="4dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/knownUsersClose"
|
||||||
|
android:layout_width="@dimen/layout_touch_size"
|
||||||
|
android:layout_height="@dimen/layout_touch_size"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:src="@drawable/ic_x_18dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||||
|
android:id="@+id/knownUsersTitle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="@string/fab_menu_create_chat"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/knownUsersClose"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
|
||||||
|
<im.vector.riotx.core.platform.MaxHeightScrollView
|
||||||
|
android:id="@+id/chipGroupScrollView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/knownUsersToolbar"
|
||||||
|
app:maxHeight="64dp">
|
||||||
|
|
||||||
|
<com.google.android.material.chip.ChipGroup
|
||||||
|
android:id="@+id/chipGroup"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:lineSpacing="2dp" />
|
||||||
|
|
||||||
|
</im.vector.riotx.core.platform.MaxHeightScrollView>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/knownUsersFilter"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||||
|
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||||
|
android:background="@null"
|
||||||
|
android:drawablePadding="8dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:hint="@string/direct_room_filter_hint"
|
||||||
|
android:importantForAutofill="no"
|
||||||
|
android:inputType="text"
|
||||||
|
android:maxHeight="80dp"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/chipGroupScrollView" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/knownUsersFilterDivider"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="?attr/vctr_list_divider_color"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/knownUsersFilter" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/addByMatrixId"
|
||||||
|
style="@style/VectorButtonStyleText"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:minHeight="@dimen/layout_touch_size"
|
||||||
|
android:text="@string/add_by_matrix_id"
|
||||||
|
android:visibility="visible"
|
||||||
|
app:icon="@drawable/ic_plus_circle"
|
||||||
|
app:iconPadding="13dp"
|
||||||
|
app:iconTint="@color/riotx_accent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/knownUsersFilterDivider" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:fastScrollEnabled="true"
|
||||||
|
android:overScrollMode="always"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/addByMatrixId"
|
||||||
|
tools:listitem="@layout/item_known_user" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
109
vector/src/main/res/layout/fragment_user_directory.xml
Normal file
109
vector/src/main/res/layout/fragment_user_directory.xml
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
<?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:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/userDirectoryToolbar"
|
||||||
|
style="@style/VectorToolbarStyle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="?actionBarSize"
|
||||||
|
android:elevation="4dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/userDirectoryClose"
|
||||||
|
android:layout_width="@dimen/layout_touch_size"
|
||||||
|
android:layout_height="@dimen/layout_touch_size"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:src="@drawable/ic_x_18dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||||
|
android:id="@+id/userDirectoryTitle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="@string/direct_chats_header"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/userDirectoryClose"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/userDirectorySearchByIdContainer"
|
||||||
|
style="@style/VectorTextInputLayout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/userDirectoryToolbar">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/userDirectorySearchById"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/add_by_matrix_id" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/userDirectoryFilterDivider"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="?attr/vctr_list_divider_color"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/userDirectorySearchByIdContainer" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:fastScrollEnabled="true"
|
||||||
|
android:overScrollMode="always"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/userDirectoryFilterDivider"
|
||||||
|
tools:listitem="@layout/item_known_user" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
72
vector/src/main/res/layout/item_known_user.xml
Normal file
72
vector/src/main/res/layout/item_known_user.xml
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?riotx_background"
|
||||||
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/knownUserAvatarContainer"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/knownUserAvatar"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/knownUserAvatarChecked"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
android:src="@drawable/ic_material_done"
|
||||||
|
android:tint="@android:color/white"
|
||||||
|
android:visibility="visible" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||||
|
android:id="@+id/knownUserName"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/knownUserID"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/knownUserAvatarContainer"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="@tools:sample/full_names" />
|
||||||
|
|
||||||
|
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||||
|
android:id="@+id/knownUserID"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?riotx_text_secondary"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/knownUserName"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/knownUserName"
|
||||||
|
tools:text="Blabla" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/userDirectoryLetterView"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:textColor="?attr/riotx_text_primary"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="normal"
|
||||||
|
tools:text="C" />
|
Loading…
Reference in a new issue