mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 05:31:21 +03:00
Space Create Wizard Flow
This commit is contained in:
parent
6c69a6055d
commit
7d2d7b411e
27 changed files with 481 additions and 68 deletions
|
@ -21,7 +21,7 @@ import org.matrix.android.sdk.api.failure.MatrixError
|
||||||
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
|
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
|
||||||
|
|
||||||
sealed class CreateRoomFailure : Failure.FeatureFailure() {
|
sealed class CreateRoomFailure : Failure.FeatureFailure() {
|
||||||
object CreatedWithTimeout : CreateRoomFailure()
|
data class CreatedWithTimeout(val roomID: String) : CreateRoomFailure()
|
||||||
data class CreatedWithFederationFailure(val matrixError: MatrixError) : CreateRoomFailure()
|
data class CreatedWithFederationFailure(val matrixError: MatrixError) : CreateRoomFailure()
|
||||||
data class AliasError(val aliasError: RoomAliasError) : CreateRoomFailure()
|
data class AliasError(val aliasError: RoomAliasError) : CreateRoomFailure()
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,17 @@
|
||||||
package org.matrix.android.sdk.api.session.space
|
package org.matrix.android.sdk.api.session.space
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.session.room.Room
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
|
||||||
interface Space {
|
interface Space {
|
||||||
|
|
||||||
fun asRoom() : Room
|
fun asRoom() : Room
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A current snapshot of [RoomSummary] associated with the room
|
||||||
|
*/
|
||||||
|
fun spaceSummary(): SpaceSummary?
|
||||||
|
|
||||||
suspend fun addChildren(roomId: String, viaServers: List<String>, order: String?, autoJoin: Boolean = false)
|
suspend fun addChildren(roomId: String, viaServers: List<String>, order: String?, autoJoin: Boolean = false)
|
||||||
|
|
||||||
suspend fun removeRoom(roomId: String)
|
suspend fun removeRoom(roomId: String)
|
||||||
|
|
|
@ -66,8 +66,13 @@ internal class DefaultRoomService @Inject constructor(
|
||||||
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource
|
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource
|
||||||
) : RoomService {
|
) : RoomService {
|
||||||
|
|
||||||
override suspend fun createRoom(createRoomParams: CreateRoomParams): String {
|
override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>): Cancelable {
|
||||||
return createRoomTask.execute(createRoomParams)
|
return createRoomTask
|
||||||
|
.configureWith(createRoomParams) {
|
||||||
|
this.callback = callback
|
||||||
|
this.retryCount = 3
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRoom(roomId: String): Room? {
|
override fun getRoom(roomId: String): Room? {
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||||
|
import org.matrix.android.sdk.api.session.space.Space
|
||||||
|
import org.matrix.android.sdk.internal.session.space.DefaultSpace
|
||||||
|
import org.matrix.android.sdk.internal.session.space.SpaceSummaryDataSource
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface SpaceGetter {
|
||||||
|
fun get(spaceId: String): Space?
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultSpaceGetter @Inject constructor(
|
||||||
|
private val roomGetter: RoomGetter,
|
||||||
|
private val spaceSummaryDataSource: SpaceSummaryDataSource
|
||||||
|
) : SpaceGetter {
|
||||||
|
|
||||||
|
override fun get(spaceId: String): Space? {
|
||||||
|
return roomGetter.getRoom(spaceId)
|
||||||
|
?.takeIf { it.roomSummary()?.roomType == RoomType.SPACE }
|
||||||
|
?.let { DefaultSpace(it, spaceSummaryDataSource) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -102,7 +102,7 @@ internal class DefaultCreateRoomTask @Inject constructor(
|
||||||
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
||||||
}
|
}
|
||||||
} catch (exception: TimeoutCancellationException) {
|
} catch (exception: TimeoutCancellationException) {
|
||||||
throw CreateRoomFailure.CreatedWithTimeout
|
throw CreateRoomFailure.CreatedWithTimeout(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
Realm.getInstance(realmConfiguration).executeTransactionAsync {
|
Realm.getInstance(realmConfiguration).executeTransactionAsync {
|
||||||
|
|
|
@ -73,7 +73,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
||||||
eventType = eventType,
|
eventType = eventType,
|
||||||
body = body.toSafeJson(eventType)
|
body = body.toSafeJson(eventType)
|
||||||
)
|
)
|
||||||
sendStateTask.execute(params)
|
sendStateTask.executeRetry(params, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun JsonDict.toSafeJson(eventType: String): JsonDict {
|
private fun JsonDict.toSafeJson(eventType: String): JsonDict {
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.space
|
||||||
|
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import kotlinx.coroutines.TimeoutCancellationException
|
||||||
|
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||||
|
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
|
||||||
|
import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
|
||||||
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple wrapper of create room task that adds waiting for DB entities of spaces
|
||||||
|
*/
|
||||||
|
internal interface CreateSpaceTask : Task<CreateRoomParams, String>
|
||||||
|
|
||||||
|
internal class DefaultCreateSpaceTask @Inject constructor(
|
||||||
|
private val createRoomTask: CreateRoomTask,
|
||||||
|
@SessionDatabase private val realmConfiguration: RealmConfiguration
|
||||||
|
) : CreateSpaceTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: CreateRoomParams): String {
|
||||||
|
val spaceId = createRoomTask.execute(params)
|
||||||
|
|
||||||
|
try {
|
||||||
|
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
||||||
|
realm.where(SpaceSummaryEntity::class.java)
|
||||||
|
.equalTo(SpaceSummaryEntityFields.SPACE_ID, spaceId)
|
||||||
|
}
|
||||||
|
} catch (exception: TimeoutCancellationException) {
|
||||||
|
throw CreateRoomFailure.CreatedWithTimeout(spaceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return spaceId
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,15 +22,19 @@ import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.room.Room
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
import org.matrix.android.sdk.api.session.space.Space
|
import org.matrix.android.sdk.api.session.space.Space
|
||||||
|
import org.matrix.android.sdk.api.session.space.SpaceSummary
|
||||||
import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
|
import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
|
||||||
import java.lang.IllegalArgumentException
|
|
||||||
|
|
||||||
class DefaultSpace(private val room: Room) : Space {
|
internal class DefaultSpace(private val room: Room, private val spaceSummaryDataSource: SpaceSummaryDataSource) : Space {
|
||||||
|
|
||||||
override fun asRoom(): Room {
|
override fun asRoom(): Room {
|
||||||
return room
|
return room
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun spaceSummary(): SpaceSummary? {
|
||||||
|
return spaceSummaryDataSource.getSpaceSummary(asRoom().roomId)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun addChildren(roomId: String, viaServers: List<String>, order: String?, autoJoin: Boolean) {
|
override suspend fun addChildren(roomId: String, viaServers: List<String>, order: String?, autoJoin: Boolean) {
|
||||||
asRoom().sendStateEvent(
|
asRoom().sendStateEvent(
|
||||||
eventType = EventType.STATE_SPACE_CHILD,
|
eventType = EventType.STATE_SPACE_CHILD,
|
||||||
|
|
|
@ -22,7 +22,6 @@ import com.zhuinden.monarchy.Monarchy
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
|
||||||
import org.matrix.android.sdk.api.session.space.CreateSpaceParams
|
import org.matrix.android.sdk.api.session.space.CreateSpaceParams
|
||||||
|
@ -32,40 +31,33 @@ import org.matrix.android.sdk.api.session.space.SpaceSummary
|
||||||
import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
|
import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
|
||||||
import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
|
import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomGetter
|
import org.matrix.android.sdk.internal.session.room.SpaceGetter
|
||||||
import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask
|
|
||||||
import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
|
|
||||||
import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
|
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
|
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask
|
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.leaving.LeaveRoomTask
|
import org.matrix.android.sdk.internal.session.room.membership.leaving.LeaveRoomTask
|
||||||
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
|
|
||||||
import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask
|
import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask
|
||||||
import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
|
import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
|
||||||
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask
|
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultSpaceService @Inject constructor(
|
internal class DefaultSpaceService @Inject constructor(
|
||||||
@SessionDatabase private val monarchy: Monarchy,
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
private val createRoomTask: CreateRoomTask,
|
private val createSpaceTask: CreateSpaceTask,
|
||||||
private val joinRoomTask: JoinRoomTask,
|
// private val joinRoomTask: JoinRoomTask,
|
||||||
private val joinSpaceTask: JoinSpaceTask,
|
private val joinSpaceTask: JoinSpaceTask,
|
||||||
private val markAllRoomsReadTask: MarkAllRoomsReadTask,
|
private val spaceGetter: SpaceGetter,
|
||||||
private val updateBreadcrumbsTask: UpdateBreadcrumbsTask,
|
// private val markAllRoomsReadTask: MarkAllRoomsReadTask,
|
||||||
private val roomIdByAliasTask: GetRoomIdByAliasTask,
|
// private val updateBreadcrumbsTask: UpdateBreadcrumbsTask,
|
||||||
private val deleteRoomAliasTask: DeleteRoomAliasTask,
|
// private val roomIdByAliasTask: GetRoomIdByAliasTask,
|
||||||
private val roomGetter: RoomGetter,
|
// private val deleteRoomAliasTask: DeleteRoomAliasTask,
|
||||||
|
// private val roomGetter: RoomGetter,
|
||||||
private val spaceSummaryDataSource: SpaceSummaryDataSource,
|
private val spaceSummaryDataSource: SpaceSummaryDataSource,
|
||||||
private val peekSpaceTask: PeekSpaceTask,
|
private val peekSpaceTask: PeekSpaceTask,
|
||||||
private val resolveSpaceInfoTask: ResolveSpaceInfoTask,
|
private val resolveSpaceInfoTask: ResolveSpaceInfoTask,
|
||||||
private val leaveRoomTask: LeaveRoomTask,
|
private val leaveRoomTask: LeaveRoomTask
|
||||||
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
|
// private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
|
||||||
private val taskExecutor: TaskExecutor
|
// private val taskExecutor: TaskExecutor
|
||||||
) : SpaceService {
|
) : SpaceService {
|
||||||
|
|
||||||
override suspend fun createSpace(params: CreateSpaceParams): String {
|
override suspend fun createSpace(params: CreateSpaceParams): String {
|
||||||
return createRoomTask.execute(params)
|
return createSpaceTask.executeRetry(params, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean): String {
|
override suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean): String {
|
||||||
|
@ -78,9 +70,7 @@ internal class DefaultSpaceService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSpace(spaceId: String): Space? {
|
override fun getSpace(spaceId: String): Space? {
|
||||||
return roomGetter.getRoom(spaceId)
|
return spaceGetter.get(spaceId)
|
||||||
?.takeIf { it.roomSummary()?.roomType == RoomType.SPACE }
|
|
||||||
?.let { DefaultSpace(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData<List<SpaceSummary>> {
|
override fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData<List<SpaceSummary>> {
|
||||||
|
|
|
@ -20,6 +20,8 @@ import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
|
import org.matrix.android.sdk.internal.session.room.DefaultSpaceGetter
|
||||||
|
import org.matrix.android.sdk.internal.session.room.SpaceGetter
|
||||||
import org.matrix.android.sdk.internal.session.space.peeking.DefaultPeekSpaceTask
|
import org.matrix.android.sdk.internal.session.space.peeking.DefaultPeekSpaceTask
|
||||||
import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask
|
import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
|
@ -45,4 +47,10 @@ internal abstract class SpaceModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindJoinSpaceTask(task: DefaultJoinSpaceTask): JoinSpaceTask
|
abstract fun bindJoinSpaceTask(task: DefaultJoinSpaceTask): JoinSpaceTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindSpaceGetter(getter: DefaultSpaceGetter): SpaceGetter
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindCreateSpaceTask(getter: DefaultCreateSpaceTask): CreateSpaceTask
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.app.features.form
|
||||||
|
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
@ -50,6 +51,12 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>() {
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var inputType: Int? = null
|
var inputType: Int? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var singleLine: Boolean? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var imeOptions: Int? = null
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var onTextChange: ((String) -> Unit)? = null
|
var onTextChange: ((String) -> Unit)? = null
|
||||||
|
|
||||||
|
@ -69,6 +76,8 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>() {
|
||||||
holder.textInputEditText.setTextSafe(value)
|
holder.textInputEditText.setTextSafe(value)
|
||||||
holder.textInputEditText.isEnabled = enabled
|
holder.textInputEditText.isEnabled = enabled
|
||||||
inputType?.let { holder.textInputEditText.inputType = it }
|
inputType?.let { holder.textInputEditText.inputType = it }
|
||||||
|
holder.textInputEditText.isSingleLine = singleLine ?: false
|
||||||
|
holder.textInputEditText.imeOptions = imeOptions ?: EditorInfo.IME_ACTION_NONE
|
||||||
|
|
||||||
holder.textInputEditText.addTextChangedListener(onTextChangeListener)
|
holder.textInputEditText.addTextChangedListener(onTextChangeListener)
|
||||||
holder.bottomSeparator.isVisible = showBottomSeparator
|
holder.bottomSeparator.isVisible = showBottomSeparator
|
||||||
|
|
|
@ -16,13 +16,16 @@
|
||||||
package im.vector.app.features.form
|
package im.vector.app.features.form
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import com.airbnb.epoxy.EpoxyModelWithHolder
|
import com.airbnb.epoxy.EpoxyModelWithHolder
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.load.MultiTransformation
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.CenterCrop
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.epoxy.ClickListener
|
import im.vector.app.core.epoxy.ClickListener
|
||||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||||
|
@ -55,13 +58,24 @@ abstract class FormEditableSquareAvatarItem : EpoxyModelWithHolder<FormEditableS
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
holder.imageContainer.onClick(clickListener?.takeIf { enabled })
|
holder.imageContainer.onClick(clickListener?.takeIf { enabled })
|
||||||
if (matrixItem != null) {
|
when {
|
||||||
avatarRenderer?.renderSpace(matrixItem!!, holder.image)
|
imageUri != null -> {
|
||||||
} else {
|
val corner = TypedValue.applyDimension(
|
||||||
GlideApp.with(holder.image)
|
TypedValue.COMPLEX_UNIT_DIP,
|
||||||
.load(imageUri)
|
8f,
|
||||||
.apply(RequestOptions.circleCropTransform())
|
holder.view.resources.displayMetrics
|
||||||
.into(holder.image)
|
).toInt()
|
||||||
|
GlideApp.with(holder.image)
|
||||||
|
.load(imageUri)
|
||||||
|
.transform(MultiTransformation(CenterCrop(), RoundedCorners(corner)))
|
||||||
|
.into(holder.image)
|
||||||
|
}
|
||||||
|
matrixItem != null -> {
|
||||||
|
avatarRenderer?.renderSpace(matrixItem!!, holder.image)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
avatarRenderer?.clear(holder.image)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
holder.delete.isVisible = enabled && (imageUri != null || matrixItem?.avatarUrl?.isNotEmpty() == true)
|
holder.delete.isVisible = enabled && (imageUri != null || matrixItem?.avatarUrl?.isNotEmpty() == true)
|
||||||
holder.delete.onClick(deleteListener?.takeIf { enabled })
|
holder.delete.onClick(deleteListener?.takeIf { enabled })
|
||||||
|
@ -72,6 +86,7 @@ abstract class FormEditableSquareAvatarItem : EpoxyModelWithHolder<FormEditableS
|
||||||
GlideApp.with(holder.image).clear(holder.image)
|
GlideApp.with(holder.image).clear(holder.image)
|
||||||
super.unbind(holder)
|
super.unbind(holder)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Holder : VectorEpoxyHolder() {
|
class Holder : VectorEpoxyHolder() {
|
||||||
val imageContainer by bind<View>(R.id.itemEditableAvatarImageContainer)
|
val imageContainer by bind<View>(R.id.itemEditableAvatarImageContainer)
|
||||||
val image by bind<ImageView>(R.id.itemEditableAvatarImage)
|
val image by bind<ImageView>(R.id.itemEditableAvatarImage)
|
||||||
|
|
|
@ -35,6 +35,10 @@ abstract class HomeSpaceSummaryItem : VectorEpoxyModel<HomeSpaceSummaryItem.Hold
|
||||||
@EpoxyAttribute var selected: Boolean = false
|
@EpoxyAttribute var selected: Boolean = false
|
||||||
@EpoxyAttribute var listener: (() -> Unit)? = null
|
@EpoxyAttribute var listener: (() -> Unit)? = null
|
||||||
|
|
||||||
|
override fun getViewType(): Int {
|
||||||
|
// mm.. it's reusing the same layout for basic space item
|
||||||
|
return R.id.space_item_home
|
||||||
|
}
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
holder.rootView.setOnClickListener { listener?.invoke() }
|
holder.rootView.setOnClickListener { listener?.invoke() }
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.home
|
package im.vector.app.features.home
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
@ -36,6 +37,7 @@ import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.di.ScreenComponent
|
import im.vector.app.core.di.ScreenComponent
|
||||||
import im.vector.app.core.extensions.exhaustive
|
import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.extensions.hideKeyboard
|
import im.vector.app.core.extensions.hideKeyboard
|
||||||
|
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||||
import im.vector.app.core.extensions.replaceFragment
|
import im.vector.app.core.extensions.replaceFragment
|
||||||
import im.vector.app.core.platform.ToolbarConfigurable
|
import im.vector.app.core.platform.ToolbarConfigurable
|
||||||
import im.vector.app.core.platform.VectorBaseActivity
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
|
@ -103,6 +105,23 @@ class HomeActivity :
|
||||||
@Inject lateinit var avatarRenderer: AvatarRenderer
|
@Inject lateinit var avatarRenderer: AvatarRenderer
|
||||||
@Inject lateinit var initSyncStepFormatter: InitSyncStepFormatter
|
@Inject lateinit var initSyncStepFormatter: InitSyncStepFormatter
|
||||||
|
|
||||||
|
private val createSpaceResultLauncher = registerStartForActivityResult { activityResult ->
|
||||||
|
if (activityResult.resultCode == Activity.RESULT_OK) {
|
||||||
|
val spaceId = activityResult.data?.extras?.getString(SpaceCreationActivity.RESULT_DATA_CREATED_SPACE_ID)
|
||||||
|
val defaultRoomsId = activityResult.data?.extras?.getString(SpaceCreationActivity.RESULT_DATA_DEFAULT_ROOM_ID)
|
||||||
|
views.drawerLayout.closeDrawer(GravityCompat.START)
|
||||||
|
|
||||||
|
// Here we want to change current space to the newly created one, and then immediatly open the default room
|
||||||
|
if (spaceId != null) {
|
||||||
|
navigator.switchToSpace(this, spaceId, defaultRoomsId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also we should show the share space bottomsheet
|
||||||
|
} else {
|
||||||
|
// viewModel.handle(CrossSigningSettingsAction.ReAuthCancelled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
|
private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
|
||||||
override fun onDrawerStateChanged(newState: Int) {
|
override fun onDrawerStateChanged(newState: Int) {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
|
@ -147,7 +166,7 @@ class HomeActivity :
|
||||||
startActivity(SpacePreviewActivity.newIntent(this, sharedAction.spaceId))
|
startActivity(SpacePreviewActivity.newIntent(this, sharedAction.spaceId))
|
||||||
}
|
}
|
||||||
is HomeActivitySharedAction.AddSpace -> {
|
is HomeActivitySharedAction.AddSpace -> {
|
||||||
startActivity(SpaceCreationActivity.newIntent(this))
|
createSpaceResultLauncher.launch(SpaceCreationActivity.newIntent(this))
|
||||||
}
|
}
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import androidx.core.app.ActivityOptionsCompat
|
||||||
import androidx.core.app.TaskStackBuilder
|
import androidx.core.app.TaskStackBuilder
|
||||||
import androidx.core.util.Pair
|
import androidx.core.util.Pair
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
|
import arrow.core.Option
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.error.fatalError
|
import im.vector.app.core.error.fatalError
|
||||||
|
@ -46,6 +47,7 @@ import im.vector.app.features.crypto.verification.SupportedVerificationMethodsPr
|
||||||
import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
||||||
import im.vector.app.features.debug.DebugMenuActivity
|
import im.vector.app.features.debug.DebugMenuActivity
|
||||||
import im.vector.app.features.devtools.RoomDevToolActivity
|
import im.vector.app.features.devtools.RoomDevToolActivity
|
||||||
|
import im.vector.app.features.grouplist.SelectedSpaceDataSource
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailActivity
|
import im.vector.app.features.home.room.detail.RoomDetailActivity
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailArgs
|
import im.vector.app.features.home.room.detail.RoomDetailArgs
|
||||||
import im.vector.app.features.home.room.detail.search.SearchActivity
|
import im.vector.app.features.home.room.detail.search.SearchActivity
|
||||||
|
@ -77,6 +79,7 @@ import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryDat
|
||||||
import org.matrix.android.sdk.api.session.terms.TermsService
|
import org.matrix.android.sdk.api.session.terms.TermsService
|
||||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||||
import org.matrix.android.sdk.api.session.widgets.model.WidgetType
|
import org.matrix.android.sdk.api.session.widgets.model.WidgetType
|
||||||
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@ -85,6 +88,7 @@ class DefaultNavigator @Inject constructor(
|
||||||
private val sessionHolder: ActiveSessionHolder,
|
private val sessionHolder: ActiveSessionHolder,
|
||||||
private val vectorPreferences: VectorPreferences,
|
private val vectorPreferences: VectorPreferences,
|
||||||
private val widgetArgsBuilder: WidgetArgsBuilder,
|
private val widgetArgsBuilder: WidgetArgsBuilder,
|
||||||
|
private val selectedSpaceDataSource: SelectedSpaceDataSource,
|
||||||
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider
|
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider
|
||||||
) : Navigator {
|
) : Navigator {
|
||||||
|
|
||||||
|
@ -98,6 +102,23 @@ class DefaultNavigator @Inject constructor(
|
||||||
startActivity(context, intent, buildTask)
|
startActivity(context, intent, buildTask)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun switchToSpace(context: Context, spaceId: String, roomId: String?) {
|
||||||
|
if (sessionHolder.getSafeActiveSession()?.spaceService()?.getSpace(spaceId) == null) {
|
||||||
|
fatalError("Trying to open an unknown space $spaceId", vectorPreferences.failFast())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionHolder.getSafeActiveSession()?.spaceService()?.getSpace(spaceId)?.spaceSummary()?.let {
|
||||||
|
Timber.d("## Nav: Switching to space $spaceId / ${it.roomSummary.name}")
|
||||||
|
selectedSpaceDataSource.post(Option.just(it))
|
||||||
|
} ?: kotlin.run {
|
||||||
|
Timber.d("## Nav: Failed to switch to space $spaceId")
|
||||||
|
}
|
||||||
|
if (roomId != null) {
|
||||||
|
openRoom(context, roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String) {
|
override fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String) {
|
||||||
val session = sessionHolder.getSafeActiveSession() ?: return
|
val session = sessionHolder.getSafeActiveSession() ?: return
|
||||||
val tx = session.cryptoService().verificationService().getExistingTransaction(otherUserId, sasTransactionId)
|
val tx = session.cryptoService().verificationService().getExistingTransaction(otherUserId, sasTransactionId)
|
||||||
|
|
|
@ -38,6 +38,8 @@ interface Navigator {
|
||||||
|
|
||||||
fun openRoom(context: Context, roomId: String, eventId: String? = null, buildTask: Boolean = false)
|
fun openRoom(context: Context, roomId: String, eventId: String? = null, buildTask: Boolean = false)
|
||||||
|
|
||||||
|
fun switchToSpace(context: Context, spaceId: String, roomId: String?)
|
||||||
|
|
||||||
fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String)
|
fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String)
|
||||||
|
|
||||||
fun requestSessionVerification(context: Context, otherSessionId: String)
|
fun requestSessionVerification(context: Context, otherSessionId: String)
|
||||||
|
|
|
@ -20,7 +20,9 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
import com.airbnb.mvrx.viewModel
|
import com.airbnb.mvrx.viewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
@ -91,6 +93,23 @@ class SpaceCreationActivity : SimpleFragmentActivity(), CreateSpaceViewModel.Fac
|
||||||
CreateSpaceEvents.NavigateToAddRooms -> {
|
CreateSpaceEvents.NavigateToAddRooms -> {
|
||||||
navigateToFragment(CreateSpaceDefaultRoomsFragment::class.java)
|
navigateToFragment(CreateSpaceDefaultRoomsFragment::class.java)
|
||||||
}
|
}
|
||||||
|
is CreateSpaceEvents.ShowModalError -> {
|
||||||
|
hideWaitingView()
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setMessage(it.errorMessage)
|
||||||
|
.setPositiveButton(getString(R.string.ok), null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
is CreateSpaceEvents.FinishSuccess -> {
|
||||||
|
setResult(RESULT_OK, Intent().apply {
|
||||||
|
putExtra(RESULT_DATA_CREATED_SPACE_ID, it.spaceId)
|
||||||
|
putExtra(RESULT_DATA_DEFAULT_ROOM_ID, it.defaultRoomId)
|
||||||
|
})
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
CreateSpaceEvents.HideModalLoading -> {
|
||||||
|
hideWaitingView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,16 +133,24 @@ class SpaceCreationActivity : SimpleFragmentActivity(), CreateSpaceViewModel.Fac
|
||||||
val titleRes = when (state.step) {
|
val titleRes = when (state.step) {
|
||||||
CreateSpaceState.Step.ChooseType -> R.string.activity_create_space_title
|
CreateSpaceState.Step.ChooseType -> R.string.activity_create_space_title
|
||||||
CreateSpaceState.Step.SetDetails -> R.string.your_public_space
|
CreateSpaceState.Step.SetDetails -> R.string.your_public_space
|
||||||
CreateSpaceState.Step.AddRooms -> R.string.your_public_space
|
CreateSpaceState.Step.AddRooms -> R.string.your_public_space
|
||||||
}
|
}
|
||||||
supportActionBar?.let {
|
supportActionBar?.let {
|
||||||
it.title = getString(titleRes)
|
it.title = getString(titleRes)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
setTitle(getString(titleRes))
|
setTitle(getString(titleRes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.creationResult is Loading) {
|
||||||
|
showWaitingView(getString(R.string.create_spaces_loading_message))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
const val RESULT_DATA_CREATED_SPACE_ID = "RESULT_DATA_CREATED_SPACE_ID"
|
||||||
|
const val RESULT_DATA_DEFAULT_ROOM_ID = "RESULT_DATA_DEFAULT_ROOM_ID"
|
||||||
|
|
||||||
fun newIntent(context: Context): Intent {
|
fun newIntent(context: Context): Intent {
|
||||||
return Intent(context, SpaceCreationActivity::class.java).apply {
|
return Intent(context, SpaceCreationActivity::class.java).apply {
|
||||||
// putExtra(MvRx.KEY_ARG, SpaceDirectoryArgs(spaceId))
|
// putExtra(MvRx.KEY_ARG, SpaceDirectoryArgs(spaceId))
|
||||||
|
|
|
@ -86,8 +86,15 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp
|
||||||
private var currentGroupId = ""
|
private var currentGroupId = ""
|
||||||
|
|
||||||
init {
|
init {
|
||||||
observeGroupSummaries()
|
observeSpaceSummaries()
|
||||||
observeSelectionState()
|
observeSelectionState()
|
||||||
|
selectedSpaceDataSource.observe().execute {
|
||||||
|
if (this.selectedSpace != it.invoke()?.orNull()) {
|
||||||
|
copy(
|
||||||
|
selectedSpace = it.invoke()?.orNull()
|
||||||
|
)
|
||||||
|
} else this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeSelectionState() {
|
private fun observeSelectionState() {
|
||||||
|
@ -143,8 +150,8 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp
|
||||||
_viewEvents.post(SpaceListViewEvents.AddSpace)
|
_viewEvents.post(SpaceListViewEvents.AddSpace)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeGroupSummaries() {
|
private fun observeSpaceSummaries() {
|
||||||
val roomSummaryQueryParams = roomSummaryQueryParams() {
|
val spaceSummaryQueryParams = roomSummaryQueryParams() {
|
||||||
memberships = listOf(Membership.JOIN, Membership.INVITE)
|
memberships = listOf(Membership.JOIN, Membership.INVITE)
|
||||||
displayName = QueryStringValue.IsNotEmpty
|
displayName = QueryStringValue.IsNotEmpty
|
||||||
excludeType = listOf(/**RoomType.MESSAGING,$*/
|
excludeType = listOf(/**RoomType.MESSAGING,$*/
|
||||||
|
@ -171,7 +178,7 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp
|
||||||
},
|
},
|
||||||
session
|
session
|
||||||
.rx()
|
.rx()
|
||||||
.liveSpaceSummaries(roomSummaryQueryParams),
|
.liveSpaceSummaries(spaceSummaryQueryParams),
|
||||||
BiFunction { allCommunityGroup, communityGroups ->
|
BiFunction { allCommunityGroup, communityGroups ->
|
||||||
listOf(allCommunityGroup) + communityGroups
|
listOf(allCommunityGroup) + communityGroups
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ class ChooseSpaceTypeFragment @Inject constructor(
|
||||||
}))
|
}))
|
||||||
|
|
||||||
views.privateButton.setOnClickListener(DebouncedClickListener({
|
views.privateButton.setOnClickListener(DebouncedClickListener({
|
||||||
sharedViewModel.handle(CreateSpaceAction.SetRoomType(SpaceType.Private))
|
// sharedViewModel.handle(CreateSpaceAction.SetRoomType(SpaceType.Private))
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,10 +46,14 @@ class CreateSpaceDefaultRoomsFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
views.nextButton.debouncedClicks {
|
views.nextButton.debouncedClicks {
|
||||||
sharedViewModel.handle(CreateSpaceAction.NextFromDetails)
|
sharedViewModel.handle(CreateSpaceAction.NextFromDefaultRooms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onNameChange(index: Int, newName: String) {
|
||||||
|
sharedViewModel.handle(CreateSpaceAction.DefaultRoomNameChanged(index, newName))
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
// Epoxy controller listener methods
|
// Epoxy controller listener methods
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
|
|
|
@ -16,25 +16,32 @@
|
||||||
|
|
||||||
package im.vector.app.features.spaces.create
|
package im.vector.app.features.spaces.create
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import com.airbnb.mvrx.activityViewModel
|
import com.airbnb.mvrx.activityViewModel
|
||||||
|
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
|
||||||
import im.vector.app.core.extensions.configureWith
|
import im.vector.app.core.extensions.configureWith
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.databinding.FragmentSpaceCreateGenericEpoxyFormBinding
|
import im.vector.app.databinding.FragmentSpaceCreateGenericEpoxyFormBinding
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class CreateSpaceDetailsFragment @Inject constructor(
|
class CreateSpaceDetailsFragment @Inject constructor(
|
||||||
private val epoxyController: SpaceDetailEpoxyController
|
private val epoxyController: SpaceDetailEpoxyController,
|
||||||
) : VectorBaseFragment<FragmentSpaceCreateGenericEpoxyFormBinding>(), SpaceDetailEpoxyController.Listener {
|
private val colorProvider: ColorProvider
|
||||||
|
) : VectorBaseFragment<FragmentSpaceCreateGenericEpoxyFormBinding>(), SpaceDetailEpoxyController.Listener,
|
||||||
|
GalleryOrCameraDialogHelper.Listener {
|
||||||
|
|
||||||
private val sharedViewModel: CreateSpaceViewModel by activityViewModel()
|
private val sharedViewModel: CreateSpaceViewModel by activityViewModel()
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
|
||||||
FragmentSpaceCreateGenericEpoxyFormBinding.inflate(layoutInflater, container, false)
|
FragmentSpaceCreateGenericEpoxyFormBinding.inflate(layoutInflater, container, false)
|
||||||
|
|
||||||
|
private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider)
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
@ -50,14 +57,19 @@ class CreateSpaceDetailsFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onImageReady(uri: Uri?) {
|
||||||
|
sharedViewModel.handle(CreateSpaceAction.SetAvatar(uri))
|
||||||
|
}
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
// Epoxy controller listener methods
|
// Epoxy controller listener methods
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
|
|
||||||
override fun onAvatarDelete() {
|
override fun onAvatarDelete() {
|
||||||
|
sharedViewModel.handle(CreateSpaceAction.SetAvatar(null))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAvatarChange() {
|
override fun onAvatarChange() {
|
||||||
|
galleryOrCameraDialogHelper.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNameChange(newName: String) {
|
override fun onNameChange(newName: String) {
|
||||||
|
|
|
@ -17,20 +17,29 @@
|
||||||
package im.vector.app.features.spaces.create
|
package im.vector.app.features.spaces.create
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.airbnb.mvrx.ActivityViewModelContext
|
import com.airbnb.mvrx.ActivityViewModelContext
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
import com.airbnb.mvrx.FragmentViewModelContext
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
import com.airbnb.mvrx.MvRxState
|
import com.airbnb.mvrx.MvRxState
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import im.vector.app.R
|
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.exhaustive
|
||||||
import im.vector.app.core.platform.VectorViewEvents
|
import im.vector.app.core.platform.VectorViewEvents
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.core.platform.VectorViewModelAction
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
|
||||||
data class CreateSpaceState(
|
data class CreateSpaceState(
|
||||||
|
@ -39,8 +48,9 @@ data class CreateSpaceState(
|
||||||
val topic: String = "",
|
val topic: String = "",
|
||||||
val step: Step = Step.ChooseType,
|
val step: Step = Step.ChooseType,
|
||||||
val spaceType: SpaceType? = null,
|
val spaceType: SpaceType? = null,
|
||||||
val nameInlineError : String? = null,
|
val nameInlineError: String? = null,
|
||||||
val defaultRooms: List<String>? = null
|
val defaultRooms: Map<Int, String?>? = null,
|
||||||
|
val creationResult: Async<String> = Uninitialized
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
enum class Step {
|
enum class Step {
|
||||||
|
@ -59,8 +69,11 @@ sealed class CreateSpaceAction : VectorViewModelAction {
|
||||||
data class SetRoomType(val type: SpaceType) : CreateSpaceAction()
|
data class SetRoomType(val type: SpaceType) : CreateSpaceAction()
|
||||||
data class NameChanged(val name: String) : CreateSpaceAction()
|
data class NameChanged(val name: String) : CreateSpaceAction()
|
||||||
data class TopicChanged(val topic: String) : CreateSpaceAction()
|
data class TopicChanged(val topic: String) : CreateSpaceAction()
|
||||||
|
data class SetAvatar(val uri: Uri?) : CreateSpaceAction()
|
||||||
object OnBackPressed : CreateSpaceAction()
|
object OnBackPressed : CreateSpaceAction()
|
||||||
object NextFromDetails : CreateSpaceAction()
|
object NextFromDetails : CreateSpaceAction()
|
||||||
|
object NextFromDefaultRooms : CreateSpaceAction()
|
||||||
|
data class DefaultRoomNameChanged(val index: Int, val name: String) : CreateSpaceAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class CreateSpaceEvents : VectorViewEvents {
|
sealed class CreateSpaceEvents : VectorViewEvents {
|
||||||
|
@ -68,12 +81,17 @@ sealed class CreateSpaceEvents : VectorViewEvents {
|
||||||
object NavigateToChooseType : CreateSpaceEvents()
|
object NavigateToChooseType : CreateSpaceEvents()
|
||||||
object NavigateToAddRooms : CreateSpaceEvents()
|
object NavigateToAddRooms : CreateSpaceEvents()
|
||||||
object Dismiss : CreateSpaceEvents()
|
object Dismiss : CreateSpaceEvents()
|
||||||
|
data class FinishSuccess(val spaceId: String, val defaultRoomId: String?) : CreateSpaceEvents()
|
||||||
|
data class ShowModalError(val errorMessage: String) : CreateSpaceEvents()
|
||||||
|
object HideModalLoading : CreateSpaceEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
class CreateSpaceViewModel @AssistedInject constructor(
|
class CreateSpaceViewModel @AssistedInject constructor(
|
||||||
@Assisted initialState: CreateSpaceState,
|
@Assisted initialState: CreateSpaceState,
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val stringProvider: StringProvider
|
private val stringProvider: StringProvider,
|
||||||
|
private val createSpaceViewModelTask: CreateSpaceViewModelTask,
|
||||||
|
private val errorFormatter: ErrorFormatter
|
||||||
) : VectorViewModel<CreateSpaceState, CreateSpaceAction, CreateSpaceEvents>(initialState) {
|
) : VectorViewModel<CreateSpaceState, CreateSpaceAction, CreateSpaceEvents>(initialState) {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
@ -90,6 +108,12 @@ class CreateSpaceViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
|
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun initialState(viewModelContext: ViewModelContext): CreateSpaceState? {
|
||||||
|
return CreateSpaceState(
|
||||||
|
defaultRooms = mapOf(0 to viewModelContext.activity.getString(R.string.create_spaces_default_public_room_name))
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handle(action: CreateSpaceAction) {
|
override fun handle(action: CreateSpaceAction) {
|
||||||
|
@ -124,6 +148,21 @@ class CreateSpaceViewModel @AssistedInject constructor(
|
||||||
CreateSpaceAction.NextFromDetails -> {
|
CreateSpaceAction.NextFromDetails -> {
|
||||||
handleNextFromDetails()
|
handleNextFromDetails()
|
||||||
}
|
}
|
||||||
|
CreateSpaceAction.NextFromDefaultRooms -> {
|
||||||
|
handleNextFromDefaultRooms()
|
||||||
|
}
|
||||||
|
is CreateSpaceAction.DefaultRoomNameChanged -> {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
defaultRooms = (defaultRooms ?: emptyMap()).toMutableMap().apply {
|
||||||
|
this[action.index] = action.name
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is CreateSpaceAction.SetAvatar -> {
|
||||||
|
setState { copy(avatarUri = action.uri) }
|
||||||
|
}
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,4 +206,53 @@ class CreateSpaceViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(CreateSpaceEvents.NavigateToAddRooms)
|
_viewEvents.post(CreateSpaceEvents.NavigateToAddRooms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleNextFromDefaultRooms() = withState { state ->
|
||||||
|
val spaceName = state.name ?: return@withState
|
||||||
|
setState {
|
||||||
|
copy(creationResult = Loading())
|
||||||
|
}
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val result = createSpaceViewModelTask.execute(
|
||||||
|
CreateSpaceTaskParams(
|
||||||
|
spaceName = spaceName,
|
||||||
|
spaceTopic = state.topic,
|
||||||
|
spaceAvatar = state.avatarUri,
|
||||||
|
isPublic = state.spaceType == SpaceType.Public,
|
||||||
|
defaultRooms = state.defaultRooms
|
||||||
|
?.entries
|
||||||
|
?.sortedBy { it.key }
|
||||||
|
?.mapNotNull { it.value } ?: emptyList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
when (result) {
|
||||||
|
is CreateSpaceTaskResult.Success -> {
|
||||||
|
setState {
|
||||||
|
copy(creationResult = Success(result.spaceId))
|
||||||
|
}
|
||||||
|
_viewEvents.post(CreateSpaceEvents.FinishSuccess(result.spaceId, result.childIds.firstOrNull()))
|
||||||
|
}
|
||||||
|
is CreateSpaceTaskResult.PartialSuccess -> {
|
||||||
|
// XXX what can we do here?
|
||||||
|
setState {
|
||||||
|
copy(creationResult = Success(result.spaceId))
|
||||||
|
}
|
||||||
|
_viewEvents.post(CreateSpaceEvents.FinishSuccess(result.spaceId, result.childIds.firstOrNull()))
|
||||||
|
}
|
||||||
|
is CreateSpaceTaskResult.FailedToCreateSpace -> {
|
||||||
|
setState {
|
||||||
|
copy(creationResult = Fail(result.failure))
|
||||||
|
}
|
||||||
|
_viewEvents.post(CreateSpaceEvents.ShowModalError(errorFormatter.toHumanReadable(result.failure)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
setState {
|
||||||
|
copy(creationResult = Fail(failure))
|
||||||
|
}
|
||||||
|
_viewEvents.post(CreateSpaceEvents.ShowModalError(errorFormatter.toHumanReadable(failure)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* 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 im.vector.app.core.platform.ViewModelTask
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
|
||||||
|
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.internal.util.awaitCallback
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
sealed class CreateSpaceTaskResult {
|
||||||
|
|
||||||
|
data class Success(val spaceId: String, val childIds: List<String>) : CreateSpaceTaskResult()
|
||||||
|
|
||||||
|
data class PartialSuccess(val spaceId: String, val childIds: List<String>, val failedRooms: Map<String, Throwable>) : CreateSpaceTaskResult()
|
||||||
|
|
||||||
|
class FailedToCreateSpace(val failure: Throwable) : CreateSpaceTaskResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CreateSpaceTaskParams(
|
||||||
|
val spaceName: String,
|
||||||
|
val spaceTopic: String?,
|
||||||
|
val spaceAvatar: Uri? = null,
|
||||||
|
val isPublic: Boolean,
|
||||||
|
val defaultRooms: List<String> = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
class CreateSpaceViewModelTask @Inject constructor(
|
||||||
|
private val session: Session,
|
||||||
|
private val stringProvider: StringProvider
|
||||||
|
) : ViewModelTask<CreateSpaceTaskParams, CreateSpaceTaskResult> {
|
||||||
|
|
||||||
|
override suspend fun execute(params: CreateSpaceTaskParams): CreateSpaceTaskResult {
|
||||||
|
val spaceID = try {
|
||||||
|
session.spaceService().createSpace(params.spaceName, params.spaceTopic, params.spaceAvatar, params.isPublic)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
return CreateSpaceTaskResult.FailedToCreateSpace(failure)
|
||||||
|
}
|
||||||
|
|
||||||
|
val createdSpace = session.spaceService().getSpace(spaceID)
|
||||||
|
|
||||||
|
val childErrors = mutableMapOf<String, Throwable>()
|
||||||
|
val childIds = mutableListOf<String>()
|
||||||
|
if (params.isPublic) {
|
||||||
|
params.defaultRooms
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
.forEach { roomName ->
|
||||||
|
try {
|
||||||
|
val roomId = try {
|
||||||
|
awaitCallback<String> {
|
||||||
|
session.createRoom(CreateRoomParams().apply {
|
||||||
|
this.name = roomName
|
||||||
|
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
|
||||||
|
}, it)
|
||||||
|
}
|
||||||
|
} catch (timeout: CreateRoomFailure.CreatedWithTimeout) {
|
||||||
|
// we ignore that?
|
||||||
|
timeout.roomID
|
||||||
|
}
|
||||||
|
val via = session.sessionParams.homeServerHost?.let { listOf(it) } ?: emptyList()
|
||||||
|
createdSpace!!.addChildren(roomId, via, null, true)
|
||||||
|
childIds.add(roomId)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.d("Failed to create child room in $spaceID")
|
||||||
|
childErrors[roomName] = failure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (childErrors.isEmpty()) {
|
||||||
|
CreateSpaceTaskResult.Success(spaceID, childIds)
|
||||||
|
} else {
|
||||||
|
CreateSpaceTaskResult.PartialSuccess(spaceID, childIds, childErrors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,6 @@ import javax.inject.Inject
|
||||||
class SpaceDefaultRoomEpoxyController @Inject constructor(
|
class SpaceDefaultRoomEpoxyController @Inject constructor(
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val colorProvider: ColorProvider
|
private val colorProvider: ColorProvider
|
||||||
// private val avatarRenderer: AvatarRenderer
|
|
||||||
) : TypedEpoxyController<CreateSpaceState>() {
|
) : TypedEpoxyController<CreateSpaceState>() {
|
||||||
|
|
||||||
var listener: Listener? = null
|
var listener: Listener? = null
|
||||||
|
@ -50,44 +49,41 @@ class SpaceDefaultRoomEpoxyController @Inject constructor(
|
||||||
formEditTextItem {
|
formEditTextItem {
|
||||||
id("roomName1")
|
id("roomName1")
|
||||||
enabled(true)
|
enabled(true)
|
||||||
value(data?.name)
|
value(data?.defaultRooms?.get(0))
|
||||||
hint(stringProvider.getString(R.string.create_room_name_hint))
|
hint(stringProvider.getString(R.string.create_room_name_section))
|
||||||
showBottomSeparator(false)
|
showBottomSeparator(false)
|
||||||
// errorMessage(data?.nameInlineError)
|
|
||||||
onTextChange { text ->
|
onTextChange { text ->
|
||||||
// listener?.onNameChange(text)
|
listener?.onNameChange(0, text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
formEditTextItem {
|
formEditTextItem {
|
||||||
id("roomName2")
|
id("roomName2")
|
||||||
enabled(true)
|
enabled(true)
|
||||||
// value(data?.name)
|
value(data?.defaultRooms?.get(1))
|
||||||
hint(stringProvider.getString(R.string.create_room_name_hint))
|
hint(stringProvider.getString(R.string.create_room_name_section))
|
||||||
showBottomSeparator(false)
|
showBottomSeparator(false)
|
||||||
// errorMessage(data?.nameInlineError)
|
|
||||||
onTextChange { text ->
|
onTextChange { text ->
|
||||||
// listener?.onNameChange(text)
|
listener?.onNameChange(1, text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
formEditTextItem {
|
formEditTextItem {
|
||||||
id("roomName3")
|
id("roomName3")
|
||||||
enabled(true)
|
enabled(true)
|
||||||
// value(data?.name)
|
value(data?.defaultRooms?.get(2))
|
||||||
hint(stringProvider.getString(R.string.create_room_name_hint))
|
hint(stringProvider.getString(R.string.create_room_name_section))
|
||||||
showBottomSeparator(false)
|
showBottomSeparator(false)
|
||||||
// errorMessage(data?.nameInlineError)
|
|
||||||
onTextChange { text ->
|
onTextChange { text ->
|
||||||
// listener?.onNameChange(text)
|
listener?.onNameChange(2, text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Listener {
|
interface Listener {
|
||||||
// fun onAvatarDelete()
|
// fun onAvatarDelete()
|
||||||
// fun onAvatarChange()
|
// fun onAvatarChange()
|
||||||
// fun onNameChange(newName: String)
|
fun onNameChange(index: Int, newName: String)
|
||||||
// fun onTopicChange(newTopic: String)
|
// fun onTopicChange(newTopic: String)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.app.features.spaces.create
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.Build
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import androidx.appcompat.content.res.AppCompatResources.getDrawable
|
import androidx.appcompat.content.res.AppCompatResources.getDrawable
|
||||||
|
@ -85,7 +86,7 @@ class WizardButtonView @JvmOverloads constructor(context: Context, attrs: Attrib
|
||||||
|
|
||||||
val outValue = TypedValue()
|
val outValue = TypedValue()
|
||||||
context.theme.resolveAttribute(android.R.attr.selectableItemBackground, outValue, true)
|
context.theme.resolveAttribute(android.R.attr.selectableItemBackground, outValue, true)
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
this.foreground = getDrawable(context, outValue.resourceId)
|
this.foreground = getDrawable(context, outValue.resourceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
3
vector/src/main/res/values/ids.xml
Normal file
3
vector/src/main/res/values/ids.xml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<resources>
|
||||||
|
<item type="id" name="space_item_home" />
|
||||||
|
</resources>
|
|
@ -3275,6 +3275,8 @@
|
||||||
<string name="create_space_error_empty_field_space_name">Give it a name to continue.</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 Runner’s World?</string>
|
<string name="create_spaces_room_public_header">What are some discussions you want to have in Runner’s World?</string>
|
||||||
<string name="create_spaces_room_public_header_desc">We’ll create rooms for them, and auto-join everyone. You can add more later too.</string>
|
<string name="create_spaces_room_public_header_desc">We’ll create rooms for them, and auto-join everyone. You can add more later too.</string>
|
||||||
|
<string name="create_spaces_default_public_room_name">General</string>
|
||||||
|
<string name="create_spaces_loading_message">Creating Space…</string>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue