Merge pull request #3235 from vector-im/feature/bca/spaces_justme_beta

Just me spaces support
This commit is contained in:
Benoit Marty 2021-04-29 17:47:19 +02:00 committed by GitHub
commit 5d9d984c73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 316 additions and 66 deletions

View File

@ -24,4 +24,12 @@ interface UpdatableLivePageResult {
val livePagedList: LiveData<PagedList<RoomSummary>> val livePagedList: LiveData<PagedList<RoomSummary>>
fun updateQuery(builder: (RoomSummaryQueryParams) -> RoomSummaryQueryParams) fun updateQuery(builder: (RoomSummaryQueryParams) -> RoomSummaryQueryParams)
val liveBoundaries: LiveData<ResultBoundaries>
} }
data class ResultBoundaries(
val frontLoaded: Boolean = false,
val endLoaded: Boolean = false,
val zeroItemLoaded: Boolean = false
)

View File

@ -18,6 +18,7 @@
package org.matrix.android.sdk.internal.session.room.summary package org.matrix.android.sdk.internal.session.room.summary
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
import androidx.paging.LivePagedListBuilder import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList import androidx.paging.PagedList
@ -28,6 +29,7 @@ import io.realm.Sort
import io.realm.kotlin.where import io.realm.kotlin.where
import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter
import org.matrix.android.sdk.api.session.room.ResultBoundaries
import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
@ -187,9 +189,25 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
roomSummaryMapper.map(it) roomSummaryMapper.map(it)
} }
val boundaries = MutableLiveData(ResultBoundaries())
val mapped = monarchy.findAllPagedWithChanges( val mapped = monarchy.findAllPagedWithChanges(
realmDataSourceFactory, realmDataSourceFactory,
LivePagedListBuilder(dataSourceFactory, pagedListConfig) LivePagedListBuilder(dataSourceFactory, pagedListConfig).also {
it.setBoundaryCallback(object : PagedList.BoundaryCallback<RoomSummary>() {
override fun onItemAtEndLoaded(itemAtEnd: RoomSummary) {
boundaries.postValue(boundaries.value?.copy(frontLoaded = true))
}
override fun onItemAtFrontLoaded(itemAtFront: RoomSummary) {
boundaries.postValue(boundaries.value?.copy(endLoaded = true))
}
override fun onZeroItemsLoaded() {
boundaries.postValue(boundaries.value?.copy(zeroItemLoaded = true))
}
})
}
) )
return object : UpdatableLivePageResult { return object : UpdatableLivePageResult {
@ -200,6 +218,9 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
roomSummariesQuery(it, builder.invoke(queryParams)).process(sortOrder) roomSummariesQuery(it, builder.invoke(queryParams)).process(sortOrder)
} }
} }
override val liveBoundaries: LiveData<ResultBoundaries>
get() = boundaries
} }
} }

View File

@ -48,6 +48,7 @@ import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs import im.vector.app.features.MainActivityArgs
import im.vector.app.features.disclaimer.showDisclaimerDialog import im.vector.app.features.disclaimer.showDisclaimerDialog
import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.matrixto.MatrixToBottomSheet
import im.vector.app.features.navigation.Navigator
import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.permalink.NavigationInterceptor import im.vector.app.features.permalink.NavigationInterceptor
import im.vector.app.features.permalink.PermalinkHandler import im.vector.app.features.permalink.PermalinkHandler
@ -115,11 +116,21 @@ class HomeActivity :
if (activityResult.resultCode == Activity.RESULT_OK) { if (activityResult.resultCode == Activity.RESULT_OK) {
val spaceId = SpaceCreationActivity.getCreatedSpaceId(activityResult.data) val spaceId = SpaceCreationActivity.getCreatedSpaceId(activityResult.data)
val defaultRoomId = SpaceCreationActivity.getDefaultRoomId(activityResult.data) val defaultRoomId = SpaceCreationActivity.getDefaultRoomId(activityResult.data)
val isJustMe = SpaceCreationActivity.isJustMeSpace(activityResult.data)
views.drawerLayout.closeDrawer(GravityCompat.START) views.drawerLayout.closeDrawer(GravityCompat.START)
val postSwitchOption: Navigator.PostSwitchSpaceAction = if (defaultRoomId != null) {
Navigator.PostSwitchSpaceAction.OpenDefaultRoom(defaultRoomId, !isJustMe)
} else if (isJustMe) {
Navigator.PostSwitchSpaceAction.OpenAddExistingRooms
} else {
Navigator.PostSwitchSpaceAction.None
}
// Here we want to change current space to the newly created one, and then immediately open the default room // Here we want to change current space to the newly created one, and then immediately open the default room
if (spaceId != null) { if (spaceId != null) {
navigator.switchToSpace(this, spaceId, defaultRoomId, true) navigator.switchToSpace(context = this,
spaceId = spaceId,
postSwitchOption)
} }
} }
} }
@ -494,7 +505,7 @@ class HomeActivity :
} }
override fun switchToSpace(spaceId: String) { override fun switchToSpace(spaceId: String) {
navigator.switchToSpace(this@HomeActivity, spaceId, null, false) navigator.switchToSpace(this@HomeActivity, spaceId, Navigator.PostSwitchSpaceAction.None)
} }
} }

