Creation wizard WIP

This commit is contained in:
Valere 2021-02-22 19:59:19 +01:00
parent d8a3229819
commit 4b27ad8ba6
26 changed files with 1194 additions and 1 deletions

View file

@ -274,6 +274,7 @@
<activity android:name=".features.devtools.RoomDevToolActivity"/>
<activity android:name=".features.spaces.SpacePreviewActivity"/>
<activity android:name=".features.spaces.SpaceExploreActivity"/>
<activity android:name=".features.spaces.SpaceCreationActivity"/>
<!-- Services -->
<service

View file

@ -118,6 +118,9 @@ import im.vector.app.features.settings.threepids.ThreePidsSettingsFragment
import im.vector.app.features.share.IncomingShareFragment
import im.vector.app.features.signout.soft.SoftLogoutFragment
import im.vector.app.features.spaces.SpaceListFragment
import im.vector.app.features.spaces.create.ChooseSpaceTypeFragment
import im.vector.app.features.spaces.create.CreateSpaceDefaultRoomsFragment
import im.vector.app.features.spaces.create.CreateSpaceDetailsFragment
import im.vector.app.features.spaces.preview.SpacePreviewFragment
import im.vector.app.features.terms.ReviewTermsFragment
import im.vector.app.features.usercode.ShowUserCodeFragment
@ -636,4 +639,19 @@ interface FragmentModule {
@IntoMap
@FragmentKey(SpacePreviewFragment::class)
fun bindSpacePreviewFragment(fragment: SpacePreviewFragment): Fragment
@Binds
@IntoMap
@FragmentKey(ChooseSpaceTypeFragment::class)
fun bindCreateSpaceFragment(fragment: ChooseSpaceTypeFragment): Fragment
@Binds
@IntoMap
@FragmentKey(CreateSpaceDetailsFragment::class)
fun bindCreateSpaceDetailsFragment(fragment: CreateSpaceDetailsFragment): Fragment
@Binds
@IntoMap
@FragmentKey(CreateSpaceDefaultRoomsFragment::class)
fun bindCreateSpaceDefaultRoomsFragment(fragment: CreateSpaceDefaultRoomsFragment): Fragment
}

View file

