mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-01-20 13:38:34 +01:00
Direct chat : finalize flow
This commit is contained in:
parent
5af6bf3762
commit
76a9625f25
@ -31,7 +31,6 @@ import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.session.user.model.SearchUserTask
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import im.vector.matrix.android.internal.task.toConfigurableTask
|
||||
import im.vector.matrix.android.internal.util.fetchCopied
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -23,13 +23,16 @@ import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.EditText
|
||||
import androidx.annotation.DrawableRes
|
||||
import im.vector.riotx.R
|
||||
|
||||
fun EditText.setupAsSearch() {
|
||||
fun EditText.setupAsSearch(@DrawableRes searchIconRes: Int = R.drawable.ic_filter,
|
||||
@DrawableRes clearIconRes: Int = R.drawable.ic_x_green) {
|
||||
|
||||
addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(editable: Editable?) {
|
||||
val clearIcon = if (editable?.isNotEmpty() == true) R.drawable.ic_clear_white else 0
|
||||
setCompoundDrawablesWithIntrinsicBounds(0, 0, clearIcon, 0)
|
||||
val clearIcon = if (editable?.isNotEmpty() == true) clearIconRes else 0
|
||||
setCompoundDrawablesWithIntrinsicBounds(searchIconRes, 0, clearIcon, 0)
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
|
||||
|
@ -86,7 +86,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
||||
}
|
||||
|
||||
private fun renderCreationLoading() {
|
||||
updateWaitingView(WaitingViewData(getString(R.string.room_recents_create_room)))
|
||||
updateWaitingView(WaitingViewData(getString(R.string.creating_direct_room)))
|
||||
}
|
||||
|
||||
private fun renderCreationFailure(error: Throwable) {
|
||||
|
@ -18,19 +18,25 @@
|
||||
|
||||
package im.vector.riotx.features.home.createdirect
|
||||
|
||||
import arrow.core.Option
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Incomplete
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.*
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import im.vector.matrix.android.internal.util.firstLetterOfDisplayName
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.NoResultItem_
|
||||
import im.vector.riotx.core.epoxy.errorWithRetryItem
|
||||
import im.vector.riotx.core.epoxy.loadingItem
|
||||
import im.vector.riotx.core.epoxy.noResultItem
|
||||
import im.vector.riotx.core.error.ErrorFormatter
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
import javax.inject.Inject
|
||||
|
||||
class CreateDirectRoomController @Inject constructor(private val avatarRenderer: AvatarRenderer,
|
||||
class CreateDirectRoomController @Inject constructor(private val session: Session,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val stringProvider: StringProvider,
|
||||
private val errorFormatter: ErrorFormatter) : EpoxyController() {
|
||||
|
||||
private var state: CreateDirectRoomViewState? = null
|
||||
@ -49,15 +55,17 @@ class CreateDirectRoomController @Inject constructor(private val avatarRenderer:
|
||||
|
||||
override fun buildModels() {
|
||||
val currentState = state ?: return
|
||||
val hasSearch = currentState.searchTerm.isNotBlank()
|
||||
val asyncUsers = if (displayMode == CreateDirectRoomViewState.DisplayMode.DIRECTORY_USERS) {
|
||||
currentState.directoryUsers
|
||||
} else {
|
||||
currentState.knownUsers
|
||||
}
|
||||
when (asyncUsers) {
|
||||
is Incomplete -> renderLoading()
|
||||
is Success -> renderUsers(asyncUsers(), currentState.selectedUsers.map { it.userId })
|
||||
is Fail -> renderFailure(asyncUsers.error)
|
||||
is Uninitialized -> renderEmptyState(false)
|
||||
is Loading -> renderLoading()
|
||||
is Success -> renderSuccess(asyncUsers(), currentState.selectedUsers.map { it.userId }, hasSearch)
|
||||
is Fail -> renderFailure(asyncUsers.error)
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,9 +83,22 @@ class CreateDirectRoomController @Inject constructor(private val avatarRenderer:
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderSuccess(users: List<User>,
|
||||
selectedUsers: List<String>,
|
||||
hasSearch: Boolean) {
|
||||
if (users.isEmpty()) {
|
||||
renderEmptyState(hasSearch)
|
||||
} else {
|
||||
renderUsers(users, selectedUsers)
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderUsers(users: List<User>, selectedUsers: List<String>) {
|
||||
var lastFirstLetter: String? = null
|
||||
users.forEach { user ->
|
||||
for (user in users) {
|
||||
if (user.userId == session.myUserId) {
|
||||
continue
|
||||
}
|
||||
val isSelected = selectedUsers.contains(user.userId)
|
||||
val currentFirstLetter = user.displayName.firstLetterOfDisplayName()
|
||||
val showLetter = currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter
|
||||
@ -102,6 +123,22 @@ class CreateDirectRoomController @Inject constructor(private val avatarRenderer:
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderEmptyState(hasSearch: Boolean) {
|
||||
val noResultRes = if (displayMode == CreateDirectRoomViewState.DisplayMode.DIRECTORY_USERS) {
|
||||
if (hasSearch) {
|
||||
R.string.no_result_placeholder
|
||||
} else {
|
||||
R.string.direct_room_start_search
|
||||
}
|
||||
} else {
|
||||
R.string.direct_room_no_known_users
|
||||
}
|
||||
noResultItem {
|
||||
id("noResult")
|
||||
text(stringProvider.getString(noResultRes))
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onItemClick(user: User)
|
||||
fun retryDirectoryUsersRequest() {
|
||||
|
@ -21,6 +21,7 @@ import android.os.Bundle
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import im.vector.riotx.R
|
||||
@ -50,7 +51,6 @@ class CreateDirectRoomDirectoryUsersFragment : VectorBaseFragment(), CreateDirec
|
||||
setupRecyclerView()
|
||||
setupSearchByMatrixIdView()
|
||||
setupCloseView()
|
||||
viewModel.subscribe(this) { renderState(it) }
|
||||
}
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
@ -61,7 +61,7 @@ class CreateDirectRoomDirectoryUsersFragment : VectorBaseFragment(), CreateDirec
|
||||
}
|
||||
|
||||
private fun setupSearchByMatrixIdView() {
|
||||
createDirectRoomSearchById.setupAsSearch()
|
||||
createDirectRoomSearchById.setupAsSearch(searchIconRes = 0)
|
||||
createDirectRoomSearchById
|
||||
.textChanges()
|
||||
.subscribe {
|
||||
@ -80,8 +80,8 @@ class CreateDirectRoomDirectoryUsersFragment : VectorBaseFragment(), CreateDirec
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderState(state: CreateDirectRoomViewState) {
|
||||
directRoomController.setData(state)
|
||||
override fun invalidate() = withState(viewModel) {
|
||||
directRoomController.setData(it)
|
||||
}
|
||||
|
||||
override fun onItemClick(user: User) {
|
||||
|
@ -71,7 +71,6 @@ class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomControlle
|
||||
viewModel.selectSubscribe(this, CreateDirectRoomViewState::selectedUsers) {
|
||||
renderSelectedUsers(it)
|
||||
}
|
||||
viewModel.subscribe(this) { renderState(it) }
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
@ -133,8 +132,8 @@ class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomControlle
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderState(state: CreateDirectRoomViewState) {
|
||||
directRoomController.setData(state)
|
||||
override fun invalidate() = withState(viewModel) {
|
||||
directRoomController.setData(it)
|
||||
}
|
||||
|
||||
private fun updateChipsView(data: SelectUserAction) {
|
||||
@ -166,8 +165,8 @@ class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomControlle
|
||||
chip.setOnCloseIconClickListener {
|
||||
viewModel.handle(CreateDirectRoomActions.RemoveSelectedUser(user))
|
||||
}
|
||||
chipGroupContainer.post {
|
||||
chipGroupContainer.fullScroll(ScrollView.FOCUS_DOWN)
|
||||
chipGroupScrollView.post {
|
||||
chipGroupScrollView.fullScroll(ScrollView.FOCUS_DOWN)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,11 +28,14 @@ import com.squareup.inject.assisted.AssistedInject
|
||||
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.user.model.User
|
||||
import im.vector.matrix.android.internal.util.firstLetterOfDisplayName
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotx.core.extensions.postLiveEvent
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.functions.BiFunction
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@ -132,14 +135,20 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||
|
||||
private fun observeDirectoryUsers() {
|
||||
directoryUsersSearch
|
||||
.throttleLast(500, TimeUnit.MILLISECONDS)
|
||||
.debounce(300, TimeUnit.MILLISECONDS)
|
||||
.switchMapSingle { search ->
|
||||
session.rx()
|
||||
.searchUsersDirectory(search, 50, emptySet())
|
||||
.map { users ->
|
||||
users.sortedBy { it.displayName }
|
||||
}
|
||||
.toAsync { copy(directoryUsers = it) }
|
||||
val stream = if (search.isBlank()) {
|
||||
Single.just(emptyList())
|
||||
} else {
|
||||
session.rx()
|
||||
.searchUsersDirectory(search, 50, emptySet())
|
||||
.map { users ->
|
||||
users.sortedBy { it.displayName.firstLetterOfDisplayName() }
|
||||
}
|
||||
}
|
||||
stream.toAsync {
|
||||
copy(directoryUsers = it, searchTerm = search)
|
||||
}
|
||||
}
|
||||
.subscribe()
|
||||
.disposeOnClear()
|
||||
@ -157,7 +166,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||
} else {
|
||||
users.filter {
|
||||
it.displayName?.contains(filterValue, ignoreCase = true) ?: false
|
||||
|| it.userId.contains(filterValue, ignoreCase = true)
|
||||
|| it.userId.contains(filterValue, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
package im.vector.riotx.features.home.createdirect
|
||||
|
||||
import arrow.core.Option
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
@ -27,7 +28,9 @@ data class CreateDirectRoomViewState(
|
||||
val knownUsers: Async<List<User>> = Uninitialized,
|
||||
val directoryUsers: Async<List<User>> = Uninitialized,
|
||||
val selectedUsers: Set<User> = emptySet(),
|
||||
val createAndInviteState: Async<String> = Uninitialized
|
||||
val createAndInviteState: Async<String> = Uninitialized,
|
||||
val searchTerm: String = "",
|
||||
val filterKnownUsersValue: Option<String> = Option.empty()
|
||||
) : MvRxState {
|
||||
|
||||
enum class DisplayMode {
|
||||
|
@ -44,7 +44,7 @@
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@string/direct_chats_header"
|
||||
android:text="@string/fab_menu_create_chat"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
@ -59,7 +59,7 @@
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
<im.vector.riotx.core.platform.MaxHeightScrollView
|
||||
android:id="@+id/chipGroupContainer"
|
||||
android:id="@+id/chipGroupScrollView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||
@ -68,13 +68,13 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/createDirectRoomToolbar"
|
||||
app:maxHeight="80dp">
|
||||
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="4dp" />
|
||||
app:lineSpacing="2dp" />
|
||||
|
||||
</im.vector.riotx.core.platform.MaxHeightScrollView>
|
||||
|
||||
@ -85,21 +85,23 @@
|
||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
android:background="@null"
|
||||
android:hint="@string/room_directory_search_hint"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:hint="@string/direct_room_filter_hint"
|
||||
android:importantForAutofill="no"
|
||||
android:maxHeight="80dp"
|
||||
android:padding="8dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/chipGroupContainer" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/chipGroupScrollView" />
|
||||
|
||||
<View
|
||||
android:id="@+id/createDirectRoomFilterDivider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="?attr/vctr_list_divider_color"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
@ -65,7 +65,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@ -74,24 +74,32 @@
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/createDirectRoomSearchById"
|
||||
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/createDirectRoomFilterDivider"
|
||||
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/createDirectRoomSearchByIdContainer" />
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:fastScrollEnabled="true"
|
||||
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/createDirectRoomSearchByIdContainer"
|
||||
app:layout_constraintTop_toBottomOf="@+id/createDirectRoomFilterDivider"
|
||||
tools:listitem="@layout/item_create_direct_room_user" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -3,5 +3,8 @@
|
||||
|
||||
<!-- Strings not defined in Riot -->
|
||||
<string name="add_by_matrix_id">Add by matrix ID</string>
|
||||
|
||||
<string name="creating_direct_room">"Creating room…"</string>
|
||||
<string name="direct_room_no_known_users">"No result found, use Add by matrix ID to search on server."</string>
|
||||
<string name="direct_room_start_search">"Start typing to get results"</string>
|
||||
<string name="direct_room_filter_hint">"Filter by username or ID…"</string>
|
||||
</resources>
|
Loading…
Reference in New Issue
Block a user