View File

@ -74,6 +74,7 @@ import im.vector.app.features.share.SharedData
import im.vector.app.features.spaces.InviteRoomSpaceChooserBottomSheet import im.vector.app.features.spaces.InviteRoomSpaceChooserBottomSheet
import im.vector.app.features.spaces.SpaceExploreActivity import im.vector.app.features.spaces.SpaceExploreActivity
import im.vector.app.features.spaces.SpacePreviewActivity import im.vector.app.features.spaces.SpacePreviewActivity
import im.vector.app.features.spaces.manage.SpaceManageActivity
import im.vector.app.features.terms.ReviewTermsActivity import im.vector.app.features.terms.ReviewTermsActivity
import im.vector.app.features.widgets.WidgetActivity import im.vector.app.features.widgets.WidgetActivity
import im.vector.app.features.widgets.WidgetArgsBuilder import im.vector.app.features.widgets.WidgetArgsBuilder
@ -106,23 +107,33 @@ class DefaultNavigator @Inject constructor(
startActivity(context, intent, buildTask) startActivity(context, intent, buildTask)
} }
override fun switchToSpace(context: Context, spaceId: String, roomId: String?, openShareSheet: Boolean) { override fun switchToSpace(context: Context, spaceId: String, postSwitchSpaceAction: Navigator.PostSwitchSpaceAction) {
if (sessionHolder.getSafeActiveSession()?.getRoomSummary(spaceId) == null) { if (sessionHolder.getSafeActiveSession()?.getRoomSummary(spaceId) == null) {
fatalError("Trying to open an unknown space $spaceId", vectorPreferences.failFast()) fatalError("Trying to open an unknown space $spaceId", vectorPreferences.failFast())
return return
} }
appStateHandler.setCurrentSpace(spaceId) appStateHandler.setCurrentSpace(spaceId)
if (roomId != null) { when (postSwitchSpaceAction) {
val args = RoomDetailArgs(roomId, eventId = null, openShareSpaceForId = spaceId.takeIf { openShareSheet }) Navigator.PostSwitchSpaceAction.None -> {
val intent = RoomDetailActivity.newIntent(context, args)
startActivity(context, intent, false)
} else {
// go back to home if we are showing room details? // go back to home if we are showing room details?
// This is a bit ugly, but the navigator is supposed to know about the activity stack // This is a bit ugly, but the navigator is supposed to know about the activity stack
if (context is RoomDetailActivity) { if (context is RoomDetailActivity) {
context.finish() context.finish()
} }
} }
Navigator.PostSwitchSpaceAction.OpenAddExistingRooms -> {
startActivity(context, SpaceManageActivity.newIntent(context, spaceId), false)
}
is Navigator.PostSwitchSpaceAction.OpenDefaultRoom -> {
val args = RoomDetailArgs(
postSwitchSpaceAction.roomId,
eventId = null,
openShareSpaceForId = spaceId.takeIf { postSwitchSpaceAction.showShareSheet }
)
val intent = RoomDetailActivity.newIntent(context, args)
startActivity(context, intent, false)
}
}
} }
override fun openSpacePreview(context: Context, spaceId: String) { override fun openSpacePreview(context: Context, spaceId: String) {
@ -236,7 +247,7 @@ class DefaultNavigator @Inject constructor(
} }
override fun switchToSpace(spaceId: String) { override fun switchToSpace(spaceId: String) {
this@DefaultNavigator.switchToSpace(context, spaceId, null, openShareSheet = false) this@DefaultNavigator.switchToSpace(context, spaceId, Navigator.PostSwitchSpaceAction.None)
} }
} }
// TODO check if there is already one?? // TODO check if there is already one??

View File

