Add "Create room" shortcut in Explore Space screen

This commit is contained in:
Valere 2021-09-28 13:15:51 +02:00
parent 30aae3f07a
commit ff4bbf0a8a
16 changed files with 231 additions and 45 deletions

1
changelog.d/3932.bugfix Normal file
View file

@ -0,0 +1 @@
Explore Rooms overflow menu - content update include "Create room"

View file

@ -309,8 +309,8 @@ class DefaultNavigator @Inject constructor(
}
}
override fun openCreateRoom(context: Context, initialName: String) {
val intent = CreateRoomActivity.getIntent(context, initialName)
override fun openCreateRoom(context: Context, initialName: String, openAfterCreate: Boolean) {
val intent = CreateRoomActivity.getIntent(context = context, initialName = initialName, openAfterCreate = openAfterCreate)
context.startActivity(intent)
}

View file

@ -76,7 +76,7 @@ interface Navigator {
fun openMatrixToBottomSheet(context: Context, link: String)
fun openCreateRoom(context: Context, initialName: String = "")
fun openCreateRoom(context: Context, initialName: String = "", openAfterCreate: Boolean = true)
fun openCreateDirectRoom(context: Context)

View file

@ -25,5 +25,6 @@ sealed class RoomDirectorySharedAction : VectorSharedAction {
object Back : RoomDirectorySharedAction()
object CreateRoom : RoomDirectorySharedAction()
object Close : RoomDirectorySharedAction()
data class CreateRoomSuccess(val createdRoomId: String) : RoomDirectorySharedAction()
object ChangeProtocol : RoomDirectorySharedAction()
}

View file

@ -16,10 +16,12 @@
package im.vector.app.features.roomdirectory.createroom
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.Mavericks
import com.google.android.material.appbar.MaterialToolbar
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.extensions.addFragment
@ -49,13 +51,11 @@ class CreateRoomActivity : VectorBaseActivity<ActivitySimpleBinding>(), ToolbarC
override fun initUiAndData() {
if (isFirstCreation()) {
val fragmentArgs: CreateRoomArgs = intent?.extras?.getParcelable(Mavericks.KEY_ARG) ?: return
addFragment(
views.simpleFragmentContainer,
CreateRoomFragment::class.java,
CreateRoomArgs(
intent?.getStringExtra(INITIAL_NAME) ?: "",
isSpace = intent?.getBooleanExtra(IS_SPACE, false) ?: false
)
fragmentArgs
)
}
}
@ -68,20 +68,35 @@ class CreateRoomActivity : VectorBaseActivity<ActivitySimpleBinding>(), ToolbarC
.onEach { sharedAction ->
when (sharedAction) {
is RoomDirectorySharedAction.Back,
is RoomDirectorySharedAction.Close -> finish()
is RoomDirectorySharedAction.Close -> finish()
is RoomDirectorySharedAction.CreateRoomSuccess -> {
setResult(Activity.RESULT_OK, Intent().apply { putExtra(RESULT_CREATED_ROOM_ID, sharedAction.createdRoomId) })
finish()
}
else -> {
// nop
}
}
}
.launchIn(lifecycleScope)
}
companion object {
private const val INITIAL_NAME = "INITIAL_NAME"
private const val IS_SPACE = "IS_SPACE"
fun getIntent(context: Context, initialName: String = "", isSpace: Boolean = false): Intent {
const val RESULT_CREATED_ROOM_ID = "RESULT_CREATED_ROOM_ID"
fun getIntent(context: Context,
initialName: String = "",
isSpace: Boolean = false,
openAfterCreate: Boolean = true,
currentSpaceId: String? = null): Intent {
return Intent(context, CreateRoomActivity::class.java).apply {
putExtra(INITIAL_NAME, initialName)
putExtra(IS_SPACE, isSpace)
putExtra(Mavericks.KEY_ARG, CreateRoomArgs(
initialName = initialName,
isSpace = isSpace,
openAfterCreate = openAfterCreate,
parentSpaceId = currentSpaceId
))
}
}
}

View file

@ -56,7 +56,8 @@ import javax.inject.Inject
data class CreateRoomArgs(
val initialName: String,
val parentSpaceId: String? = null,
val isSpace: Boolean = false
val isSpace: Boolean = false,
val openAfterCreate: Boolean = true
) : Parcelable
class CreateRoomFragment @Inject constructor(
@ -226,16 +227,19 @@ class CreateRoomFragment @Inject constructor(
views.waitingView.root.isVisible = async is Loading
if (async is Success) {
// Navigate to freshly created room
if (state.isSubSpace) {
navigator.switchToSpace(
requireContext(),
async(),
Navigator.PostSwitchSpaceAction.None
)
} else {
navigator.openRoom(requireActivity(), async())
if (state.openAfterCreate) {
if (state.isSubSpace) {
navigator.switchToSpace(
requireContext(),
async(),
Navigator.PostSwitchSpaceAction.None
)
} else {
navigator.openRoom(requireActivity(), async())
}
}
sharedActionViewModel.post(RoomDirectorySharedAction.CreateRoomSuccess(async()))
sharedActionViewModel.post(RoomDirectorySharedAction.Close)
} else {
// Populate list with Epoxy

View file

@ -27,6 +27,7 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.AppStateHandler
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.raw.wellknown.getElementWellknown
@ -52,10 +53,11 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset
import timber.log.Timber
class CreateRoomViewModel @AssistedInject constructor(@Assisted private val initialState: CreateRoomViewState,
class CreateRoomViewModel @AssistedInject constructor(@Assisted val initialState: CreateRoomViewState,
private val session: Session,
private val rawService: RawService,
vectorPreferences: VectorPreferences
private val vectorPreferences: VectorPreferences,
appStateHandler: AppStateHandler
) : VectorViewModel<CreateRoomViewState, CreateRoomAction, CreateRoomViewEvents>(initialState) {
@AssistedFactory
@ -69,6 +71,8 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
initHomeServerName()
initAdminE2eByDefault()
val parentSpaceId = initialState.parentSpaceId ?: appStateHandler.safeActiveSpaceId()
val restrictedSupport = session.getHomeServerCapabilities().isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED)
val createRestricted = when (restrictedSupport) {
HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED -> true
@ -76,7 +80,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
else -> false
}
val defaultJoinRules = if (initialState.parentSpaceId != null && createRestricted) {
val defaultJoinRules = if (parentSpaceId != null && createRestricted) {
RoomJoinRules.RESTRICTED
} else {
RoomJoinRules.INVITE
@ -84,9 +88,10 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
setState {
copy(
parentSpaceId = parentSpaceId,
supportsRestricted = createRestricted,
roomJoinRules = defaultJoinRules,
parentSpaceSummary = initialState.parentSpaceId?.let { session.getRoomSummary(it) }
parentSpaceSummary = parentSpaceId?.let { session.getRoomSummary(it) }
)
}
}
@ -162,7 +167,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
CreateRoomViewState(
isEncrypted = adminE2EByDefault,
hsAdminHasDisabledE2E = !adminE2EByDefault,
parentSpaceId = initialState.parentSpaceId
parentSpaceId = this.parentSpaceId
)
}
@ -298,11 +303,11 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
runCatching { session.createRoom(createRoomParams) }.fold(
{ roomId ->
if (initialState.parentSpaceId != null) {
if (state.parentSpaceId != null) {
// add it as a child
try {
session.spaceService()
.getSpace(initialState.parentSpaceId)
.getSpace(state.parentSpaceId)
?.addChildren(roomId, viaServers = null, order = null)
} catch (failure: Throwable) {
Timber.w(failure, "Failed to add as a child")

View file

@ -39,13 +39,15 @@ data class CreateRoomViewState(
val parentSpaceSummary: RoomSummary? = null,
val supportsRestricted: Boolean = false,
val aliasLocalPart: String? = null,
val isSubSpace: Boolean = false
val isSubSpace: Boolean = false,
val openAfterCreate: Boolean = true
) : MavericksState {
constructor(args: CreateRoomArgs) : this(
roomName = args.initialName,
parentSpaceId = args.parentSpaceId,
isSubSpace = args.isSpace
isSubSpace = args.isSpace,
openAfterCreate = args.openAfterCreate
)
/**

View file

@ -16,6 +16,7 @@
package im.vector.app.features.spaces
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
@ -25,13 +26,18 @@ import com.airbnb.mvrx.Mavericks
import com.airbnb.mvrx.viewModel
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.matrixto.MatrixToBottomSheet
import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity
import im.vector.app.features.navigation.Navigator
import im.vector.app.features.spaces.explore.SpaceDirectoryArgs
import im.vector.app.features.spaces.explore.SpaceDirectoryFragment
import im.vector.app.features.spaces.explore.SpaceDirectoryState
import im.vector.app.features.spaces.explore.SpaceDirectoryViewAction
import im.vector.app.features.spaces.explore.SpaceDirectoryViewEvents
import im.vector.app.features.spaces.explore.SpaceDirectoryViewModel
@ -44,6 +50,15 @@ class SpaceExploreActivity : VectorBaseActivity<ActivitySimpleBinding>(), Matrix
val sharedViewModel: SpaceDirectoryViewModel by viewModel()
private val createRoomResultLauncher = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
activityResult.data?.extras?.getString(CreateRoomActivity.RESULT_CREATED_ROOM_ID)?.let {
// we want to refresh from API
sharedViewModel.handle(SpaceDirectoryViewAction.RefreshUntilFound(it))
}
}
}
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentResumed(fm: FragmentManager, f: Fragment) {
if (f is MatrixToBottomSheet) {
@ -84,6 +99,13 @@ class SpaceExploreActivity : VectorBaseActivity<ActivitySimpleBinding>(), Matrix
is SpaceDirectoryViewEvents.NavigateToMxToBottomSheet -> {
MatrixToBottomSheet.withLink(it.link).show(supportFragmentManager, "ShowChild")
}
is SpaceDirectoryViewEvents.NavigateToCreateNewRoom -> {
createRoomResultLauncher.launch(CreateRoomActivity.getIntent(
this,
openAfterCreate = false,
currentSpaceId = it.currentSpaceId
))
}
}
}
}

View file

@ -89,6 +89,9 @@ class SpaceDirectoryFragment @Inject constructor(
SpaceAddRoomSpaceChooserBottomSheet.ACTION_ADD_SPACES -> {
addExistingRoomActivityResult.launch(SpaceManageActivity.newIntent(requireContext(), spaceId, ManageType.AddRoomsOnlySpaces))
}
SpaceAddRoomSpaceChooserBottomSheet.ACTION_CREATE_ROOM -> {
viewModel.handle(SpaceDirectoryViewAction.CreateNewRoom)
}
else -> {
// nop
}
@ -114,6 +117,12 @@ class SpaceDirectoryFragment @Inject constructor(
invalidateOptionsMenu()
}
views.addOrCreateChatRoomButton.debouncedClicks {
withState(viewModel) {
addExistingRooms(it.spaceId)
}
}
views.spaceCard.matrixToCardMainButton.isVisible = false
views.spaceCard.matrixToCardSecondaryButton.isVisible = false
}
@ -142,6 +151,7 @@ class SpaceDirectoryFragment @Inject constructor(
}
spaceCardRenderer.render(state.currentRootSummary, emptyList(), this, views.spaceCard)
views.addOrCreateChatRoomButton.isVisible = state.canAddRooms
}
override fun onPrepareOptionsMenu(menu: Menu) = withState(viewModel) { state ->

View file

@ -24,7 +24,9 @@ sealed class SpaceDirectoryViewAction : VectorViewModelAction {
data class JoinOrOpen(val spaceChildInfo: SpaceChildInfo) : SpaceDirectoryViewAction()
data class ShowDetails(val spaceChildInfo: SpaceChildInfo) : SpaceDirectoryViewAction()
data class NavigateToRoom(val roomId: String) : SpaceDirectoryViewAction()
object CreateNewRoom : SpaceDirectoryViewAction()
object HandleBack : SpaceDirectoryViewAction()
object Retry : SpaceDirectoryViewAction()
data class RefreshUntilFound(val roomIdToFind: String) : SpaceDirectoryViewAction()
object LoadAdditionalItemsIfNeeded : SpaceDirectoryViewAction()
}

View file

@ -22,4 +22,5 @@ sealed class SpaceDirectoryViewEvents : VectorViewEvents {
object Dismiss : SpaceDirectoryViewEvents()
data class NavigateToRoom(val roomId: String) : SpaceDirectoryViewEvents()
data class NavigateToMxToBottomSheet(val link: String) : SpaceDirectoryViewEvents()
data class NavigateToCreateNewRoom(val currentSpaceId: String) : SpaceDirectoryViewEvents()
}

View file

@ -71,6 +71,27 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
observeJoinedRooms()
observeMembershipChanges()
observePermissions()
observeKnownSummaries()
}
private fun observeKnownSummaries() {
// A we prefer to use known summaries to have better name resolution
// it's important to have them up to date. Particularly after creation where
// resolved name is sometimes just "New Room"
session.rx().liveRoomSummaries(
roomSummaryQueryParams {
memberships = listOf(Membership.JOIN)
includeType = null
}
).execute {
val updatedRoomSummaries = it
copy(
knownRoomSummaries = this.knownRoomSummaries.map { rs ->
updatedRoomSummaries.invoke()?.firstOrNull { it.roomId == rs.roomId }
?: rs
}
)
}
}
private fun observePermissions() {
@ -103,7 +124,7 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
try {
val query = session.spaceService().querySpaceChildren(
spaceId,
limit = 10
limit = PAGE_LENGTH
)
val knownSummaries = query.children.mapNotNull {
session.getRoomSummary(it.childRoomId)
@ -181,9 +202,17 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
SpaceDirectoryViewAction.Retry -> {
handleRetry()
}
is SpaceDirectoryViewAction.RefreshUntilFound -> {
handleRefreshUntilFound(action.roomIdToFind)
}
SpaceDirectoryViewAction.LoadAdditionalItemsIfNeeded -> {
loadAdditionalItemsIfNeeded()
}
is SpaceDirectoryViewAction.CreateNewRoom -> {
withState { state ->
_viewEvents.post(SpaceDirectoryViewEvents.NavigateToCreateNewRoom(state.currentRootSummary?.roomId ?: initialState.spaceId))
}
}
}
}
@ -207,6 +236,66 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
refreshFromApi(state.hierarchyStack.lastOrNull() ?: initialState.spaceId)
}
private fun handleRefreshUntilFound(roomIdToFind: String?) = withState { state ->
val currentRootId = state.hierarchyStack.lastOrNull() ?: initialState.spaceId
val mutablePaginationStatus = state.paginationStatus.toMutableMap().apply {
this[currentRootId] = Loading()
}
// mark as paginating
setState {
copy(
paginationStatus = mutablePaginationStatus
)
}
viewModelScope.launch(Dispatchers.IO) {
var query = session.spaceService().querySpaceChildren(
currentRootId,
limit = PAGE_LENGTH
)
var knownSummaries = query.children.mapNotNull {
session.getRoomSummary(it.childRoomId)
?.takeIf { it.membership == Membership.JOIN } // only take if joined because it will be up to date (synced)
}.distinctBy { it.roomId }
while (!query.children.any { it.childRoomId == roomIdToFind } && query.nextToken != null) {
// continue to paginate until found
val paginate = session.spaceService().querySpaceChildren(
currentRootId,
limit = PAGE_LENGTH,
from = query.nextToken,
knownStateList = query.childrenState
)
knownSummaries = (
knownSummaries
+ (paginate.children.mapNotNull {
session.getRoomSummary(it.childRoomId)
?.takeIf { it.membership == Membership.JOIN } // only take if joined because it will be up to date (synced)
})
).distinctBy { it.roomId }
query = query.copy(
children = query.children + paginate.children,
nextToken = paginate.nextToken
)
}
setState {
copy(
apiResults = this.apiResults.toMutableMap().apply {
this[currentRootId] = Success(query)
},
paginationStatus = this.paginationStatus.toMutableMap().apply { this[currentRootId] = Success(Unit) }.toMap(),
knownRoomSummaries = (state.knownRoomSummaries + knownSummaries).distinctBy { it.roomId },
)
}
}
}
private fun handleExploreSubSpace(action: SpaceDirectoryViewAction.ExploreSubSpace) = withState { state ->
val newRootId = action.spaceChildInfo.childRoomId
val curSum = RoomSummary(
@ -252,7 +341,9 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
if (mutablePaginationStatus[currentRootId] is Loading) return@withState
setState {
copy(paginationStatus = mutablePaginationStatus.toMap())
copy(paginationStatus = mutablePaginationStatus.apply {
this[currentRootId] = Loading()
})
}
viewModelScope.launch(Dispatchers.IO) {
@ -268,7 +359,7 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
}
val query = session.spaceService().querySpaceChildren(
currentRootId,
limit = 10,
limit = PAGE_LENGTH,
from = currentResponse.nextToken,
knownStateList = currentResponse.childrenState
)

View file

@ -34,6 +34,13 @@ class SpaceAddRoomSpaceChooserBottomSheet : VectorBaseBottomSheetDialogFragment<
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
views.createRooms.views.bottomSheetActionClickableZone.debouncedClicks {
setFragmentResult(REQUEST_KEY, Bundle().apply {
putString(BUNDLE_KEY_ACTION, ACTION_CREATE_ROOM)
})
dismiss()
}
views.addSpaces.views.bottomSheetActionClickableZone.debouncedClicks {
setFragmentResult(REQUEST_KEY, Bundle().apply {
putString(BUNDLE_KEY_ACTION, ACTION_ADD_SPACES)
@ -55,6 +62,7 @@ class SpaceAddRoomSpaceChooserBottomSheet : VectorBaseBottomSheetDialogFragment<
const val BUNDLE_KEY_ACTION = "SpaceAddRoomSpaceChooserBottomSheet.Action"
const val ACTION_ADD_ROOMS = "Action.AddRoom"
const val ACTION_ADD_SPACES = "Action.AddSpaces"
const val ACTION_CREATE_ROOM = "Action.CreateRoom"
fun newInstance(): SpaceAddRoomSpaceChooserBottomSheet {
return SpaceAddRoomSpaceChooserBottomSheet()

View file

@ -7,24 +7,34 @@
android:background="?android:colorBackground"
android:orientation="vertical">
<TextView
android:id="@+id/headerText"
style="@style/Widget.Vector.TextView.Subtitle"
<!-- <TextView-->
<!-- android:id="@+id/headerText"-->
<!-- style="@style/Widget.Vector.TextView.Subtitle"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginTop="16dp"-->
<!-- android:layout_marginBottom="16dp"-->
<!-- android:gravity="center"-->
<!-- android:text="@string/space_add_rooms"-->
<!-- android:textColor="?vctr_content_primary"-->
<!-- android:textStyle="bold"-->
<!-- app:layout_constraintTop_toTopOf="parent" />-->
<im.vector.app.core.ui.views.BottomSheetActionButton
android:id="@+id/createRooms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:gravity="center"
android:text="@string/space_add_existing_rooms"
android:textColor="?vctr_content_primary"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="parent" />
app:actionTitle="@string/create_new_room"
app:leftIcon="@drawable/ic_fab_add"
app:tint="?vctr_content_primary"
app:titleTextColor="?vctr_content_primary"
tools:actionDescription="" />
<im.vector.app.core.ui.views.BottomSheetActionButton
android:id="@+id/addRooms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:actionTitle="@string/space_add_child_title"
app:actionTitle="@string/space_add_existing_rooms_only"
app:leftIcon="@drawable/ic_fab_add"
app:tint="?vctr_content_primary"
app:titleTextColor="?vctr_content_primary"

View file

@ -51,4 +51,18 @@
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/item_room_directory" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/addOrCreateChatRoomButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp "
android:contentDescription="@string/a11y_create_room"
android:scaleType="center"
app:maxImageSize="20dp"
android:src="@drawable/ic_fab_add"
android:visibility="visible"
tools:visibility="visible" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>