diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index e66a123773..205669fb8a 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -274,6 +274,7 @@
+
() {
+
+ @EpoxyAttribute
+ var avatarRenderer: AvatarRenderer? = null
+
+ @EpoxyAttribute
+ var matrixItem: MatrixItem? = null
+
+ @EpoxyAttribute
+ var enabled: Boolean = true
+
+ @EpoxyAttribute
+ var imageUri: Uri? = null
+
+ @EpoxyAttribute
+ var clickListener: ClickListener? = null
+
+ @EpoxyAttribute
+ var deleteListener: ClickListener? = null
+
+ override fun bind(holder: Holder) {
+ super.bind(holder)
+ holder.imageContainer.onClick(clickListener?.takeIf { enabled })
+ if (matrixItem != null) {
+ avatarRenderer?.renderSpace(matrixItem!!, holder.image)
+ } else {
+ GlideApp.with(holder.image)
+ .load(imageUri)
+ .apply(RequestOptions.circleCropTransform())
+ .into(holder.image)
+ }
+ holder.delete.isVisible = enabled && (imageUri != null || matrixItem?.avatarUrl?.isNotEmpty() == true)
+ holder.delete.onClick(deleteListener?.takeIf { enabled })
+ }
+
+ override fun unbind(holder: Holder) {
+ avatarRenderer?.clear(holder.image)
+ GlideApp.with(holder.image).clear(holder.image)
+ super.unbind(holder)
+ }
+ class Holder : VectorEpoxyHolder() {
+ val imageContainer by bind(R.id.itemEditableAvatarImageContainer)
+ val image by bind(R.id.itemEditableAvatarImage)
+ val delete by bind(R.id.itemEditableAvatarDelete)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
index 138ffc26f4..982b59263d 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
@@ -54,6 +54,7 @@ import im.vector.app.features.popup.VerificationVectorAlert
import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.VectorSettingsActivity
+import im.vector.app.features.spaces.SpaceCreationActivity
import im.vector.app.features.spaces.SpacePreviewActivity
import im.vector.app.features.themes.ThemeUtils
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
@@ -145,6 +146,9 @@ class HomeActivity :
is HomeActivitySharedAction.OpenSpacePreview -> {
startActivity(SpacePreviewActivity.newIntent(this, sharedAction.spaceId))
}
+ is HomeActivitySharedAction.AddSpace -> {
+ startActivity(SpaceCreationActivity.newIntent(this))
+ }
}.exhaustive
}
.disposeOnDestroy()
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt
index f72354465b..72e3523336 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt
@@ -25,5 +25,6 @@ sealed class HomeActivitySharedAction : VectorSharedAction {
object OpenDrawer : HomeActivitySharedAction()
object CloseDrawer : HomeActivitySharedAction()
object OpenGroup : HomeActivitySharedAction()
+ object AddSpace : HomeActivitySharedAction()
data class OpenSpacePreview(val spaceId: String) : HomeActivitySharedAction()
}
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
new file mode 100644
index 0000000000..65c8102020
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt
@@ -0,0 +1,135 @@
+/*
+ * 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
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.MenuItem
+import androidx.fragment.app.Fragment
+import com.airbnb.mvrx.viewModel
+import com.airbnb.mvrx.withState
+import im.vector.app.R
+import im.vector.app.core.di.ScreenComponent
+import im.vector.app.core.extensions.toMvRxBundle
+import im.vector.app.core.platform.SimpleFragmentActivity
+import im.vector.app.features.spaces.create.ChooseSpaceTypeFragment
+import im.vector.app.features.spaces.create.CreateSpaceAction
+import im.vector.app.features.spaces.create.CreateSpaceDefaultRoomsFragment
+import im.vector.app.features.spaces.create.CreateSpaceDetailsFragment
+import im.vector.app.features.spaces.create.CreateSpaceEvents
+import im.vector.app.features.spaces.create.CreateSpaceState
+import im.vector.app.features.spaces.create.CreateSpaceViewModel
+import javax.inject.Inject
+
+class SpaceCreationActivity : SimpleFragmentActivity(), CreateSpaceViewModel.Factory {
+
+ @Inject lateinit var viewModelFactory: CreateSpaceViewModel.Factory
+
+ override fun injectWith(injector: ScreenComponent) {
+ super.injectWith(injector)
+ injector.inject(this)
+ }
+
+ val viewModel: CreateSpaceViewModel by viewModel()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ if (isFirstCreation()) {
+ when (withState(viewModel) { it.step }) {
+ CreateSpaceState.Step.ChooseType -> {
+ navigateToFragment(ChooseSpaceTypeFragment::class.java)
+ }
+ CreateSpaceState.Step.SetDetails -> {
+ navigateToFragment(ChooseSpaceTypeFragment::class.java)
+ }
+ CreateSpaceState.Step.AddRooms -> TODO()
+ }
+ }
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ if (item.itemId == android.R.id.home) {
+ onBackPressed()
+ return true
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ override fun initUiAndData() {
+ super.initUiAndData()
+ viewModel.subscribe(this) {
+ renderState(it)
+ }
+
+ viewModel.observeViewEvents {
+ when (it) {
+ CreateSpaceEvents.NavigateToDetails -> {
+ navigateToFragment(CreateSpaceDetailsFragment::class.java)
+ }
+ CreateSpaceEvents.NavigateToChooseType -> {
+ navigateToFragment(ChooseSpaceTypeFragment::class.java)
+ }
+ CreateSpaceEvents.Dismiss -> {
+ finish()
+ }
+ CreateSpaceEvents.NavigateToAddRooms -> {
+ navigateToFragment(CreateSpaceDefaultRoomsFragment::class.java)
+ }
+ }
+ }
+ }
+
+ private fun navigateToFragment(fragmentClass: Class) {
+ val frag = supportFragmentManager.findFragmentByTag(fragmentClass.name) ?: createFragment(fragmentClass, Bundle().toMvRxBundle())
+ supportFragmentManager.beginTransaction()
+ .setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
+ .replace(R.id.container,
+ frag,
+ fragmentClass.name
+ )
+ .commit()
+ }
+
+ override fun onBackPressed() {
+ viewModel.handle(CreateSpaceAction.OnBackPressed)
+ }
+
+ private fun renderState(state: CreateSpaceState) {
+ val titleRes = when (state.step) {
+ CreateSpaceState.Step.ChooseType -> R.string.activity_create_space_title
+ CreateSpaceState.Step.SetDetails -> R.string.your_public_space
+ CreateSpaceState.Step.AddRooms -> R.string.your_public_space
+ }
+ supportActionBar?.let {
+ it.title = getString(titleRes)
+ } ?: run {
+ setTitle(getString(titleRes))
+ }
+ }
+
+ companion object {
+ fun newIntent(context: Context): Intent {
+ return Intent(context, SpaceCreationActivity::class.java).apply {
+ // putExtra(MvRx.KEY_ARG, SpaceDirectoryArgs(spaceId))
+ }
+ }
+ }
+
+ override fun create(initialState: CreateSpaceState): CreateSpaceViewModel = viewModelFactory.create(initialState)
+}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt
index e14b920b2c..bc1af38473 100644
--- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt
@@ -57,6 +57,7 @@ class SpaceListFragment @Inject constructor(
when (it) {
is SpaceListViewEvents.OpenSpaceSummary -> sharedActionViewModel.post(HomeActivitySharedAction.OpenSpacePreview(it.id))
is SpaceListViewEvents.OpenSpace -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup)
+ is SpaceListViewEvents.AddSpace -> sharedActionViewModel.post(HomeActivitySharedAction.AddSpace)
}.exhaustive
}
}
@@ -78,4 +79,8 @@ class SpaceListFragment @Inject constructor(
override fun onSpaceSelected(spaceSummary: SpaceSummary) {
viewModel.handle(SpaceListAction.SelectSpace(spaceSummary))
}
+
+ override fun onAddSpaceSelected() {
+ viewModel.handle(SpaceListAction.AddSpace)
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt
index 29d48b4cd1..d1bc3c6e1c 100644
--- a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt
+++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt
@@ -19,8 +19,10 @@ package im.vector.app.features.spaces
import com.airbnb.epoxy.EpoxyController
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
+import im.vector.app.core.ui.list.genericButtonItem
import im.vector.app.core.ui.list.genericFooterItem
import im.vector.app.core.ui.list.genericItemHeader
+import im.vector.app.core.utils.DebouncedClickListener
import im.vector.app.features.grouplist.homeSpaceSummaryItem
import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.session.room.model.Membership
@@ -103,9 +105,19 @@ class SpaceSummaryController @Inject constructor(
}
}
}
+
+ // Temporary item to create a new Space (will move with final design)
+
+ genericButtonItem {
+ id("create")
+ text(stringProvider.getString(R.string.add_space))
+ iconRes(R.drawable.ic_add_black)
+ buttonClickAction(DebouncedClickListener({ callback?.onAddSpaceSelected() }))
+ }
}
interface Callback {
fun onSpaceSelected(spaceSummary: SpaceSummary)
+ fun onAddSpaceSelected()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt
index cbfa760f56..b8089d6f98 100644
--- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt
@@ -46,6 +46,7 @@ const val ALL_COMMUNITIES_GROUP_ID = "+ALL_COMMUNITIES_GROUP_ID"
sealed class SpaceListAction : VectorViewModelAction {
data class SelectSpace(val spaceSummary: SpaceSummary) : SpaceListAction()
+ object AddSpace : SpaceListAction()
}
/**
@@ -54,6 +55,7 @@ sealed class SpaceListAction : VectorViewModelAction {
sealed class SpaceListViewEvents : VectorViewEvents {
object OpenSpace : SpaceListViewEvents()
data class OpenSpaceSummary(val id: String) : SpaceListViewEvents()
+ object AddSpace : SpaceListViewEvents()
}
data class SpaceListViewState(
@@ -110,6 +112,7 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp
override fun handle(action: SpaceListAction) {
when (action) {
is SpaceListAction.SelectSpace -> handleSelectSpace(action)
+ else -> handleAddSpace()
}
}
@@ -136,6 +139,10 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp
}
}
+ private fun handleAddSpace() {
+ _viewEvents.post(SpaceListViewEvents.AddSpace)
+ }
+
private fun observeGroupSummaries() {
val roomSummaryQueryParams = roomSummaryQueryParams() {
memberships = listOf(Membership.JOIN, Membership.INVITE)
diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/ChooseSpaceTypeFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/ChooseSpaceTypeFragment.kt
new file mode 100644
index 0000000000..080bb99ea4
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/create/ChooseSpaceTypeFragment.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.core.platform.VectorBaseFragment
+import im.vector.app.core.utils.DebouncedClickListener
+import im.vector.app.databinding.FragmentSpaceCreateChooseTypeBinding
+import javax.inject.Inject
+
+class ChooseSpaceTypeFragment @Inject constructor(
+ // private val viewModelFactory: CreateSpaceViewModel.Factory,
+) : VectorBaseFragment() {
+
+ private val sharedViewModel: CreateSpaceViewModel by activityViewModel()
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
+ FragmentSpaceCreateChooseTypeBinding.inflate(layoutInflater, container, false)
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ views.publicButton.setOnClickListener(DebouncedClickListener({
+ sharedViewModel.handle(CreateSpaceAction.SetRoomType(SpaceType.Public))
+ }))
+
+ views.privateButton.setOnClickListener(DebouncedClickListener({
+ sharedViewModel.handle(CreateSpaceAction.SetRoomType(SpaceType.Private))
+ }))
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt
new file mode 100644
index 0000000000..2dc36f8715
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.core.extensions.configureWith
+import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.databinding.FragmentSpaceCreateGenericEpoxyFormBinding
+import javax.inject.Inject
+
+class CreateSpaceDefaultRoomsFragment @Inject constructor(
+ private val epoxyController: SpaceDefaultRoomEpoxyController
+) : VectorBaseFragment(), SpaceDefaultRoomEpoxyController.Listener {
+
+ private val sharedViewModel: CreateSpaceViewModel by activityViewModel()
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
+ FragmentSpaceCreateGenericEpoxyFormBinding.inflate(layoutInflater, container, false)
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ views.recyclerView.configureWith(epoxyController)
+ epoxyController.listener = this
+
+ sharedViewModel.subscribe(this) {
+ epoxyController.setData(it)
+ }
+
+ views.nextButton.debouncedClicks {
+ sharedViewModel.handle(CreateSpaceAction.NextFromDetails)
+ }
+ }
+
+ // -----------------------------
+ // Epoxy controller listener methods
+ // -----------------------------
+}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt
new file mode 100644
index 0000000000..506f71c92f
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.core.extensions.configureWith
+import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.databinding.FragmentSpaceCreateGenericEpoxyFormBinding
+import javax.inject.Inject
+
+class CreateSpaceDetailsFragment @Inject constructor(
+ private val epoxyController: SpaceDetailEpoxyController
+) : VectorBaseFragment(), SpaceDetailEpoxyController.Listener {
+
+ private val sharedViewModel: CreateSpaceViewModel by activityViewModel()
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
+ FragmentSpaceCreateGenericEpoxyFormBinding.inflate(layoutInflater, container, false)
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ views.recyclerView.configureWith(epoxyController)
+ epoxyController.listener = this
+
+ sharedViewModel.subscribe(this) {
+ epoxyController.setData(it)
+ }
+
+ views.nextButton.debouncedClicks {
+ sharedViewModel.handle(CreateSpaceAction.NextFromDetails)
+ }
+ }
+
+ // -----------------------------
+ // Epoxy controller listener methods
+ // -----------------------------
+
+ override fun onAvatarDelete() {
+ }
+
+ override fun onAvatarChange() {
+ }
+
+ override fun onNameChange(newName: String) {
+ sharedViewModel.handle(CreateSpaceAction.NameChanged(newName))
+ }
+
+ override fun onTopicChange(newTopic: String) {
+ sharedViewModel.handle(CreateSpaceAction.TopicChanged(newTopic))
+ }
+}
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
new file mode 100644
index 0000000000..aca8452300
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt
@@ -0,0 +1,170 @@
+/*
+ * 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.net.Uri
+import com.airbnb.mvrx.ActivityViewModelContext
+import com.airbnb.mvrx.FragmentViewModelContext
+import com.airbnb.mvrx.MvRxState
+import com.airbnb.mvrx.MvRxViewModelFactory
+import com.airbnb.mvrx.ViewModelContext
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import im.vector.app.R
+import im.vector.app.core.extensions.exhaustive
+import im.vector.app.core.platform.VectorViewEvents
+import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.core.platform.VectorViewModelAction
+import im.vector.app.core.resources.StringProvider
+import org.matrix.android.sdk.api.session.Session
+
+data class CreateSpaceState(
+ val name: String? = null,
+ val avatarUri: Uri? = null,
+ val topic: String = "",
+ val step: Step = Step.ChooseType,
+ val spaceType: SpaceType? = null,
+ val nameInlineError : String? = null,
+ val defaultRooms: List? = null
+) : MvRxState {
+
+ enum class Step {
+ ChooseType,
+ SetDetails,
+ AddRooms
+ }
+}
+
+enum class SpaceType {
+ Public,
+ Private
+}
+
+sealed class CreateSpaceAction : VectorViewModelAction {
+ data class SetRoomType(val type: SpaceType) : CreateSpaceAction()
+ data class NameChanged(val name: String) : CreateSpaceAction()
+ data class TopicChanged(val topic: String) : CreateSpaceAction()
+ object OnBackPressed : CreateSpaceAction()
+ object NextFromDetails : CreateSpaceAction()
+}
+
+sealed class CreateSpaceEvents : VectorViewEvents {
+ object NavigateToDetails : CreateSpaceEvents()
+ object NavigateToChooseType : CreateSpaceEvents()
+ object NavigateToAddRooms : CreateSpaceEvents()
+ object Dismiss : CreateSpaceEvents()
+}
+
+class CreateSpaceViewModel @AssistedInject constructor(
+ @Assisted initialState: CreateSpaceState,
+ private val session: Session,
+ private val stringProvider: StringProvider
+) : VectorViewModel(initialState) {
+
+ @AssistedFactory
+ interface Factory {
+ fun create(initialState: CreateSpaceState): CreateSpaceViewModel
+ }
+
+ companion object : MvRxViewModelFactory {
+
+ override fun create(viewModelContext: ViewModelContext, state: CreateSpaceState): CreateSpaceViewModel? {
+ val factory = when (viewModelContext) {
+ is FragmentViewModelContext -> viewModelContext.fragment as? Factory
+ is ActivityViewModelContext -> viewModelContext.activity as? Factory
+ }
+ return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
+ }
+ }
+
+ override fun handle(action: CreateSpaceAction) {
+ when (action) {
+ is CreateSpaceAction.SetRoomType -> {
+ setState {
+ copy(
+ step = CreateSpaceState.Step.SetDetails,
+ spaceType = action.type
+ )
+ }
+ _viewEvents.post(CreateSpaceEvents.NavigateToDetails)
+ }
+ is CreateSpaceAction.NameChanged -> {
+ setState {
+ copy(
+ nameInlineError = null,
+ name = action.name
+ )
+ }
+ }
+ is CreateSpaceAction.TopicChanged -> {
+ setState {
+ copy(
+ topic = action.topic
+ )
+ }
+ }
+ CreateSpaceAction.OnBackPressed -> {
+ handleBackNavigation()
+ }
+ CreateSpaceAction.NextFromDetails -> {
+ handleNextFromDetails()
+ }
+ }.exhaustive
+ }
+
+ private fun handleBackNavigation() = withState { state ->
+ when (state.step) {
+ CreateSpaceState.Step.ChooseType -> {
+ _viewEvents.post(CreateSpaceEvents.Dismiss)
+ }
+ CreateSpaceState.Step.SetDetails -> {
+ setState {
+ copy(
+ step = CreateSpaceState.Step.ChooseType
+ )
+ }
+ _viewEvents.post(CreateSpaceEvents.NavigateToChooseType)
+ }
+ CreateSpaceState.Step.AddRooms -> {
+ setState {
+ copy(
+ step = CreateSpaceState.Step.SetDetails
+ )
+ }
+ _viewEvents.post(CreateSpaceEvents.NavigateToDetails)
+ }
+ }
+ }
+
+ private fun handleNextFromDetails() = withState { state ->
+ if (state.name.isNullOrBlank()) {
+ setState {
+ copy(
+ nameInlineError = stringProvider.getString(R.string.create_space_error_empty_field_space_name)
+ )
+ }
+ } else {
+ setState {
+ copy(
+ step = CreateSpaceState.Step.AddRooms
+ )
+ }
+ _viewEvents.post(CreateSpaceEvents.NavigateToAddRooms)
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt
new file mode 100644
index 0000000000..05abcf95b0
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt
@@ -0,0 +1,93 @@
+/*
+ * 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 com.airbnb.epoxy.TypedEpoxyController
+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.GenericItem
+import im.vector.app.core.ui.list.genericFooterItem
+import im.vector.app.features.form.formEditTextItem
+import javax.inject.Inject
+
+class SpaceDefaultRoomEpoxyController @Inject constructor(
+ private val stringProvider: StringProvider,
+ private val colorProvider: ColorProvider
+// private val avatarRenderer: AvatarRenderer
+) : TypedEpoxyController() {
+
+ var listener: Listener? = null
+
+ override fun buildModels(data: CreateSpaceState?) {
+ genericFooterItem {
+ id("info_help_header")
+ style(GenericItem.STYLE.BIG_TEXT)
+ text(stringProvider.getString(R.string.create_spaces_room_public_header))
+ textColor(colorProvider.getColorFromAttribute(R.attr.riot_primary_text_color))
+ }
+
+ genericFooterItem {
+ id("info_help")
+ text(stringProvider.getString(R.string.create_spaces_room_public_header_desc))
+ textColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary))
+ }
+
+ formEditTextItem {
+ id("roomName1")
+ enabled(true)
+ value(data?.name)
+ hint(stringProvider.getString(R.string.create_room_name_hint))
+ showBottomSeparator(false)
+// errorMessage(data?.nameInlineError)
+ onTextChange { text ->
+// listener?.onNameChange(text)
+ }
+ }
+
+ formEditTextItem {
+ id("roomName2")
+ enabled(true)
+// value(data?.name)
+ hint(stringProvider.getString(R.string.create_room_name_hint))
+ showBottomSeparator(false)
+// errorMessage(data?.nameInlineError)
+ onTextChange { text ->
+// listener?.onNameChange(text)
+ }
+ }
+
+ formEditTextItem {
+ id("roomName3")
+ enabled(true)
+// value(data?.name)
+ hint(stringProvider.getString(R.string.create_room_name_hint))
+ showBottomSeparator(false)
+// errorMessage(data?.nameInlineError)
+ onTextChange { text ->
+// listener?.onNameChange(text)
+ }
+ }
+ }
+
+ interface Listener {
+// fun onAvatarDelete()
+// fun onAvatarChange()
+// fun onNameChange(newName: String)
+// fun onTopicChange(newTopic: String)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDetailEpoxyController.kt b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDetailEpoxyController.kt
new file mode 100644
index 0000000000..357b741ff4
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDetailEpoxyController.kt
@@ -0,0 +1,90 @@
+/*
+ * 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 com.airbnb.epoxy.TypedEpoxyController
+import im.vector.app.R
+import im.vector.app.core.resources.StringProvider
+import im.vector.app.core.ui.list.genericFooterItem
+import im.vector.app.features.form.formEditTextItem
+import im.vector.app.features.form.formEditableSquareAvatarItem
+import im.vector.app.features.form.formMultiLineEditTextItem
+import im.vector.app.features.home.AvatarRenderer
+import org.matrix.android.sdk.api.util.MatrixItem
+import javax.inject.Inject
+
+class SpaceDetailEpoxyController @Inject constructor(
+ private val stringProvider: StringProvider,
+ private val avatarRenderer: AvatarRenderer
+) : TypedEpoxyController() {
+
+ var listener: Listener? = null
+
+ override fun buildModels(data: CreateSpaceState?) {
+ genericFooterItem {
+ id("info_help")
+ text(
+ if (data?.spaceType == SpaceType.Public) {
+ stringProvider.getString(R.string.create_spaces_details_public_header)
+ } else {
+ stringProvider.getString(R.string.create_spaces_details_private_header)
+ }
+ )
+ }
+
+ formEditableSquareAvatarItem {
+ id("avatar")
+ enabled(true)
+ imageUri(data?.avatarUri)
+ avatarRenderer(avatarRenderer)
+ matrixItem(data?.name?.let { MatrixItem.RoomItem("", it, null).takeIf { !it.displayName.isNullOrBlank() } })
+ clickListener { listener?.onAvatarChange() }
+ deleteListener { listener?.onAvatarDelete() }
+ }
+
+ formEditTextItem {
+ id("name")
+ enabled(true)
+ value(data?.name)
+ hint(stringProvider.getString(R.string.create_room_name_hint))
+ showBottomSeparator(false)
+ errorMessage(data?.nameInlineError)
+ onTextChange { text ->
+ listener?.onNameChange(text)
+ }
+ }
+
+ formMultiLineEditTextItem {
+ id("topic")
+ enabled(true)
+ value(data?.topic)
+ hint(stringProvider.getString(R.string.create_room_topic_hint))
+ showBottomSeparator(false)
+ textSizeSp(15)
+ onTextChange { text ->
+ listener?.onTopicChange(text)
+ }
+ }
+ }
+
+ interface Listener {
+ fun onAvatarDelete()
+ fun onAvatarChange()
+ fun onNameChange(newName: String)
+ fun onTopicChange(newTopic: String)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt b/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt
new file mode 100644
index 0000000000..552a98ded2
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.content.Context
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.util.TypedValue
+import androidx.appcompat.content.res.AppCompatResources.getDrawable
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.withStyledAttributes
+import im.vector.app.R
+import im.vector.app.databinding.ViewSpaceTypeButtonBinding
+
+class WizardButtonView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
+ : ConstraintLayout(context, attrs, defStyle) {
+
+ private val views: ViewSpaceTypeButtonBinding
+
+ var title: String? = null
+ set(value) {
+ if (value != title) {
+ field = value
+ views.title.text = value
+ }
+ }
+
+ var subTitle: String? = null
+ set(value) {
+ if (value != subTitle) {
+ field = value
+ views.subTitle.text = value
+ }
+ }
+
+ var icon: Drawable? = null
+ set(value) {
+ if (value != icon) {
+ field = value
+ views.buttonImageView.setImageDrawable(value)
+ }
+ }
+
+// private var tint: Int? = null
+// set(value) {
+// field = value
+// if (value != null) {
+// views.buttonImageView.imageTintList = ColorStateList.valueOf(value)
+// } else {
+// views.buttonImageView.clearColorFilter()
+// }
+// }
+
+// var action: (() -> Unit)? = null
+
+ init {
+ inflate(context, R.layout.view_space_type_button, this)
+ views = ViewSpaceTypeButtonBinding.bind(this)
+
+ if (isInEditMode) {
+ title = "Title"
+ subTitle = "This is doing something"
+ }
+
+ context.withStyledAttributes(attrs, R.styleable.WizardButtonView) {
+ title = getString(R.styleable.WizardButtonView_title) ?: ""
+ subTitle = getString(R.styleable.WizardButtonView_subTitle) ?: ""
+ icon = getDrawable(R.styleable.WizardButtonView_icon)
+// tint = getColor(R.styleable.WizardButtonView_iconTint, ThemeUtils.getColor(context, R.attr.riotx_text_primary))
+ }
+
+ val outValue = TypedValue()
+ context.theme.resolveAttribute(android.R.attr.selectableItemBackground, outValue, true)
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
+ this.foreground = getDrawable(context, outValue.resourceId)
+ }
+
+// views.content.isClickable = true
+// views.content.isFocusable = true
+// views.content.setOnClickListener {
+// action?.invoke()
+// }
+ }
+}
diff --git a/vector/src/main/res/drawable/ic_camera_plain.xml b/vector/src/main/res/drawable/ic_camera_plain.xml
new file mode 100644
index 0000000000..56d55c9da1
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_camera_plain.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/vector/src/main/res/drawable/ic_public_room.xml b/vector/src/main/res/drawable/ic_public_room.xml
new file mode 100644
index 0000000000..1520898831
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_public_room.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/vector/src/main/res/drawable/ic_room_private.xml b/vector/src/main/res/drawable/ic_room_private.xml
new file mode 100644
index 0000000000..cacdf15a3b
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_room_private.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/vector/src/main/res/layout/fragment_space_create_choose_type.xml b/vector/src/main/res/layout/fragment_space_create_choose_type.xml
new file mode 100644
index 0000000000..ddf61aabf8
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_space_create_choose_type.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/fragment_space_create_generic_epoxy_form.xml b/vector/src/main/res/layout/fragment_space_create_generic_epoxy_form.xml
new file mode 100644
index 0000000000..3097664e02
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_space_create_generic_epoxy_form.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/item_editable_square_avatar.xml b/vector/src/main/res/layout/item_editable_square_avatar.xml
new file mode 100644
index 0000000000..b3ec057fd4
--- /dev/null
+++ b/vector/src/main/res/layout/item_editable_square_avatar.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/view_space_type_button.xml b/vector/src/main/res/layout/view_space_type_button.xml
new file mode 100644
index 0000000000..1ccbed3201
--- /dev/null
+++ b/vector/src/main/res/layout/view_space_type_button.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/values/attrs.xml b/vector/src/main/res/values/attrs.xml
index ecc64bf07d..86105cf74d 100644
--- a/vector/src/main/res/values/attrs.xml
+++ b/vector/src/main/res/values/attrs.xml
@@ -54,8 +54,10 @@
+
+
-
+
@@ -68,4 +70,12 @@
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 01adc31bb1..b2bfe76f57 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -3259,4 +3259,22 @@
Messages failed to send
Delete unsent messages
Are you sure you want to delete all unsent messages in this room?
+
+ Add Space
+ Your public space
+ Spaces are a new way to group rooms and people
+ What type of space do you want to create?
+ To join an existing space, you need an invite.
+ Public
+ Open to anyone, best for communities
+ Private
+ Invite only, best for yourself or teams
+ Create a space
+ Add some details to help it stand out. You can change these at any point.
+ Add some details to help people identify it. You can change these at any point.
+ Give it a name to continue.
+ What are some discussions you want to have in Runner’s World?
+ We’ll create rooms for them, and auto-join everyone. You can add more later too.
+
+