@ -38,7 +38,13 @@ interface Navigator {
fun openRoom(context: Context, roomId: String, eventId: String? = null, buildTask: Boolean = false) fun openRoom(context: Context, roomId: String, eventId: String? = null, buildTask: Boolean = false)
fun switchToSpace(context: Context, spaceId: String, roomId: String?, openShareSheet: Boolean) sealed class PostSwitchSpaceAction {
object None : PostSwitchSpaceAction()
data class OpenDefaultRoom(val roomId: String, val showShareSheet: Boolean) : PostSwitchSpaceAction()
object OpenAddExistingRooms: PostSwitchSpaceAction()
}
fun switchToSpace(context: Context, spaceId: String, postSwitchSpaceAction: PostSwitchSpaceAction)
fun openSpacePreview(context: Context, spaceId: String) fun openSpacePreview(context: Context, spaceId: String)

View File

@ -47,7 +47,8 @@ import javax.inject.Inject
@Parcelize @Parcelize
data class CreateRoomArgs( data class CreateRoomArgs(
val initialName: String val initialName: String,
val parentSpaceId: String? = null
) : Parcelable ) : Parcelable
class CreateRoomFragment @Inject constructor( class CreateRoomFragment @Inject constructor(

View File

@ -42,8 +42,9 @@ import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
import timber.log.Timber
class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: CreateRoomViewState, class CreateRoomViewModel @AssistedInject constructor(@Assisted private val initialState: CreateRoomViewState,
private val session: Session, private val session: Session,
private val rawService: RawService private val rawService: RawService
) : VectorViewModel<CreateRoomViewState, CreateRoomAction, CreateRoomViewEvents>(initialState) { ) : VectorViewModel<CreateRoomViewState, CreateRoomAction, CreateRoomViewEvents>(initialState) {
@ -133,7 +134,8 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
CreateRoomViewState( CreateRoomViewState(
isEncrypted = adminE2EByDefault, isEncrypted = adminE2EByDefault,
hsAdminHasDisabledE2E = !adminE2EByDefault hsAdminHasDisabledE2E = !adminE2EByDefault,
parentSpaceId = initialState.parentSpaceId
) )
} }
@ -228,9 +230,21 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
// TODO: Should this be non-cancellable? // TODO: Should this be non-cancellable?
viewModelScope.launch { viewModelScope.launch {
val result = runCatching { session.createRoom(createRoomParams) } runCatching { session.createRoom(createRoomParams) }.fold(
result.fold(
{ roomId -> { roomId ->
if (initialState.parentSpaceId != null) {
// add it as a child
try {
val via = session.sessionParams.homeServerHost?.let { listOf(it) }.orEmpty()
session.spaceService()
.getSpace(initialState.parentSpaceId)
?.addChildren(roomId, viaServers = via, order = null)
} catch (failure: Throwable) {
Timber.w(failure, "Failed to add as a child")
}
}
setState { setState {
copy(asyncCreateRoomRequest = Success(roomId)) copy(asyncCreateRoomRequest = Success(roomId))
} }

View File

@ -32,11 +32,13 @@ data class CreateRoomViewState(
val disableFederation: Boolean = false, val disableFederation: Boolean = false,
val homeServerName: String = "", val homeServerName: String = "",
val hsAdminHasDisabledE2E: Boolean = false, val hsAdminHasDisabledE2E: Boolean = false,
val asyncCreateRoomRequest: Async<String> = Uninitialized val asyncCreateRoomRequest: Async<String> = Uninitialized,
val parentSpaceId: String?
) : MvRxState { ) : MvRxState {
constructor(args: CreateRoomArgs) : this( constructor(args: CreateRoomArgs) : this(
roomName = args.initialName roomName = args.initialName,
parentSpaceId = args.parentSpaceId
) )
/** /**

View File

@ -36,6 +36,7 @@ import im.vector.app.features.spaces.create.CreateSpaceDetailsFragment
import im.vector.app.features.spaces.create.CreateSpaceEvents import im.vector.app.features.spaces.create.CreateSpaceEvents
import im.vector.app.features.spaces.create.CreateSpaceState import im.vector.app.features.spaces.create.CreateSpaceState
import im.vector.app.features.spaces.create.CreateSpaceViewModel import im.vector.app.features.spaces.create.CreateSpaceViewModel
import im.vector.app.features.spaces.create.SpaceTopology
import im.vector.app.features.spaces.create.SpaceType import im.vector.app.features.spaces.create.SpaceType
import javax.inject.Inject import javax.inject.Inject
@ -106,6 +107,7 @@ class SpaceCreationActivity : SimpleFragmentActivity(), CreateSpaceViewModel.Fac
setResult(RESULT_OK, Intent().apply { setResult(RESULT_OK, Intent().apply {
putExtra(RESULT_DATA_CREATED_SPACE_ID, it.spaceId) putExtra(RESULT_DATA_CREATED_SPACE_ID, it.spaceId)
putExtra(RESULT_DATA_DEFAULT_ROOM_ID, it.defaultRoomId) putExtra(RESULT_DATA_DEFAULT_ROOM_ID, it.defaultRoomId)
putExtra(RESULT_DATA_CREATED_SPACE_IS_JUST_ME, it.topology == SpaceTopology.JustMe)
}) })
finish() finish()
} }
@ -155,6 +157,7 @@ class SpaceCreationActivity : SimpleFragmentActivity(), CreateSpaceViewModel.Fac
companion object { companion object {
private const val RESULT_DATA_CREATED_SPACE_ID = "RESULT_DATA_CREATED_SPACE_ID" private const val RESULT_DATA_CREATED_SPACE_ID = "RESULT_DATA_CREATED_SPACE_ID"
private const val RESULT_DATA_DEFAULT_ROOM_ID = "RESULT_DATA_DEFAULT_ROOM_ID" private const val RESULT_DATA_DEFAULT_ROOM_ID = "RESULT_DATA_DEFAULT_ROOM_ID"
private const val RESULT_DATA_CREATED_SPACE_IS_JUST_ME = "RESULT_DATA_CREATED_SPACE_IS_JUST_ME"
fun newIntent(context: Context): Intent { fun newIntent(context: Context): Intent {
return Intent(context, SpaceCreationActivity::class.java).apply { return Intent(context, SpaceCreationActivity::class.java).apply {
@ -169,6 +172,10 @@ class SpaceCreationActivity : SimpleFragmentActivity(), CreateSpaceViewModel.Fac
fun getDefaultRoomId(data: Intent?): String? { fun getDefaultRoomId(data: Intent?): String? {
return data?.extras?.getString(RESULT_DATA_DEFAULT_ROOM_ID) return data?.extras?.getString(RESULT_DATA_DEFAULT_ROOM_ID)
} }
fun isJustMeSpace(data: Intent?): Boolean {
return data?.extras?.getBoolean(RESULT_DATA_CREATED_SPACE_IS_JUST_ME, false) == true
}
} }
override fun create(initialState: CreateSpaceState): CreateSpaceViewModel = viewModelFactory.create(initialState) override fun create(initialState: CreateSpaceState): CreateSpaceViewModel = viewModelFactory.create(initialState)

View File

@ -117,7 +117,8 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomS
} }
views.addRooms.views.bottomSheetActionClickableZone.debouncedClicks { views.addRooms.views.bottomSheetActionClickableZone.debouncedClicks {
startActivity(SpaceManageActivity.newIntent(requireContext(), spaceArgs.spaceId)) dismiss()
startActivity(SpaceManageActivity.newIntent(requireActivity(), spaceArgs.spaceId))
} }
views.leaveSpace.views.bottomSheetActionClickableZone.debouncedClicks { views.leaveSpace.views.bottomSheetActionClickableZone.debouncedClicks {

View File

@ -42,8 +42,7 @@ class ChoosePrivateSpaceTypeFragment @Inject constructor(
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
views.justMeButton.setOnClickListener(DebouncedClickListener({ views.justMeButton.setOnClickListener(DebouncedClickListener({
vectorBaseActivity.notImplemented("Organize room as space is not yet implemented") sharedViewModel.handle(CreateSpaceAction.SetSpaceTopology(SpaceTopology.JustMe))
// sharedViewModel.handle(CreateSpaceAction.SetSpaceTopology(SpaceTopology.JustMe))
})) }))
views.teammatesButton.setOnClickListener(DebouncedClickListener({ views.teammatesButton.setOnClickListener(DebouncedClickListener({

View File

@ -24,7 +24,7 @@ sealed class CreateSpaceEvents : VectorViewEvents {
object NavigateToAddRooms : CreateSpaceEvents() object NavigateToAddRooms : CreateSpaceEvents()
object NavigateToChoosePrivateType : CreateSpaceEvents() object NavigateToChoosePrivateType : CreateSpaceEvents()
object Dismiss : CreateSpaceEvents() object Dismiss : CreateSpaceEvents()
data class FinishSuccess(val spaceId: String, val defaultRoomId: String?) : CreateSpaceEvents() data class FinishSuccess(val spaceId: String, val defaultRoomId: String?, val topology: SpaceTopology?) : CreateSpaceEvents()
data class ShowModalError(val errorMessage: String) : CreateSpaceEvents() data class ShowModalError(val errorMessage: String) : CreateSpaceEvents()
object HideModalLoading : CreateSpaceEvents() object HideModalLoading : CreateSpaceEvents()
} }

View File

@ -126,10 +126,11 @@ class CreateSpaceViewModel @AssistedInject constructor(
SpaceTopology.JustMe -> { SpaceTopology.JustMe -> {
setState { setState {
copy( copy(
spaceTopology = SpaceTopology.JustMe spaceTopology = SpaceTopology.JustMe,
defaultRooms = emptyMap()
) )
} }
// XXX finish and open the add rooms directly handleNextFromDefaultRooms()
} }
SpaceTopology.MeAndTeammates -> { SpaceTopology.MeAndTeammates -> {
setState { setState {
@ -237,14 +238,26 @@ class CreateSpaceViewModel @AssistedInject constructor(
setState { setState {
copy(creationResult = Success(result.spaceId)) copy(creationResult = Success(result.spaceId))
} }
_viewEvents.post(CreateSpaceEvents.FinishSuccess(result.spaceId, result.childIds.firstOrNull())) _viewEvents.post(
CreateSpaceEvents.FinishSuccess(
result.spaceId,
result.childIds.firstOrNull(),
state.spaceTopology
)
)
} }
is CreateSpaceTaskResult.PartialSuccess -> { is CreateSpaceTaskResult.PartialSuccess -> {
// XXX what can we do here? // XXX what can we do here?
setState { setState {
copy(creationResult = Success(result.spaceId)) copy(creationResult = Success(result.spaceId))
} }
_viewEvents.post(CreateSpaceEvents.FinishSuccess(result.spaceId, result.childIds.firstOrNull())) _viewEvents.post(
CreateSpaceEvents.FinishSuccess(
result.spaceId,
result.childIds.firstOrNull(),
state.spaceTopology
)
)
} }
is CreateSpaceTaskResult.FailedToCreateSpace -> { is CreateSpaceTaskResult.FailedToCreateSpace -> {
setState { setState {

View File

@ -22,6 +22,8 @@ import com.airbnb.epoxy.paging.PagedListEpoxyController
import im.vector.app.core.utils.DebouncedClickListener import im.vector.app.core.utils.DebouncedClickListener
import im.vector.app.core.utils.createUIHandler import im.vector.app.core.utils.createUIHandler
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.list.RoomCategoryItem_
import org.matrix.android.sdk.api.session.room.ResultBoundaries
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
@ -54,6 +56,26 @@ class AddRoomListController @Inject constructor(
var listener: Listener? = null var listener: Listener? = null
var ignoreRooms: List<String>? = null var ignoreRooms: List<String>? = null
var initialLoadOccurred = false
fun boundaryChange(boundary: ResultBoundaries) {
val boundaryHasLoadedSomething = boundary.frontLoaded || boundary.zeroItemLoaded
if (initialLoadOccurred != boundaryHasLoadedSomething) {
initialLoadOccurred = boundaryHasLoadedSomething
requestForcedModelBuild()
}
}
var sectionName: String? = null
set(value) {
if (value != field) {
field = value
requestForcedModelBuild()
}
}
var totalSize: Int = 0
var selectedItems: Map<String, Boolean> = emptyMap() var selectedItems: Map<String, Boolean> = emptyMap()
set(value) { set(value) {
field = value field = value
@ -62,13 +84,27 @@ class AddRoomListController @Inject constructor(
} }
override fun addModels(models: List<EpoxyModel<*>>) { override fun addModels(models: List<EpoxyModel<*>>) {
if (ignoreRooms == null) { val filteredModel = if (ignoreRooms == null) {
super.addModels(models) models
} else { } else {
super.addModels(
models.filter { models.filter {
it !is RoomSelectionItem || !ignoreRooms!!.contains(it.matrixItem.id) it !is RoomSelectionItem || !ignoreRooms!!.contains(it.matrixItem.id)
} }
}
val somethingToShow = filteredModel.isNotEmpty() || !initialLoadOccurred
if (somethingToShow || filteredModel.isNotEmpty()) {
add(
RoomCategoryItem_().apply {
id("header")
title(sectionName ?: "")
expanded(true)
}
)
}
super.addModels(filteredModel)
if (!initialLoadOccurred) {
add(
RoomSelectionPlaceHolderItem_().apply { id("loading") }
) )
} }
} }

View File

@ -16,6 +16,9 @@
package im.vector.app.features.spaces.manage package im.vector.app.features.spaces.manage
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
@ -23,23 +26,30 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyViewHolder
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.jakewharton.rxbinding3.appcompat.queryTextChanges import com.jakewharton.rxbinding3.appcompat.queryTextChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentSpaceAddRoomsBinding import im.vector.app.databinding.FragmentSpaceAddRoomsBinding
import im.vector.app.features.home.room.list.RoomCategoryItem_
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
class SpaceAddRoomFragment @Inject constructor( class SpaceAddRoomFragment @Inject constructor(
private val epoxyController: AddRoomListController, private val spaceEpoxyController: AddRoomListController,
private val roomEpoxyController: AddRoomListController,
private val viewModelFactory: SpaceAddRoomsViewModel.Factory private val viewModelFactory: SpaceAddRoomsViewModel.Factory
) : VectorBaseFragment<FragmentSpaceAddRoomsBinding>(), ) : VectorBaseFragment<FragmentSpaceAddRoomsBinding>(),
OnBackPressed, AddRoomListController.Listener, SpaceAddRoomsViewModel.Factory { OnBackPressed, AddRoomListController.Listener, SpaceAddRoomsViewModel.Factory {
@ -79,7 +89,8 @@ class SpaceAddRoomFragment @Inject constructor(
.disposeOnDestroyView() .disposeOnDestroyView()
viewModel.selectionListLiveData.observe(viewLifecycleOwner) { viewModel.selectionListLiveData.observe(viewLifecycleOwner) {
epoxyController.selectedItems = it spaceEpoxyController.selectedItems = it
roomEpoxyController.selectedItems = it
saveNeeded = it.values.any { it } saveNeeded = it.values.any { it }
invalidateOptionsMenu() invalidateOptionsMenu()
} }
@ -89,7 +100,8 @@ class SpaceAddRoomFragment @Inject constructor(
}.disposeOnDestroyView() }.disposeOnDestroyView()
viewModel.selectSubscribe(this, SpaceAddRoomsState::ignoreRooms) { viewModel.selectSubscribe(this, SpaceAddRoomsState::ignoreRooms) {
epoxyController.ignoreRooms = it spaceEpoxyController.ignoreRooms = it
roomEpoxyController.ignoreRooms = it
}.disposeOnDestroyView() }.disposeOnDestroyView()
viewModel.selectSubscribe(this, SpaceAddRoomsState::isSaving) { viewModel.selectSubscribe(this, SpaceAddRoomsState::isSaving) {
@ -100,9 +112,9 @@ class SpaceAddRoomFragment @Inject constructor(
} }
}.disposeOnDestroyView() }.disposeOnDestroyView()
// views.createNewRoom.debouncedClicks { views.createNewRoom.debouncedClicks {
// sharedActionViewModel.post(RoomDirectorySharedAction.CreateRoom) sharedViewModel.handle(SpaceManagedSharedAction.CreateRoom)
// } }
viewModel.observeViewEvents { viewModel.observeViewEvents {
when (it) { when (it) {
@ -142,16 +154,71 @@ class SpaceAddRoomFragment @Inject constructor(
override fun onDestroyView() { override fun onDestroyView() {
views.roomList.cleanup() views.roomList.cleanup()
epoxyController.listener = null spaceEpoxyController.listener = null
roomEpoxyController.listener = null
super.onDestroyView() super.onDestroyView()
} }
private fun setupRecyclerView() { private fun setupRecyclerView() {
views.roomList.configureWith(epoxyController, showDivider = true) val concatAdapter = ConcatAdapter()
epoxyController.listener = this spaceEpoxyController.sectionName = getString(R.string.spaces_header)
viewModel.updatableLivePageResult.livePagedList.observe(viewLifecycleOwner) { roomEpoxyController.sectionName = getString(R.string.rooms_header)
epoxyController.submitList(it) spaceEpoxyController.listener = this
roomEpoxyController.listener = this
viewModel.updatableLiveSpacePageResult.liveBoundaries.observe(viewLifecycleOwner) {
spaceEpoxyController.boundaryChange(it)
} }
viewModel.updatableLiveSpacePageResult.livePagedList.observe(viewLifecycleOwner) {
spaceEpoxyController.totalSize = it.size
spaceEpoxyController.submitList(it)
}
viewModel.updatableLivePageResult.liveBoundaries.observe(viewLifecycleOwner) {
roomEpoxyController.boundaryChange(it)
}
viewModel.updatableLivePageResult.livePagedList.observe(viewLifecycleOwner) {
roomEpoxyController.totalSize = it.size
roomEpoxyController.submitList(it)
}
views.roomList.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
views.roomList.addItemDecoration(
object : DividerItemDecoration(context, VERTICAL) {
val decorationDrawable = ContextCompat.getDrawable(requireContext(), R.drawable.divider_horizontal)
override fun getDrawable(): Drawable? {
return decorationDrawable
}
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val position = parent.getChildAdapterPosition(view)
val vh = parent.findViewHolderForAdapterPosition(position)
val nextIsSectionOrFinal = parent.findViewHolderForAdapterPosition(position + 1)?.let {
(it as? EpoxyViewHolder)?.model is RoomCategoryItem_
} ?: true
if (vh == null
|| (vh as? EpoxyViewHolder)?.model is RoomCategoryItem_
|| nextIsSectionOrFinal
) {
outRect.setEmpty()
} else {
super.getItemOffsets(outRect, view, parent, state)
}
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(c, parent, state)
}
}
)
views.roomList.setHasFixedSize(true)
concatAdapter.addAdapter(roomEpoxyController.adapter)
concatAdapter.addAdapter(spaceEpoxyController.adapter)
views.roomList.adapter = concatAdapter
} }
override fun onBackPressed(toolbarButton: Boolean): Boolean { override fun onBackPressed(toolbarButton: Boolean): Boolean {

View File

@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
class AddRoomError(val errorList: Map<String, Throwable>) : Throwable() { class AddRoomError(val errorList: Map<String, Throwable>) : Throwable() {
@ -53,11 +54,31 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
private val session: Session private val session: Session
) : VectorViewModel<SpaceAddRoomsState, SpaceAddRoomActions, SpaceAddRoomsViewEvents>(initialState) { ) : VectorViewModel<SpaceAddRoomsState, SpaceAddRoomActions, SpaceAddRoomsViewEvents>(initialState) {
val updatableLivePageResult: UpdatableLivePageResult by lazy { val updatableLiveSpacePageResult: UpdatableLivePageResult by lazy {
session.getFilteredPagedRoomSummariesLive( session.getFilteredPagedRoomSummariesLive(
roomSummaryQueryParams { roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN) this.memberships = listOf(Membership.JOIN)
this.excludeType = null this.excludeType = null
this.includeType = listOf(RoomType.SPACE)
this.activeSpaceId = ActiveSpaceFilter.ExcludeSpace(initialState.spaceId)
this.displayName = QueryStringValue.Contains(initialState.currentFilter, QueryStringValue.Case.INSENSITIVE)
},
pagedListConfig = PagedList.Config.Builder()
.setPageSize(10)
.setInitialLoadSizeHint(20)
.setEnablePlaceholders(true)
.setPrefetchDistance(10)
.build(),
sortOrder = RoomSortOrder.NAME
)
}
val updatableLivePageResult: UpdatableLivePageResult by lazy {
session.getFilteredPagedRoomSummariesLive(
roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN)
this.excludeType = listOf(RoomType.SPACE)
this.includeType = null
this.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS this.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
this.activeSpaceId = ActiveSpaceFilter.ExcludeSpace(initialState.spaceId) this.activeSpaceId = ActiveSpaceFilter.ExcludeSpace(initialState.spaceId)
this.displayName = QueryStringValue.Contains(initialState.currentFilter, QueryStringValue.Case.INSENSITIVE) this.displayName = QueryStringValue.Contains(initialState.currentFilter, QueryStringValue.Case.INSENSITIVE)
@ -116,6 +137,11 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE)
) )
} }
updatableLiveSpacePageResult.updateQuery {
it.copy(
displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE)
)
}
setState { setState {
copy( copy(
currentFilter = action.filter currentFilter = action.filter

View File

@ -25,9 +25,14 @@ import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.viewModel
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ScreenComponent import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.roomdirectory.RoomDirectorySharedAction
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
import im.vector.app.features.roomdirectory.createroom.CreateRoomArgs
import im.vector.app.features.roomdirectory.createroom.CreateRoomFragment
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import javax.inject.Inject import javax.inject.Inject
@ -39,6 +44,7 @@ data class SpaceManageArgs(
class SpaceManageActivity : VectorBaseActivity<ActivitySimpleBinding>(), SpaceManageSharedViewModel.Factory { class SpaceManageActivity : VectorBaseActivity<ActivitySimpleBinding>(), SpaceManageSharedViewModel.Factory {
@Inject lateinit var sharedViewModelFactory: SpaceManageSharedViewModel.Factory @Inject lateinit var sharedViewModelFactory: SpaceManageSharedViewModel.Factory
private lateinit var sharedDirectoryActionViewModel: RoomDirectorySharedActionViewModel
override fun injectWith(injector: ScreenComponent) { override fun injectWith(injector: ScreenComponent) {
injector.inject(this) injector.inject(this)
@ -53,9 +59,20 @@ class SpaceManageActivity : VectorBaseActivity<ActivitySimpleBinding>(), SpaceMa
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
sharedDirectoryActionViewModel = viewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
sharedDirectoryActionViewModel
.observe()
.subscribe { sharedAction ->
when (sharedAction) {
is RoomDirectorySharedAction.Back,
is RoomDirectorySharedAction.Close -> finish()
}
}
.disposeOnDestroy()
val args = intent?.getParcelableExtra<SpaceManageArgs>(MvRx.KEY_ARG)
if (isFirstCreation()) { if (isFirstCreation()) {
val simpleName = SpaceAddRoomFragment::class.java.simpleName val simpleName = SpaceAddRoomFragment::class.java.simpleName
val args = intent?.getParcelableExtra<SpaceManageArgs>(MvRx.KEY_ARG)
if (supportFragmentManager.findFragmentByTag(simpleName) == null) { if (supportFragmentManager.findFragmentByTag(simpleName) == null) {
supportFragmentManager.commitTransaction { supportFragmentManager.commitTransaction {
replace(R.id.simpleFragmentContainer, replace(R.id.simpleFragmentContainer,
@ -78,6 +95,13 @@ class SpaceManageActivity : VectorBaseActivity<ActivitySimpleBinding>(), SpaceMa
SpaceManagedSharedViewEvents.ShowLoading -> { SpaceManagedSharedViewEvents.ShowLoading -> {
views.simpleActivityWaitingView.isVisible = true views.simpleActivityWaitingView.isVisible = true
} }
SpaceManagedSharedViewEvents.NavigateToCreateRoom -> {
addFragmentToBackstack(
R.id.simpleFragmentContainer,
CreateRoomFragment::class.java,
CreateRoomArgs("", parentSpaceId = args?.spaceId)
)
}
} }
} }
} }

View File

@ -54,6 +54,7 @@ class SpaceManageSharedViewModel @AssistedInject constructor(
} }
SpaceManagedSharedAction.HideLoading -> _viewEvents.post(SpaceManagedSharedViewEvents.HideLoading) SpaceManagedSharedAction.HideLoading -> _viewEvents.post(SpaceManagedSharedViewEvents.HideLoading)
SpaceManagedSharedAction.ShowLoading -> _viewEvents.post(SpaceManagedSharedViewEvents.ShowLoading) SpaceManagedSharedAction.ShowLoading -> _viewEvents.post(SpaceManagedSharedViewEvents.ShowLoading)
SpaceManagedSharedAction.CreateRoom -> _viewEvents.post(SpaceManagedSharedViewEvents.NavigateToCreateRoom)
} }
} }
} }

View File

@ -22,4 +22,5 @@ sealed class SpaceManagedSharedAction : VectorViewModelAction {
object HandleBack : SpaceManagedSharedAction() object HandleBack : SpaceManagedSharedAction()
object ShowLoading : SpaceManagedSharedAction() object ShowLoading : SpaceManagedSharedAction()
object HideLoading : SpaceManagedSharedAction() object HideLoading : SpaceManagedSharedAction()
object CreateRoom : SpaceManagedSharedAction()
} }

View File

@ -22,4 +22,5 @@ sealed class SpaceManagedSharedViewEvents : VectorViewEvents {
object Finish : SpaceManagedSharedViewEvents() object Finish : SpaceManagedSharedViewEvents()
object ShowLoading : SpaceManagedSharedViewEvents() object ShowLoading : SpaceManagedSharedViewEvents()
object HideLoading : SpaceManagedSharedViewEvents() object HideLoading : SpaceManagedSharedViewEvents()
object NavigateToCreateRoom : SpaceManagedSharedViewEvents()
} }

View File

@ -82,21 +82,21 @@
app:layout_constraintTop_toBottomOf="@+id/addRoomToSpaceToolbar" app:layout_constraintTop_toBottomOf="@+id/addRoomToSpaceToolbar"
app:queryHint="@string/search_hint_room_name" /> app:queryHint="@string/search_hint_room_name" />
<!-- <com.google.android.material.button.MaterialButton--> <com.google.android.material.button.MaterialButton
<!-- android:id="@+id/createNewRoom"--> android:id="@+id/createNewRoom"
<!-- style="@style/VectorButtonStyleText"--> style="@style/VectorButtonStyleText"
<!-- android:layout_width="wrap_content"--> android:layout_width="wrap_content"
<!-- android:layout_height="wrap_content"--> android:layout_height="wrap_content"
<!-- android:layout_marginStart="16dp"--> android:layout_marginStart="16dp"
<!-- android:layout_marginTop="8dp"--> android:layout_marginTop="8dp"
<!-- android:layout_marginBottom="8dp"--> android:layout_marginBottom="8dp"
<!-- android:minHeight="@dimen/layout_touch_size"--> android:minHeight="@dimen/layout_touch_size"
<!-- android:text="@string/create_new_room"--> android:text="@string/create_new_room"
<!-- app:icon="@drawable/ic_plus_circle"--> app:icon="@drawable/ic_plus_circle"
<!-- app:iconPadding="13dp"--> app:iconPadding="13dp"
<!-- app:iconTint="@color/riotx_accent"--> app:iconTint="@color/riotx_accent"
<!-- app:layout_constraintStart_toStartOf="parent"--> app:layout_constraintStart_toStartOf="parent"
<!-- app:layout_constraintTop_toBottomOf="@+id/addRoomToSpaceToolbar" />--> app:layout_constraintTop_toBottomOf="@+id/addRoomToSpaceToolbar" />
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>