From 99123bf0cc72b9c9da82af45da431c8d2c0323a5 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 7 Sep 2021 14:10:22 +0200 Subject: [PATCH 001/155] Add invite by email screen in create space flow --- changelog.d/3678.feature | 1 + changelog.d/3945.bugfix | 1 + .../im/vector/app/core/di/FragmentModule.kt | 6 + .../features/spaces/SpaceCreationActivity.kt | 5 + .../spaces/create/BetaWarningBottomSheet.kt | 46 ------- .../create/ChoosePrivateSpaceTypeFragment.kt | 10 +- .../spaces/create/CreateSpaceAction.kt | 2 + .../CreateSpaceAdd3pidInvitesFragment.kt | 86 ++++++++++++ .../spaces/create/CreateSpaceEvents.kt | 1 + .../spaces/create/CreateSpaceState.kt | 8 +- .../spaces/create/CreateSpaceViewModel.kt | 126 +++++++++++++++--- .../spaces/create/CreateSpaceViewModelTask.kt | 37 +++-- .../create/SpaceAdd3pidEpoxyController.kt | 99 ++++++++++++++ ...tom_sheet_space_create_private_warning.xml | 46 ------- vector/src/main/res/values/strings.xml | 7 + 15 files changed, 348 insertions(+), 133 deletions(-) create mode 100644 changelog.d/3678.feature create mode 100644 changelog.d/3945.bugfix delete mode 100644 vector/src/main/java/im/vector/app/features/spaces/create/BetaWarningBottomSheet.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAdd3pidInvitesFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/create/SpaceAdd3pidEpoxyController.kt delete mode 100644 vector/src/main/res/layout/bottom_sheet_space_create_private_warning.xml diff --git a/changelog.d/3678.feature b/changelog.d/3678.feature new file mode 100644 index 0000000000..7889cafd7d --- /dev/null +++ b/changelog.d/3678.feature @@ -0,0 +1 @@ +Spaces | M3.23 Invite by email in create private space flow \ No newline at end of file diff --git a/changelog.d/3945.bugfix b/changelog.d/3945.bugfix new file mode 100644 index 0000000000..caedcc9cba --- /dev/null +++ b/changelog.d/3945.bugfix @@ -0,0 +1 @@ +Remove the "Teammate spaces aren't quite ready" bottom sheet \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index 6b42f1e428..ede5a8d5f3 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -142,6 +142,7 @@ import im.vector.app.features.signout.soft.SoftLogoutFragment import im.vector.app.features.spaces.SpaceListFragment import im.vector.app.features.spaces.create.ChoosePrivateSpaceTypeFragment import im.vector.app.features.spaces.create.ChooseSpaceTypeFragment +import im.vector.app.features.spaces.create.CreateSpaceAdd3pidInvitesFragment import im.vector.app.features.spaces.create.CreateSpaceDefaultRoomsFragment import im.vector.app.features.spaces.create.CreateSpaceDetailsFragment import im.vector.app.features.spaces.explore.SpaceDirectoryFragment @@ -793,6 +794,11 @@ interface FragmentModule { @FragmentKey(ChoosePrivateSpaceTypeFragment::class) fun bindChoosePrivateSpaceTypeFragment(fragment: ChoosePrivateSpaceTypeFragment): Fragment + @Binds + @IntoMap + @FragmentKey(CreateSpaceAdd3pidInvitesFragment::class) + fun bindCreateSpaceAdd3pidInvitesFragment(fragment: CreateSpaceAdd3pidInvitesFragment): Fragment + @Binds @IntoMap @FragmentKey(SpaceAddRoomFragment::class) diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt index a02755a155..1aae661d51 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt @@ -31,6 +31,7 @@ import im.vector.app.core.platform.SimpleFragmentActivity import im.vector.app.features.spaces.create.ChoosePrivateSpaceTypeFragment import im.vector.app.features.spaces.create.ChooseSpaceTypeFragment import im.vector.app.features.spaces.create.CreateSpaceAction +import im.vector.app.features.spaces.create.CreateSpaceAdd3pidInvitesFragment import im.vector.app.features.spaces.create.CreateSpaceDefaultRoomsFragment import im.vector.app.features.spaces.create.CreateSpaceDetailsFragment import im.vector.app.features.spaces.create.CreateSpaceEvents @@ -92,6 +93,9 @@ class SpaceCreationActivity : SimpleFragmentActivity(), CreateSpaceViewModel.Fac CreateSpaceEvents.NavigateToAddRooms -> { navigateToFragment(CreateSpaceDefaultRoomsFragment::class.java) } + CreateSpaceEvents.NavigateToAdd3Pid -> { + navigateToFragment(CreateSpaceAdd3pidInvitesFragment::class.java) + } CreateSpaceEvents.NavigateToChoosePrivateType -> { navigateToFragment(ChoosePrivateSpaceTypeFragment::class.java) } @@ -143,6 +147,7 @@ class SpaceCreationActivity : SimpleFragmentActivity(), CreateSpaceViewModel.Fac if (state.spaceType == SpaceType.Public) R.string.your_public_space else R.string.your_private_space } + CreateSpaceState.Step.AddEmailsOrInvites, CreateSpaceState.Step.ChoosePrivateType -> R.string.your_private_space } supportActionBar?.let { diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/BetaWarningBottomSheet.kt b/vector/src/main/java/im/vector/app/features/spaces/create/BetaWarningBottomSheet.kt deleted file mode 100644 index fc3d98e6b8..0000000000 --- a/vector/src/main/java/im/vector/app/features/spaces/create/BetaWarningBottomSheet.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2021 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.app.features.spaces.create - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.setFragmentResult -import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment -import im.vector.app.databinding.BottomSheetSpaceCreatePrivateWarningBinding - -class BetaWarningBottomSheet : VectorBaseBottomSheetDialogFragment() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = - BottomSheetSpaceCreatePrivateWarningBinding.inflate(inflater, container, false) - - override val showExpanded = true - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - views.continueButton.debouncedClicks { - setFragmentResult(REQUEST_KEY, Bundle.EMPTY) - dismiss() - } - } - - companion object { - const val REQUEST_KEY = "BetaWarningBottomSheet" - } -} diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/ChoosePrivateSpaceTypeFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/ChoosePrivateSpaceTypeFragment.kt index 4031b56c1d..4f079551eb 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/ChoosePrivateSpaceTypeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/ChoosePrivateSpaceTypeFragment.kt @@ -20,7 +20,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.fragment.app.setFragmentResultListener import com.airbnb.mvrx.activityViewModel import im.vector.app.R import im.vector.app.core.epoxy.onClick @@ -39,13 +38,6 @@ class ChoosePrivateSpaceTypeFragment @Inject constructor( override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = FragmentSpaceCreateChoosePrivateModelBinding.inflate(layoutInflater, container, false) - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setFragmentResultListener(BetaWarningBottomSheet.REQUEST_KEY) { _, _ -> - sharedViewModel.handle(CreateSpaceAction.SetSpaceTopology(SpaceTopology.MeAndTeammates)) - } - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -54,7 +46,7 @@ class ChoosePrivateSpaceTypeFragment @Inject constructor( } views.teammatesButton.onClick { - BetaWarningBottomSheet().show(parentFragmentManager, "warning") + sharedViewModel.handle(CreateSpaceAction.SetSpaceTopology(SpaceTopology.MeAndTeammates)) } sharedViewModel.subscribe { state -> diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAction.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAction.kt index 1f0ed6428f..e2eaa5784f 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAction.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAction.kt @@ -28,6 +28,8 @@ sealed class CreateSpaceAction : VectorViewModelAction { object OnBackPressed : CreateSpaceAction() object NextFromDetails : CreateSpaceAction() object NextFromDefaultRooms : CreateSpaceAction() + object NextFromAdd3pid : CreateSpaceAction() data class DefaultRoomNameChanged(val index: Int, val name: String) : CreateSpaceAction() + data class DefaultInvite3pidChanged(val index: Int, val email: String) : CreateSpaceAction() data class SetSpaceTopology(val topology: SpaceTopology) : CreateSpaceAction() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAdd3pidInvitesFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAdd3pidInvitesFragment.kt new file mode 100644 index 0000000000..98b04dde7c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAdd3pidInvitesFragment.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2021 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.app.features.spaces.create + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.airbnb.mvrx.activityViewModel +import im.vector.app.R +import im.vector.app.core.extensions.configureWith +import im.vector.app.core.extensions.hideKeyboard +import im.vector.app.core.platform.OnBackPressed +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentSpaceCreateGenericEpoxyFormBinding +import im.vector.app.features.settings.VectorSettingsActivity +import javax.inject.Inject + +class CreateSpaceAdd3pidInvitesFragment @Inject constructor( + private val epoxyController: SpaceAdd3pidEpoxyController +) : VectorBaseFragment(), + SpaceAdd3pidEpoxyController.Listener, + OnBackPressed { + + private val sharedViewModel: CreateSpaceViewModel by activityViewModel() + + override fun onBackPressed(toolbarButton: Boolean): Boolean { + sharedViewModel.handle(CreateSpaceAction.OnBackPressed) + return true + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + views.recyclerView.configureWith(epoxyController) + epoxyController.listener = this + + sharedViewModel.subscribe(this) { + invalidateState(it) + } + + views.nextButton.setText(R.string.next_pf) + views.nextButton.debouncedClicks { + view.hideKeyboard() + sharedViewModel.handle(CreateSpaceAction.NextFromAdd3pid) + } + } + + private fun invalidateState(it: CreateSpaceState) { + epoxyController.setData(it) + val noEmails = it.default3pidInvite?.all { it.value.isNullOrBlank() } ?: true + views.nextButton.text = if (noEmails) { + getString(R.string.skip_for_now) + } else { + getString(R.string.next_pf) + } + } + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = + FragmentSpaceCreateGenericEpoxyFormBinding.inflate(layoutInflater, container, false) + + override fun on3pidChange(index: Int, newName: String) { + sharedViewModel.handle(CreateSpaceAction.DefaultInvite3pidChanged(index, newName)) + } + + override fun onNoIdentityServer() { + navigator.openSettings( + requireContext(), + VectorSettingsActivity.EXTRA_DIRECT_ACCESS_DISCOVERY_SETTINGS + ) + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceEvents.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceEvents.kt index 073531353f..eeb2ca30ff 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceEvents.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceEvents.kt @@ -22,6 +22,7 @@ sealed class CreateSpaceEvents : VectorViewEvents { object NavigateToDetails : CreateSpaceEvents() object NavigateToChooseType : CreateSpaceEvents() object NavigateToAddRooms : CreateSpaceEvents() + object NavigateToAdd3Pid : CreateSpaceEvents() object NavigateToChoosePrivateType : CreateSpaceEvents() object Dismiss : CreateSpaceEvents() data class FinishSuccess(val spaceId: String, val defaultRoomId: String?, val topology: SpaceTopology?) : CreateSpaceEvents() diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceState.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceState.kt index 39a69e837b..6fb5853269 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceState.kt @@ -34,13 +34,17 @@ data class CreateSpaceState( val aliasVerificationTask: Async = Uninitialized, val nameInlineError: String? = null, val defaultRooms: Map? = null, - val creationResult: Async = Uninitialized + val default3pidInvite: Map? = null, + val emailValidationResult: Map? = null, + val creationResult: Async = Uninitialized, + val canInviteByMail: Boolean = false ) : MvRxState { enum class Step { ChooseType, SetDetails, AddRooms, - ChoosePrivateType + ChoosePrivateType, + AddEmailsOrInvites } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt index 2537a3a592..b233469383 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt @@ -31,6 +31,7 @@ import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.exhaustive +import im.vector.app.core.extensions.isEmail import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import kotlinx.coroutines.Dispatchers @@ -38,6 +39,7 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.identity.IdentityServiceListener import org.matrix.android.sdk.api.session.room.AliasAvailabilityResult import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure @@ -49,12 +51,28 @@ class CreateSpaceViewModel @AssistedInject constructor( private val errorFormatter: ErrorFormatter ) : VectorViewModel(initialState) { + private val identityService = session.identityService() + + private val identityServerManagerListener = object : IdentityServiceListener { + override fun onIdentityServerChange() { + val identityServerUrl = identityService.getCurrentIdentityServerUrl() + setState { + copy( + canInviteByMail = identityServerUrl != null + ) + } + } + } + init { + val identityServerUrl = identityService.getCurrentIdentityServerUrl() setState { copy( - homeServerName = session.myUserId.getDomain() + homeServerName = session.myUserId.getDomain(), + canInviteByMail = identityServerUrl != null ) } + startListenToIdentityManager() } @AssistedFactory @@ -62,6 +80,19 @@ class CreateSpaceViewModel @AssistedInject constructor( fun create(initialState: CreateSpaceState): CreateSpaceViewModel } + private fun startListenToIdentityManager() { + identityService.addListener(identityServerManagerListener) + } + + private fun stopListenToIdentityManager() { + identityService.addListener(identityServerManagerListener) + } + + override fun onCleared() { + stopListenToIdentityManager() + super.onCleared() + } + companion object : MvRxViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: CreateSpaceState): CreateSpaceViewModel? { @@ -84,7 +115,7 @@ class CreateSpaceViewModel @AssistedInject constructor( override fun handle(action: CreateSpaceAction) { when (action) { - is CreateSpaceAction.SetRoomType -> { + is CreateSpaceAction.SetRoomType -> { setState { copy( step = CreateSpaceState.Step.SetDetails, @@ -93,7 +124,7 @@ class CreateSpaceViewModel @AssistedInject constructor( } _viewEvents.post(CreateSpaceEvents.NavigateToDetails) } - is CreateSpaceAction.NameChanged -> { + is CreateSpaceAction.NameChanged -> { setState { if (aliasManuallyModified) { copy( @@ -113,14 +144,14 @@ class CreateSpaceViewModel @AssistedInject constructor( } } } - is CreateSpaceAction.TopicChanged -> { + is CreateSpaceAction.TopicChanged -> { setState { copy( topic = action.topic ) } } - is CreateSpaceAction.SpaceAliasChanged -> { + is CreateSpaceAction.SpaceAliasChanged -> { // This called only when the alias is change manually // not when programmatically changed via a change on name setState { @@ -131,28 +162,43 @@ class CreateSpaceViewModel @AssistedInject constructor( ) } } - CreateSpaceAction.OnBackPressed -> { + CreateSpaceAction.OnBackPressed -> { handleBackNavigation() } - CreateSpaceAction.NextFromDetails -> { + CreateSpaceAction.NextFromDetails -> { handleNextFromDetails() } - CreateSpaceAction.NextFromDefaultRooms -> { + CreateSpaceAction.NextFromDefaultRooms -> { handleNextFromDefaultRooms() } - is CreateSpaceAction.DefaultRoomNameChanged -> { + CreateSpaceAction.NextFromAdd3pid -> { + handleNextFrom3pid() + } + is CreateSpaceAction.DefaultRoomNameChanged -> { setState { copy( - defaultRooms = (defaultRooms ?: emptyMap()).toMutableMap().apply { + defaultRooms = defaultRooms.orEmpty().toMutableMap().apply { this[action.index] = action.name } ) } } - is CreateSpaceAction.SetAvatar -> { + is CreateSpaceAction.DefaultInvite3pidChanged -> { + setState { + copy( + default3pidInvite = default3pidInvite.orEmpty().toMutableMap().apply { + this[action.index] = action.email + }, + emailValidationResult = emailValidationResult.orEmpty().toMutableMap().apply { + this.remove(action.index) + } + ) + } + } + is CreateSpaceAction.SetAvatar -> { setState { copy(avatarUri = action.uri) } } - is CreateSpaceAction.SetSpaceTopology -> { + is CreateSpaceAction.SetSpaceTopology -> { handleSetTopology(action) } }.exhaustive @@ -173,20 +219,20 @@ class CreateSpaceViewModel @AssistedInject constructor( setState { copy( spaceTopology = SpaceTopology.MeAndTeammates, - step = CreateSpaceState.Step.AddRooms + step = CreateSpaceState.Step.AddEmailsOrInvites ) } - _viewEvents.post(CreateSpaceEvents.NavigateToAddRooms) + _viewEvents.post(CreateSpaceEvents.NavigateToAdd3Pid) } } } private fun handleBackNavigation() = withState { state -> when (state.step) { - CreateSpaceState.Step.ChooseType -> { + CreateSpaceState.Step.ChooseType -> { _viewEvents.post(CreateSpaceEvents.Dismiss) } - CreateSpaceState.Step.SetDetails -> { + CreateSpaceState.Step.SetDetails -> { setState { copy( step = CreateSpaceState.Step.ChooseType, @@ -196,15 +242,15 @@ class CreateSpaceViewModel @AssistedInject constructor( } _viewEvents.post(CreateSpaceEvents.NavigateToChooseType) } - CreateSpaceState.Step.AddRooms -> { + CreateSpaceState.Step.AddRooms -> { if (state.spaceType == SpaceType.Private && state.spaceTopology == SpaceTopology.MeAndTeammates) { setState { copy( spaceTopology = null, - step = CreateSpaceState.Step.ChoosePrivateType + step = CreateSpaceState.Step.AddEmailsOrInvites ) } - _viewEvents.post(CreateSpaceEvents.NavigateToChoosePrivateType) + _viewEvents.post(CreateSpaceEvents.NavigateToAdd3Pid) } else { setState { copy( @@ -214,7 +260,7 @@ class CreateSpaceViewModel @AssistedInject constructor( _viewEvents.post(CreateSpaceEvents.NavigateToDetails) } } - CreateSpaceState.Step.ChoosePrivateType -> { + CreateSpaceState.Step.ChoosePrivateType -> { setState { copy( step = CreateSpaceState.Step.SetDetails @@ -222,6 +268,36 @@ class CreateSpaceViewModel @AssistedInject constructor( } _viewEvents.post(CreateSpaceEvents.NavigateToDetails) } + CreateSpaceState.Step.AddEmailsOrInvites -> { + setState { + copy( + step = CreateSpaceState.Step.ChoosePrivateType + ) + } + _viewEvents.post(CreateSpaceEvents.NavigateToChoosePrivateType) + } + } + } + + private fun handleNextFrom3pid() = withState { state -> + // check if emails are valid + val emailValidation = state.default3pidInvite?.mapValues { + val email = it.value + email.isNullOrEmpty() || email.isEmail() + } + if (emailValidation?.all { it.value } != false) { + setState { + copy( + step = CreateSpaceState.Step.AddRooms + ) + } + _viewEvents.post(CreateSpaceEvents.NavigateToAddRooms) + } else { + setState { + copy( + emailValidationResult = emailValidation + ) + } } } @@ -296,8 +372,14 @@ class CreateSpaceViewModel @AssistedInject constructor( defaultRooms = state.defaultRooms ?.entries ?.sortedBy { it.key } - ?.mapNotNull { it.value } ?: emptyList(), - spaceAlias = alias + ?.mapNotNull { it.value } + .orEmpty(), + spaceAlias = alias, + defaultEmailToInvite = state.default3pidInvite + ?.values + ?.mapNotNull { it.takeIf { it?.isEmail() == true } } + ?.takeIf { state.spaceTopology == SpaceTopology.MeAndTeammates } + .orEmpty() ) ) when (result) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt index c68b8a0b9b..41a85d3f30 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt @@ -25,12 +25,17 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry 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.RestrictedRoomPreset +import org.matrix.android.sdk.api.session.space.CreateSpaceParams import timber.log.Timber import javax.inject.Inject @@ -49,7 +54,8 @@ data class CreateSpaceTaskParams( val spaceAvatar: Uri? = null, val spaceAlias: String? = null, val isPublic: Boolean, - val defaultRooms: List = emptyList() + val defaultRooms: List = emptyList(), + val defaultEmailToInvite: List = emptyList() ) class CreateSpaceViewModelTask @Inject constructor( @@ -60,13 +66,28 @@ class CreateSpaceViewModelTask @Inject constructor( override suspend fun execute(params: CreateSpaceTaskParams): CreateSpaceTaskResult { val spaceID = try { - session.spaceService().createSpace( - params.spaceName, - params.spaceTopic, - params.spaceAvatar, - params.isPublic, - params.spaceAlias - ) + session.spaceService().createSpace(CreateSpaceParams().apply { + this.name = params.spaceName + this.topic = params.spaceTopic + this.avatarUri = params.spaceAvatar + if (params.isPublic) { + this.roomAliasName = params.spaceAlias + this.powerLevelContentOverride = (powerLevelContentOverride ?: PowerLevelsContent()).copy( + invite = 0 + ) + this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT + this.historyVisibility = RoomHistoryVisibility.WORLD_READABLE + this.guestAccess = GuestAccess.CanJoin + } else { + this.preset = CreateRoomPreset.PRESET_PRIVATE_CHAT + visibility = RoomDirectoryVisibility.PRIVATE + this.invite3pids.addAll( + params.defaultEmailToInvite.map { + ThreePid.Email(it) + } + ) + } + }) } catch (failure: Throwable) { return CreateSpaceTaskResult.FailedToCreateSpace(failure) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceAdd3pidEpoxyController.kt b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceAdd3pidEpoxyController.kt new file mode 100644 index 0000000000..05d8a78b30 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceAdd3pidEpoxyController.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2021 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.app.features.spaces.create + +import android.text.InputType +import com.airbnb.epoxy.TypedEpoxyController +import com.google.android.material.textfield.TextInputLayout +import im.vector.app.R +import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.resources.StringProvider +import im.vector.app.core.ui.list.ItemStyle +import im.vector.app.core.ui.list.genericButtonItem +import im.vector.app.core.ui.list.genericFooterItem +import im.vector.app.core.ui.list.genericPillItem +import im.vector.app.features.form.formEditTextItem +import javax.inject.Inject + +class SpaceAdd3pidEpoxyController @Inject constructor( + private val stringProvider: StringProvider, + private val colorProvider: ColorProvider +) : TypedEpoxyController() { + + var listener: Listener? = null + + override fun buildModels(data: CreateSpaceState?) { + val host = this + data ?: return + genericFooterItem { + id("info_help_header") + style(ItemStyle.TITLE) + text(host.stringProvider.getString(R.string.create_spaces_invite_public_header)) + textColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary)) + } + genericFooterItem { + id("info_help_desc") + text(host.stringProvider.getString(R.string.create_spaces_invite_public_header_desc, data.name ?: "")) + textColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)) + } + + if (data.canInviteByMail) { + buildEmailFields(data, host) + } else { + genericPillItem { + id("no_IDS") + imageRes(R.drawable.ic_baseline_perm_contact_calendar_24) + text(host.stringProvider.getString(R.string.create_space_identity_server_info_none)) + } + genericButtonItem { + id("Discover_Settings") + text(host.stringProvider.getString(R.string.open_discovery_settings)) + textColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary)) + buttonClickAction { + host.listener?.onNoIdentityServer() + } + } + } + } + + private fun buildEmailFields(data: CreateSpaceState, host: SpaceAdd3pidEpoxyController) { + for (index in 0..2) { + val mail = data.default3pidInvite?.get(index) + formEditTextItem { + id("3pid$index") + enabled(true) + value(mail) + hint(host.stringProvider.getString(R.string.medium_email)) + inputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) + endIconMode(TextInputLayout.END_ICON_CLEAR_TEXT) + errorMessage( + if (data.emailValidationResult?.get(index) == false) { + host.stringProvider.getString(R.string.does_not_look_like_valid_email) + } else null + ) + onTextChange { text -> + host.listener?.on3pidChange(index, text) + } + } + } + } + + interface Listener { + fun on3pidChange(index: Int, newName: String) + fun onNoIdentityServer() + } +} diff --git a/vector/src/main/res/layout/bottom_sheet_space_create_private_warning.xml b/vector/src/main/res/layout/bottom_sheet_space_create_private_warning.xml deleted file mode 100644 index 6e46c87548..0000000000 --- a/vector/src/main/res/layout/bottom_sheet_space_create_private_warning.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - -