Merge pull request #638 from vector-im/feature/filter

Fix 2 issues with share Activity: filter and room lists
This commit is contained in:
Benoit Marty 2019-10-24 14:03:08 +02:00 committed by GitHub
commit e52f0faaa7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 251 additions and 41 deletions

View file

@ -42,7 +42,7 @@ import javax.inject.Singleton
@Singleton @Singleton
class AppStateHandler @Inject constructor( class AppStateHandler @Inject constructor(
private val sessionObservableStore: ActiveSessionObservableStore, private val sessionObservableStore: ActiveSessionObservableStore,
private val homeRoomListStore: HomeRoomListObservableStore, private val homeRoomListObservableStore: HomeRoomListObservableStore,
private val selectedGroupStore: SelectedGroupStore) : LifecycleObserver { private val selectedGroupStore: SelectedGroupStore) : LifecycleObserver {
private val compositeDisposable = CompositeDisposable() private val compositeDisposable = CompositeDisposable()
@ -92,7 +92,7 @@ class AppStateHandler @Inject constructor(
} }
) )
.subscribe { .subscribe {
homeRoomListStore.post(it) homeRoomListObservableStore.post(it)
} }
.addTo(compositeDisposable) .addTo(compositeDisposable)
} }

View file

@ -41,11 +41,12 @@ import im.vector.riotx.features.home.createdirect.CreateDirectRoomKnownUsersFrag
import im.vector.riotx.features.home.group.GroupListFragment import im.vector.riotx.features.home.group.GroupListFragment
import im.vector.riotx.features.home.room.detail.RoomDetailFragment import im.vector.riotx.features.home.room.detail.RoomDetailFragment
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.action.* import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
import im.vector.riotx.features.home.room.list.RoomListFragment import im.vector.riotx.features.home.room.list.RoomListFragment
import im.vector.riotx.features.home.room.list.RoomListModule
import im.vector.riotx.features.invite.VectorInviteView import im.vector.riotx.features.invite.VectorInviteView
import im.vector.riotx.features.link.LinkHandlerActivity import im.vector.riotx.features.link.LinkHandlerActivity
import im.vector.riotx.features.login.LoginActivity import im.vector.riotx.features.login.LoginActivity
@ -70,7 +71,17 @@ import im.vector.riotx.features.settings.push.PushGatewaysFragment
import im.vector.riotx.features.share.IncomingShareActivity import im.vector.riotx.features.share.IncomingShareActivity
import im.vector.riotx.features.ui.UiStateRepository import im.vector.riotx.features.ui.UiStateRepository
@Component(dependencies = [VectorComponent::class], modules = [AssistedInjectModule::class, ViewModelModule::class, HomeModule::class]) @Component(
dependencies = [
VectorComponent::class
],
modules = [
AssistedInjectModule::class,
ViewModelModule::class,
HomeModule::class,
RoomListModule::class
]
)
@ScreenScope @ScreenScope
interface ScreenComponent { interface ScreenComponent {

View file

@ -23,6 +23,7 @@ import dagger.Component
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.auth.Authenticator import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.riotx.ActiveSessionObservableStore
import im.vector.riotx.EmojiCompatFontProvider import im.vector.riotx.EmojiCompatFontProvider
import im.vector.riotx.EmojiCompatWrapper import im.vector.riotx.EmojiCompatWrapper
import im.vector.riotx.VectorApplication import im.vector.riotx.VectorApplication
@ -42,6 +43,7 @@ import im.vector.riotx.features.rageshake.VectorFileLogger
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotx.features.session.SessionListener import im.vector.riotx.features.session.SessionListener
import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.settings.VectorPreferences
import im.vector.riotx.features.share.ShareRoomListObservableStore
import im.vector.riotx.features.ui.UiStateRepository import im.vector.riotx.features.ui.UiStateRepository
import javax.inject.Singleton import javax.inject.Singleton
@ -85,8 +87,12 @@ interface VectorComponent {
fun homeRoomListObservableStore(): HomeRoomListObservableStore fun homeRoomListObservableStore(): HomeRoomListObservableStore
fun shareRoomListObservableStore(): ShareRoomListObservableStore
fun selectedGroupStore(): SelectedGroupStore fun selectedGroupStore(): SelectedGroupStore
fun activeSessionObservableStore(): ActiveSessionObservableStore
fun incomingVerificationRequestHandler(): IncomingVerificationRequestHandler fun incomingVerificationRequestHandler(): IncomingVerificationRequestHandler
fun incomingKeyRequestHandler(): KeyRequestHandler fun incomingKeyRequestHandler(): KeyRequestHandler

View file

@ -0,0 +1,27 @@
/*
* 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.
* 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.home.room.list
import dagger.Binds
import dagger.Module
@Module
abstract class RoomListModule {
@Binds
abstract fun providesRoomListViewModelFactory(roomListViewModelFactory: RoomListViewModelFactory): RoomListViewModel.Factory
}

View file

@ -21,8 +21,6 @@ import androidx.lifecycle.MutableLiveData
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
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.Membership import im.vector.matrix.android.api.session.room.model.Membership
@ -31,18 +29,18 @@ import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.utils.LiveEvent import im.vector.riotx.core.utils.LiveEvent
import im.vector.riotx.features.home.HomeRoomListObservableStore import im.vector.riotx.core.utils.RxStore
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject
class RoomListViewModel @AssistedInject constructor(@Assisted initialState: RoomListViewState, class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
private val session: Session, private val session: Session,
private val homeRoomListObservableSource: HomeRoomListObservableStore, private val roomSummariesStore: RxStore<List<RoomSummary>>,
private val alphabeticalRoomComparator: AlphabeticalRoomComparator, private val alphabeticalRoomComparator: AlphabeticalRoomComparator,
private val chronologicalRoomComparator: ChronologicalRoomComparator) private val chronologicalRoomComparator: ChronologicalRoomComparator)
: VectorViewModel<RoomListViewState>(initialState) { : VectorViewModel<RoomListViewState>(initialState) {
@AssistedInject.Factory
interface Factory { interface Factory {
fun create(initialState: RoomListViewState): RoomListViewModel fun create(initialState: RoomListViewState): RoomListViewModel
} }
@ -101,7 +99,7 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
} }
private fun observeRoomSummaries() { private fun observeRoomSummaries() {
homeRoomListObservableSource roomSummariesStore
.observe() .observe()
.observeOn(Schedulers.computation()) .observeOn(Schedulers.computation())
.map { .map {
@ -111,7 +109,7 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
copy(asyncRooms = asyncRooms) copy(asyncRooms = asyncRooms)
} }
homeRoomListObservableSource roomSummariesStore
.observe() .observe()
.observeOn(Schedulers.computation()) .observeOn(Schedulers.computation())
.map { buildRoomSummaries(it) } .map { buildRoomSummaries(it) }

View file

@ -0,0 +1,39 @@
/*
* 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.
* 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.home.room.list
import im.vector.matrix.android.api.session.Session
import im.vector.riotx.features.home.HomeRoomListObservableStore
import im.vector.riotx.features.share.ShareRoomListObservableStore
import javax.inject.Inject
import javax.inject.Provider
class RoomListViewModelFactory @Inject constructor(private val session: Provider<Session>,
private val homeRoomListObservableStore: Provider<HomeRoomListObservableStore>,
private val shareRoomListObservableStore: Provider<ShareRoomListObservableStore>,
private val alphabeticalRoomComparator: Provider<AlphabeticalRoomComparator>,
private val chronologicalRoomComparator: Provider<ChronologicalRoomComparator>) : RoomListViewModel.Factory {
override fun create(initialState: RoomListViewState): RoomListViewModel {
return RoomListViewModel(
initialState,
session.get(),
if (initialState.displayMode == RoomListFragment.DisplayMode.SHARE) shareRoomListObservableStore.get() else homeRoomListObservableStore.get(),
alphabeticalRoomComparator.get(),
chronologicalRoomComparator.get())
}
}

View file

@ -20,6 +20,8 @@ import androidx.annotation.StringRes
import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyController
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.noResultItem
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.room.filtered.FilteredRoomFooterItem import im.vector.riotx.features.home.room.filtered.FilteredRoomFooterItem
import im.vector.riotx.features.home.room.filtered.filteredRoomFooterItem import im.vector.riotx.features.home.room.filtered.filteredRoomFooterItem
@ -47,24 +49,28 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
override fun buildModels() { override fun buildModels() {
val nonNullViewState = viewState ?: return val nonNullViewState = viewState ?: return
if (nonNullViewState.displayMode == RoomListFragment.DisplayMode.FILTERED) { when (nonNullViewState.displayMode) {
buildFilteredRooms(nonNullViewState) RoomListFragment.DisplayMode.FILTERED,
} else { RoomListFragment.DisplayMode.SHARE -> {
val roomSummaries = nonNullViewState.asyncFilteredRooms() buildFilteredRooms(nonNullViewState)
roomSummaries?.forEach { (category, summaries) -> }
if (summaries.isEmpty()) { else -> {
return@forEach val roomSummaries = nonNullViewState.asyncFilteredRooms()
} else { roomSummaries?.forEach { (category, summaries) ->
val isExpanded = nonNullViewState.isCategoryExpanded(category) if (summaries.isEmpty()) {
buildRoomCategory(nonNullViewState, summaries, category.titleRes, nonNullViewState.isCategoryExpanded(category)) { return@forEach
listener?.onToggleRoomCategory(category) } else {
} val isExpanded = nonNullViewState.isCategoryExpanded(category)
if (isExpanded) { buildRoomCategory(nonNullViewState, summaries, category.titleRes, nonNullViewState.isCategoryExpanded(category)) {
buildRoomModels(summaries, listener?.onToggleRoomCategory(category)
nonNullViewState.joiningRoomsIds, }
nonNullViewState.joiningErrorRoomsIds, if (isExpanded) {
nonNullViewState.rejectingRoomsIds, buildRoomModels(summaries,
nonNullViewState.rejectingErrorRoomsIds) nonNullViewState.joiningRoomsIds,
nonNullViewState.joiningErrorRoomsIds,
nonNullViewState.rejectingRoomsIds,
nonNullViewState.rejectingErrorRoomsIds)
}
} }
} }
} }
@ -80,12 +86,15 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
.filter { it.membership == Membership.JOIN && roomListNameFilter.test(it) } .filter { it.membership == Membership.JOIN && roomListNameFilter.test(it) }
buildRoomModels(filteredSummaries, buildRoomModels(filteredSummaries,
viewState.joiningRoomsIds, viewState.joiningRoomsIds,
viewState.joiningErrorRoomsIds, viewState.joiningErrorRoomsIds,
viewState.rejectingRoomsIds, viewState.rejectingRoomsIds,
viewState.rejectingErrorRoomsIds) viewState.rejectingErrorRoomsIds)
addFilterFooter(viewState) when {
viewState.displayMode == RoomListFragment.DisplayMode.FILTERED -> addFilterFooter(viewState)
filteredSummaries.isEmpty() -> addEmptyFooter()
}
} }
private fun addFilterFooter(viewState: RoomListViewState) { private fun addFilterFooter(viewState: RoomListViewState) {
@ -96,6 +105,13 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
} }
} }
private fun addEmptyFooter() {
noResultItem {
id("no_result")
text(stringProvider.getString(R.string.no_result_placeholder))
}
}
private fun buildRoomCategory(viewState: RoomListViewState, private fun buildRoomCategory(viewState: RoomListViewState,
summaries: List<RoomSummary>, summaries: List<RoomSummary>,
@StringRes titleRes: Int, @StringRes titleRes: Int,

View file

@ -20,6 +20,8 @@ import android.content.ClipDescription
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.widget.SearchView
import com.airbnb.mvrx.viewModel
import com.kbeanie.multipicker.utils.IntentUtils import com.kbeanie.multipicker.utils.IntentUtils
import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.riotx.R import im.vector.riotx.R
@ -39,8 +41,10 @@ class IncomingShareActivity :
VectorBaseActivity(), AttachmentsHelper.Callback { VectorBaseActivity(), AttachmentsHelper.Callback {
@Inject lateinit var sessionHolder: ActiveSessionHolder @Inject lateinit var sessionHolder: ActiveSessionHolder
private lateinit var roomListFragment: RoomListFragment @Inject lateinit var incomingShareViewModelFactory: IncomingShareViewModel.Factory
private var roomListFragment: RoomListFragment? = null
private lateinit var attachmentsHelper: AttachmentsHelper private lateinit var attachmentsHelper: AttachmentsHelper
private val incomingShareViewModel: IncomingShareViewModel by viewModel()
override fun getLayoutRes(): Int { override fun getLayoutRes(): Int {
return R.layout.activity_incoming_share return R.layout.activity_incoming_share
@ -77,12 +81,23 @@ class IncomingShareActivity :
} else { } else {
cannotManageShare() cannotManageShare()
} }
incomingShareSearchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
return true
}
override fun onQueryTextChange(newText: String): Boolean {
roomListFragment?.filterRoomsWith(newText)
return true
}
})
} }
override fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>) { override fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>) {
val roomListParams = RoomListParams(RoomListFragment.DisplayMode.SHARE, sharedData = SharedData.Attachments(attachments)) val roomListParams = RoomListParams(RoomListFragment.DisplayMode.SHARE, sharedData = SharedData.Attachments(attachments))
roomListFragment = RoomListFragment.newInstance(roomListParams) roomListFragment = RoomListFragment.newInstance(roomListParams)
replaceFragment(roomListFragment, R.id.shareRoomListFragmentContainer) .also { replaceFragment(it, R.id.shareRoomListFragmentContainer) }
} }
override fun onAttachmentsProcessFailed() { override fun onAttachmentsProcessFailed() {
@ -102,7 +117,7 @@ class IncomingShareActivity :
} else { } else {
val roomListParams = RoomListParams(RoomListFragment.DisplayMode.SHARE, sharedData = SharedData.Text(sharedText)) val roomListParams = RoomListParams(RoomListFragment.DisplayMode.SHARE, sharedData = SharedData.Text(sharedText))
roomListFragment = RoomListFragment.newInstance(roomListParams) roomListFragment = RoomListFragment.newInstance(roomListParams)
replaceFragment(roomListFragment, R.id.shareRoomListFragmentContainer) .also { replaceFragment(it, R.id.shareRoomListFragmentContainer) }
true true
} }
} }

View file

@ -0,0 +1,73 @@
/*
* 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.
* 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.share
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.rx.rx
import im.vector.riotx.ActiveSessionObservableStore
import im.vector.riotx.core.platform.VectorViewModel
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import java.util.concurrent.TimeUnit
data class IncomingShareState(private val dummy: Boolean = false) : MvRxState
/**
* View model used to observe the room list and post update to the ShareRoomListObservableStore
*/
class IncomingShareViewModel @AssistedInject constructor(@Assisted initialState: IncomingShareState,
private val sessionObservableStore: ActiveSessionObservableStore,
private val shareRoomListObservableStore: ShareRoomListObservableStore)
: VectorViewModel<IncomingShareState>(initialState) {
@AssistedInject.Factory
interface Factory {
fun create(initialState: IncomingShareState): IncomingShareViewModel
}
companion object : MvRxViewModelFactory<IncomingShareViewModel, IncomingShareState> {
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: IncomingShareState): IncomingShareViewModel? {
val activity: IncomingShareActivity = (viewModelContext as ActivityViewModelContext).activity()
return activity.incomingShareViewModelFactory.create(state)
}
}
init {
observeRoomSummaries()
}
private fun observeRoomSummaries() {
sessionObservableStore.observe()
.observeOn(AndroidSchedulers.mainThread())
.switchMap {
it.orNull()?.rx()?.liveRoomSummaries()
?: Observable.just(emptyList())
}
.throttleLast(300, TimeUnit.MILLISECONDS)
.subscribe {
shareRoomListObservableStore.post(it)
}
.disposeOnClear()
}
}

View file

@ -0,0 +1,25 @@
/*
* 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.
* 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.share
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotx.core.utils.RxStore
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ShareRoomListObservableStore @Inject constructor() : RxStore<List<RoomSummary>>()