@ -77,6 +77,7 @@ import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheet
import im.vector.app.features.share.IncomingShareActivity
import im.vector.app.features.signout.soft.SoftLogoutActivity
import im.vector.app.features.spaces.SpaceCreationActivity
import im.vector.app.features.terms.ReviewTermsActivity
import im.vector.app.features.ui.UiStateRepository
import im.vector.app.features.usercode.UserCodeActivity
@ -151,6 +152,7 @@ interface ScreenComponent {
fun inject(activity: CallTransferActivity)
fun inject(activity: ReAuthActivity)
fun inject(activity: RoomDevToolActivity)
fun inject(activity: SpaceCreationActivity)
/* ==========================================================================================
* BottomSheets

View file

@ -0,0 +1,80 @@
/*
* Copyright (c) 2020 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.form
import android.net.Uri
import android.view.View
import android.widget.ImageView
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.airbnb.epoxy.EpoxyModelWithHolder
import com.bumptech.glide.request.RequestOptions
import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.onClick
import im.vector.app.core.glide.GlideApp
import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.util.MatrixItem
@EpoxyModelClass(layout = R.layout.item_editable_square_avatar)
abstract class FormEditableSquareAvatarItem : EpoxyModelWithHolder<FormEditableSquareAvatarItem.Holder>() {
@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<View>(R.id.itemEditableAvatarImageContainer)
val image by bind<ImageView>(R.id.itemEditableAvatarImage)
val delete by bind<View>(R.id.itemEditableAvatarDelete)
}
}

View file

@ -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()

View file

@ -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()
}

View file

@ -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<out Fragment>) {
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)
}

View file

@ -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)
}
}

View file

@ -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()
}
}

View file

@ -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)

View file

@ -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<FragmentSpaceCreateChooseTypeBinding>() {
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))
}))
}
}

View file

@ -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<FragmentSpaceCreateGenericEpoxyFormBinding>(), 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
// -----------------------------
}

View file

@ -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<FragmentSpaceCreateGenericEpoxyFormBinding>(), 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))
}
}

View file

@ -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<String>? = 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<CreateSpaceState, CreateSpaceAction, CreateSpaceEvents>(initialState) {
@AssistedFactory
interface Factory {
fun create(initialState: CreateSpaceState): CreateSpaceViewModel
}
companion object : MvRxViewModelFactory<CreateSpaceViewModel, CreateSpaceState> {
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)
}
}
}

View file

@ -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<CreateSpaceState>() {
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)
}
}

View file

@ -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<CreateSpaceState>() {
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)
}
}

View file

@ -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()
// }
}
}

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M6.4931,1.6667C5.569,1.6667 4.7811,2.2962 4.5568,3.165C4.5039,3.3699 4.4261,3.5713 4.2866,3.7303L4.0563,3.9928C3.8664,4.2092 3.5925,4.3333 3.3046,4.3333H1.3333C0.597,4.3333 0,4.9303 0,5.6667V13C0,13.7364 0.597,14.3333 1.3333,14.3333H14.6667C15.403,14.3333 16,13.7364 16,13V5.6667C16,4.9303 15.403,4.3333 14.6667,4.3333H12.6954C12.4075,4.3333 12.1336,4.2092 11.9437,3.9928L11.7134,3.7303C11.5739,3.5713 11.4961,3.3699 11.4432,3.165C11.219,2.2962 10.431,1.6667 9.5069,1.6667H6.4931ZM10.6667,9C10.6667,10.4727 9.4728,11.6667 8,11.6667C6.5272,11.6667 5.3333,10.4727 5.3333,9C5.3333,7.5272 6.5272,6.3333 8,6.3333C9.4728,6.3333 10.6667,7.5272 10.6667,9Z"
android:fillColor="#737D8C"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M24,12C24,18.6274 18.6274,24 12,24C5.3726,24 0,18.6274 0,12C0,5.3726 5.3726,0 12,0C18.6274,0 24,5.3726 24,12Z"
android:fillColor="#C1C6CD"/>
<path
android:pathData="M18.6999,18.8177L19.6243,15.9652C19.6324,15.9404 19.6365,15.9143 19.6365,15.8882V15.0473C19.6365,14.9823 19.6111,14.9197 19.5657,14.8731L18.7105,13.9934C18.6916,13.9739 18.6697,13.9577 18.6456,13.9453L16.8092,13.0009C16.7852,12.9885 16.7632,12.9723 16.7443,12.9529L15.8919,12.0761C15.8449,12.0277 15.7802,12.0004 15.7127,12.0004H12.2501C12.1121,12.0004 12.0001,11.8885 12.0001,11.7504V10.2868C12.0001,10.1487 11.8882,10.0368 11.7501,10.0368H9.3865C9.2484,10.0368 9.1365,9.9248 9.1365,9.7868V8.3798C9.1365,8.2215 9.2818,8.103 9.4369,8.1349L13.6089,8.9931C13.7639,9.0251 13.9092,8.9066 13.9092,8.7483V7.1928C13.9092,7.1277 13.9346,7.0652 13.98,7.0185L16.6034,4.3201C16.6978,4.2231 16.6978,4.0686 16.6034,3.9716L14.9023,2.2219C14.8769,2.1957 14.8461,2.1755 14.8121,2.1625L13.3697,1.613C12.4823,1.275 11.5072,1.2439 10.6001,1.5247L9.1365,1.9777C7.1638,2.6207 5.6129,4.1612 4.9568,6.1296L4.621,7.1369C4.5407,7.3779 4.4931,7.6285 4.4795,7.8822L4.3696,9.9275C4.3659,9.9972 4.3914,10.0652 4.44,10.1152L6.1992,11.9247C6.2463,11.9731 6.311,12.0004 6.3785,12.0004H8.1214C8.1612,12.0004 8.2004,12.0099 8.2358,12.0281L9.9554,12.9124C10.0387,12.9553 10.091,13.0411 10.091,13.1348V15.8701C10.091,15.908 10.0996,15.9454 10.1162,15.9794L10.9772,17.7506C11.019,17.8367 11.1063,17.8913 11.202,17.8913H12.8491C12.9166,17.8913 12.9812,17.9186 13.0283,17.967L13.9092,18.8731L14.8357,19.8261C14.8543,19.8452 14.8697,19.8671 14.8813,19.8911L15.6626,21.4982C15.7383,21.654 15.9458,21.6874 16.0667,21.5631L16.7729,20.8368L17.7274,19.8549L18.6413,18.9149C18.6681,18.8874 18.6881,18.8542 18.6999,18.8177Z"
android:fillColor="#F4F6FA"
android:fillAlpha="0.91"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11.3333,2C8.3878,2 6,4.3878 6,7.3333V10C4.8954,10 4,10.8954 4,12V20C4,21.1046 4.8954,22 6,22H18C19.1046,22 20,21.1046 20,20V12C20,10.8954 19.1046,10 18,10V7.3333C18,4.3878 15.6122,2 12.6667,2H11.3333ZM15.3333,10V7.3333C15.3333,5.8606 14.1394,4.6667 12.6667,4.6667H11.3333C9.8606,4.6667 8.6667,5.8606 8.6667,7.3333V10H15.3333Z"
android:fillColor="#C1C6CD"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp">
<TextView
android:id="@+id/headerText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/create_spaces_type_header"
android:textColor="?riotx_text_secondary"
android:textSize="18sp"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:gravity="start"
android:text="@string/create_spaces_choose_type_label"
android:textColor="?riotx_text_primary"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/joinInfoHelpText"
app:layout_constraintTop_toBottomOf="@id/headerText"
app:layout_constraintVertical_bias="1" />
<TextView
android:id="@+id/joinInfoHelpText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:gravity="start"
android:text="@string/create_spaces_join_info_help"
android:textColor="?riotx_text_secondary"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@id/publicButton" />
<im.vector.app.features.spaces.create.WizardButtonView
android:id="@+id/publicButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:icon="@drawable/ic_public_room"
app:layout_constraintBottom_toTopOf="@id/privateButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:subTitle="@string/space_type_public_desc"
app:title="@string/space_type_public" />
<im.vector.app.features.spaces.create.WizardButtonView
android:id="@+id/privateButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:icon="@drawable/ic_room_private"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:subTitle="@string/space_type_private_desc"
app:title="@string/space_type_private" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/buttonBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/item_form_text_input" />
<FrameLayout
android:id="@+id/buttonBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?riotx_background"
android:elevation="2dp"
android:padding="8dp"
app:layout_constraintBottom_toBottomOf="parent">
<com.google.android.material.button.MaterialButton
android:id="@+id/nextButton"
style="@style/VectorButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/next_pf" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/memberProfileInfoContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?riotx_background"
android:padding="16dp">
<!-- I cannot do what I want using layer-list, do it manually here-->
<FrameLayout
android:id="@+id/itemEditableAvatarImageContainer"
android:layout_width="80dp"
android:layout_height="80dp"
android:background="@drawable/rounded_rect_shape_8"
android:contentDescription="@string/a11y_change_avatar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/itemEditableAvatarImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
android:scaleType="fitCenter"
tools:alpha="0.3"
tools:src="@tools:sample/avatars" />
</FrameLayout>
<ImageView
android:id="@+id/itemEditableAvatarDelete"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="@drawable/header_panel_round_background"
android:contentDescription="@string/a11y_delete_avatar"
android:scaleType="center"
android:src="@drawable/ic_delete"
android:visibility="gone"
app:layout_constraintCircle="@+id/itemEditableAvatarImageContainer"
app:layout_constraintCircleAngle="45"
app:layout_constraintCircleRadius="48dp"
app:tint="@color/riotx_destructive_accent"
tools:ignore="MissingConstraints"
tools:visibility="visible" />
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center"
android:background="@drawable/circle"
android:backgroundTint="?riotx_background"
android:padding="8dp"
android:src="@drawable/ic_camera_plain"
app:tint="?riotx_text_secondary"
app:layout_constraintCircle="@+id/itemEditableAvatarImageContainer"
app:layout_constraintCircleAngle="135"
app:layout_constraintCircleRadius="48dp"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/rounded_rect_shape_8"
android:id="@+id/content"
android:padding="4dp">
<ImageView
android:id="@+id/buttonImageView"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_margin="8dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_public_room"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="8dp"
android:gravity="start"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/rightChevron"
app:layout_constraintStart_toEndOf="@id/buttonImageView"
app:layout_constraintTop_toTopOf="parent"
tools:text="Public" />
<TextView
android:id="@+id/subTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="8dp"
android:gravity="start"
android:textColor="?riotx_text_secondary"
android:textSize="12sp"
android:textStyle="normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/rightChevron"
app:layout_constraintStart_toEndOf="@id/buttonImageView"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_goneMarginBottom="8dp"
tools:text="Open to anyone, best for communities" />
<ImageView
android:id="@+id/rightChevron"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_material_chevron_right_black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?riotx_text_secondary" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -54,8 +54,10 @@
<attr name="forceStartPadding" format="boolean" />
</declare-styleable>
<attr name="iconTint" format="color" />
<declare-styleable name="SignOutBottomSheetActionButton">
<attr name="iconTint" format="color" />
<attr name="iconTint"/>
<attr name="actionTitle" />
<attr name="leftIcon" />
<attr name="textColor" format="color" />
@ -68,4 +70,12 @@
<enum name="continue_with" value="2" />
</attr>
</declare-styleable>
<declare-styleable name="WizardButtonView">
<attr name="icon" format="reference" />
<attr name="iconTint"/>
<attr name="title" format="string" />
<attr name="subTitle" format="string" />
</declare-styleable>
</resources>

View file

@ -3259,4 +3259,22 @@
<string name="event_status_failed_messages_warning">Messages failed to send</string>
<string name="event_status_delete_all_failed_dialog_title">Delete unsent messages</string>
<string name="event_status_delete_all_failed_dialog_message">Are you sure you want to delete all unsent messages in this room?</string>
<string name="add_space">Add Space</string>
<string name="your_public_space">Your public space</string>
<string name="create_spaces_type_header">Spaces are a new way to group rooms and people</string>
<string name="create_spaces_choose_type_label">What type of space do you want to create?</string>
<string name="create_spaces_join_info_help">To join an existing space, you need an invite.</string>
<string name="space_type_public">Public</string>
<string name="space_type_public_desc">Open to anyone, best for communities</string>
<string name="space_type_private">Private</string>
<string name="space_type_private_desc">Invite only, best for yourself or teams</string>
<string name="activity_create_space_title">Create a space</string>
<string name="create_spaces_details_public_header">Add some details to help it stand out. You can change these at any point.</string>
<string name="create_spaces_details_private_header">Add some details to help people identify it. You can change these at any point.</string>
<string name="create_space_error_empty_field_space_name">Give it a name to continue.</string>
<string name="create_spaces_room_public_header">What are some discussions you want to have in Runners World?</string>
<string name="create_spaces_room_public_header_desc">Well create rooms for them, and auto-join everyone. You can add more later too.</string>
</resources>