Merge pull request #5166 from element-hq/feature/fga/create_room_flow_rework
Create room flow rework
This commit is contained in:
commit
8e37d16f9f
340 changed files with 2539 additions and 1784 deletions
|
|
@ -3,18 +3,18 @@ appId: ${MAESTRO_APP_ID}
|
||||||
# Purpose: Test the creation and deletion of a room
|
# Purpose: Test the creation and deletion of a room
|
||||||
- tapOn: "Create a new conversation or room"
|
- tapOn: "Create a new conversation or room"
|
||||||
- tapOn: "New room"
|
- tapOn: "New room"
|
||||||
- tapOn: "Search for someone"
|
|
||||||
- inputText: ${MAESTRO_INVITEE1_MXID}
|
|
||||||
- tapOn:
|
|
||||||
text: ${MAESTRO_INVITEE1_MXID}
|
|
||||||
index: 1
|
|
||||||
- tapOn: "Next"
|
|
||||||
- tapOn: "e.g. your project name"
|
- tapOn: "e.g. your project name"
|
||||||
- inputText: "aRoomName"
|
- inputText: "aRoomName"
|
||||||
- tapOn: "What is this room about?"
|
- tapOn: "What is this room about?"
|
||||||
- inputText: "aRoomTopic"
|
- inputText: "aRoomTopic"
|
||||||
- tapOn: "Create"
|
- tapOn: "Create"
|
||||||
- takeScreenshot: build/maestro/320-createAndDeleteRoom
|
- takeScreenshot: build/maestro/320-createAndDeleteRoom
|
||||||
|
- tapOn: "Search for someone"
|
||||||
|
- inputText: ${MAESTRO_INVITEE1_MXID}
|
||||||
|
- tapOn:
|
||||||
|
text: ${MAESTRO_INVITEE1_MXID}
|
||||||
|
index: 1
|
||||||
|
- tapOn: "Finish"
|
||||||
- tapOn: "aRoomName"
|
- tapOn: "aRoomName"
|
||||||
- tapOn: "Invite"
|
- tapOn: "Invite"
|
||||||
# assert there's 1 member and 1 invitee
|
# assert there's 1 member and 1 invitee
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ dependencies {
|
||||||
testImplementation(projects.features.rageshake.test)
|
testImplementation(projects.features.rageshake.test)
|
||||||
testImplementation(projects.services.appnavstate.test)
|
testImplementation(projects.services.appnavstate.test)
|
||||||
testImplementation(projects.services.analytics.test)
|
testImplementation(projects.services.analytics.test)
|
||||||
|
testImplementation(projects.services.toolbox.test)
|
||||||
testImplementation(libs.test.appyx.junit)
|
testImplementation(libs.test.appyx.junit)
|
||||||
testImplementation(libs.test.arch.core)
|
testImplementation(libs.test.arch.core)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,6 @@ import io.element.android.appnav.loggedin.SendQueues
|
||||||
import io.element.android.appnav.room.RoomFlowNode
|
import io.element.android.appnav.room.RoomFlowNode
|
||||||
import io.element.android.appnav.room.RoomNavigationTarget
|
import io.element.android.appnav.room.RoomNavigationTarget
|
||||||
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
|
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
|
||||||
import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
|
||||||
import io.element.android.features.enterprise.api.SessionEnterpriseService
|
import io.element.android.features.enterprise.api.SessionEnterpriseService
|
||||||
import io.element.android.features.ftue.api.FtueEntryPoint
|
import io.element.android.features.ftue.api.FtueEntryPoint
|
||||||
import io.element.android.features.ftue.api.state.FtueService
|
import io.element.android.features.ftue.api.state.FtueService
|
||||||
|
|
@ -60,6 +59,7 @@ import io.element.android.features.roomdirectory.api.RoomDescription
|
||||||
import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint
|
import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint
|
||||||
import io.element.android.features.securebackup.api.SecureBackupEntryPoint
|
import io.element.android.features.securebackup.api.SecureBackupEntryPoint
|
||||||
import io.element.android.features.share.api.ShareEntryPoint
|
import io.element.android.features.share.api.ShareEntryPoint
|
||||||
|
import io.element.android.features.startchat.api.StartChatEntryPoint
|
||||||
import io.element.android.features.userprofile.api.UserProfileEntryPoint
|
import io.element.android.features.userprofile.api.UserProfileEntryPoint
|
||||||
import io.element.android.features.verifysession.api.IncomingVerificationEntryPoint
|
import io.element.android.features.verifysession.api.IncomingVerificationEntryPoint
|
||||||
import io.element.android.libraries.architecture.BackstackView
|
import io.element.android.libraries.architecture.BackstackView
|
||||||
|
|
@ -94,15 +94,6 @@ import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.collections.List
|
|
||||||
import kotlin.collections.any
|
|
||||||
import kotlin.collections.emptyList
|
|
||||||
import kotlin.collections.first
|
|
||||||
import kotlin.collections.forEach
|
|
||||||
import kotlin.collections.listOf
|
|
||||||
import kotlin.collections.mapNotNull
|
|
||||||
import kotlin.collections.plus
|
|
||||||
import kotlin.collections.setOf
|
|
||||||
import kotlin.time.Duration.Companion.minutes
|
import kotlin.time.Duration.Companion.minutes
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
import kotlin.time.toKotlinDuration
|
import kotlin.time.toKotlinDuration
|
||||||
|
|
@ -113,7 +104,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val homeEntryPoint: HomeEntryPoint,
|
private val homeEntryPoint: HomeEntryPoint,
|
||||||
private val preferencesEntryPoint: PreferencesEntryPoint,
|
private val preferencesEntryPoint: PreferencesEntryPoint,
|
||||||
private val createRoomEntryPoint: CreateRoomEntryPoint,
|
private val startChatEntryPoint: StartChatEntryPoint,
|
||||||
private val appNavigationStateService: AppNavigationStateService,
|
private val appNavigationStateService: AppNavigationStateService,
|
||||||
private val secureBackupEntryPoint: SecureBackupEntryPoint,
|
private val secureBackupEntryPoint: SecureBackupEntryPoint,
|
||||||
private val userProfileEntryPoint: UserProfileEntryPoint,
|
private val userProfileEntryPoint: UserProfileEntryPoint,
|
||||||
|
|
@ -304,7 +295,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
backstack.push(NavTarget.Settings())
|
backstack.push(NavTarget.Settings())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateRoomClick() {
|
override fun onStartChatClick() {
|
||||||
backstack.push(NavTarget.CreateRoom)
|
backstack.push(NavTarget.CreateRoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -422,7 +413,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
NavTarget.CreateRoom -> {
|
NavTarget.CreateRoom -> {
|
||||||
val callback = object : CreateRoomEntryPoint.Callback {
|
val callback = object : StartChatEntryPoint.Callback {
|
||||||
override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>) {
|
override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>) {
|
||||||
backstack.replace(NavTarget.Room(roomIdOrAlias = roomIdOrAlias, serverNames = serverNames))
|
backstack.replace(NavTarget.Room(roomIdOrAlias = roomIdOrAlias, serverNames = serverNames))
|
||||||
}
|
}
|
||||||
|
|
@ -432,7 +423,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createRoomEntryPoint
|
startChatEntryPoint
|
||||||
.nodeBuilder(this, buildContext)
|
.nodeBuilder(this, buildContext)
|
||||||
.callback(callback)
|
.callback(callback)
|
||||||
.build()
|
.build()
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||||
import io.element.android.services.apperror.api.AppErrorState
|
import io.element.android.services.apperror.api.AppErrorState
|
||||||
import io.element.android.services.apperror.api.AppErrorStateService
|
import io.element.android.services.apperror.api.AppErrorStateService
|
||||||
import io.element.android.services.apperror.impl.DefaultAppErrorStateService
|
import io.element.android.services.apperror.impl.DefaultAppErrorStateService
|
||||||
|
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
|
|
@ -42,7 +43,9 @@ class RootPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - passes app error state`() = runTest {
|
fun `present - passes app error state`() = runTest {
|
||||||
val presenter = createRootPresenter(
|
val presenter = createRootPresenter(
|
||||||
appErrorService = DefaultAppErrorStateService().apply {
|
appErrorService = DefaultAppErrorStateService(
|
||||||
|
stringProvider = FakeStringProvider(),
|
||||||
|
).apply {
|
||||||
showError("Bad news", "Something bad happened")
|
showError("Bad news", "Something bad happened")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -61,7 +64,9 @@ class RootPresenterTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createRootPresenter(
|
private fun createRootPresenter(
|
||||||
appErrorService: AppErrorStateService = DefaultAppErrorStateService(),
|
appErrorService: AppErrorStateService = DefaultAppErrorStateService(
|
||||||
|
stringProvider = FakeStringProvider(),
|
||||||
|
),
|
||||||
): RootPresenter {
|
): RootPresenter {
|
||||||
return RootPresenter(
|
return RootPresenter(
|
||||||
crashDetectionPresenter = { aCrashDetectionState() },
|
crashDetectionPresenter = { aCrashDetectionState() },
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
* Copyright 2025 New Vector Ltd.
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
|
@ -11,17 +11,17 @@ import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
import com.bumble.appyx.core.plugin.Plugin
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
import io.element.android.libraries.architecture.FeatureEntryPoint
|
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
interface CreateRoomEntryPoint : FeatureEntryPoint {
|
interface CreateRoomEntryPoint : FeatureEntryPoint {
|
||||||
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
||||||
|
|
||||||
interface NodeBuilder {
|
interface NodeBuilder {
|
||||||
fun callback(callback: Callback): NodeBuilder
|
fun callback(callback: Callback): NodeBuilder
|
||||||
fun build(): Node
|
fun build(): Node
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>)
|
fun onRoomCreated(roomId: RoomId)
|
||||||
fun onOpenRoomDirectory()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import extension.ComponentMergingStrategy
|
|
||||||
import extension.setupAnvil
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -23,7 +22,7 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setupAnvil(componentMergingStrategy = ComponentMergingStrategy.KSP)
|
setupAnvil()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
|
|
@ -41,6 +40,7 @@ dependencies {
|
||||||
implementation(projects.services.analytics.api)
|
implementation(projects.services.analytics.api)
|
||||||
implementation(libs.coil.compose)
|
implementation(libs.coil.compose)
|
||||||
implementation(projects.libraries.featureflag.api)
|
implementation(projects.libraries.featureflag.api)
|
||||||
|
implementation(projects.features.invitepeople.api)
|
||||||
api(projects.features.createroom.api)
|
api(projects.features.createroom.api)
|
||||||
|
|
||||||
testImplementation(libs.test.junit)
|
testImplementation(libs.test.junit)
|
||||||
|
|
@ -56,7 +56,7 @@ dependencies {
|
||||||
testImplementation(projects.libraries.mediaupload.test)
|
testImplementation(projects.libraries.mediaupload.test)
|
||||||
testImplementation(projects.libraries.permissions.test)
|
testImplementation(projects.libraries.permissions.test)
|
||||||
testImplementation(projects.libraries.usersearch.test)
|
testImplementation(projects.libraries.usersearch.test)
|
||||||
testImplementation(projects.features.createroom.test)
|
testImplementation(projects.features.startchat.test)
|
||||||
testImplementation(projects.libraries.featureflag.test)
|
testImplementation(projects.libraries.featureflag.test)
|
||||||
testImplementation(projects.tests.testutils)
|
testImplementation(projects.tests.testutils)
|
||||||
testImplementation(libs.androidx.compose.ui.test.junit)
|
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||||
|
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.createroom.impl
|
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
|
||||||
import com.bumble.appyx.core.node.Node
|
|
||||||
import com.bumble.appyx.core.plugin.Plugin
|
|
||||||
import com.bumble.appyx.core.plugin.plugins
|
|
||||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
|
||||||
import dagger.assisted.Assisted
|
|
||||||
import dagger.assisted.AssistedInject
|
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
|
||||||
import io.element.android.features.createroom.CreateRoomNavigator
|
|
||||||
import io.element.android.features.createroom.impl.addpeople.AddPeopleNode
|
|
||||||
import io.element.android.features.createroom.impl.configureroom.ConfigureRoomNode
|
|
||||||
import io.element.android.features.createroom.impl.di.CreateRoomComponent
|
|
||||||
import io.element.android.libraries.architecture.BackstackView
|
|
||||||
import io.element.android.libraries.architecture.BaseFlowNode
|
|
||||||
import io.element.android.libraries.architecture.bindings
|
|
||||||
import io.element.android.libraries.architecture.createNode
|
|
||||||
import io.element.android.libraries.di.DaggerComponentOwner
|
|
||||||
import io.element.android.libraries.di.SessionScope
|
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
|
||||||
class ConfigureRoomFlowNode @AssistedInject constructor(
|
|
||||||
@Assisted buildContext: BuildContext,
|
|
||||||
@Assisted plugins: List<Plugin>,
|
|
||||||
) : DaggerComponentOwner,
|
|
||||||
BaseFlowNode<ConfigureRoomFlowNode.NavTarget>(
|
|
||||||
backstack = BackStack(
|
|
||||||
initialElement = NavTarget.Root,
|
|
||||||
savedStateMap = buildContext.savedStateMap,
|
|
||||||
),
|
|
||||||
buildContext = buildContext,
|
|
||||||
plugins = plugins
|
|
||||||
) {
|
|
||||||
private val component by lazy {
|
|
||||||
parent!!.bindings<CreateRoomComponent.ParentBindings>().createRoomComponentBuilder().build()
|
|
||||||
}
|
|
||||||
private val navigator = plugins<CreateRoomNavigator>().first()
|
|
||||||
|
|
||||||
override val daggerComponent: Any
|
|
||||||
get() = component
|
|
||||||
|
|
||||||
sealed interface NavTarget : Parcelable {
|
|
||||||
@Parcelize
|
|
||||||
data object Root : NavTarget
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
data object ConfigureRoom : NavTarget
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
|
||||||
return when (navTarget) {
|
|
||||||
NavTarget.Root -> {
|
|
||||||
val callback = object : AddPeopleNode.Callback {
|
|
||||||
override fun onContinue() {
|
|
||||||
backstack.push(NavTarget.ConfigureRoom)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
createNode<AddPeopleNode>(buildContext = buildContext, plugins = listOf(callback))
|
|
||||||
}
|
|
||||||
NavTarget.ConfigureRoom -> {
|
|
||||||
createNode<ConfigureRoomNode>(buildContext = buildContext, plugins = listOf(navigator))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun View(modifier: Modifier) {
|
|
||||||
BackstackView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
* Copyright 2025 New Vector Ltd.
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
|
@ -8,28 +8,25 @@
|
||||||
package io.element.android.features.createroom.impl
|
package io.element.android.features.createroom.impl
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.navigation.transition.JumpToEndTransitionHandler
|
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
import com.bumble.appyx.core.plugin.Plugin
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
import com.bumble.appyx.core.plugin.plugins
|
import com.bumble.appyx.core.plugin.plugins
|
||||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||||
|
import com.bumble.appyx.navmodel.backstack.operation.replace
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.features.createroom.DefaultCreateRoomNavigator
|
|
||||||
import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
||||||
import io.element.android.features.createroom.impl.joinbyaddress.JoinRoomByAddressNode
|
import io.element.android.features.createroom.impl.addpeople.AddPeopleNode
|
||||||
import io.element.android.features.createroom.impl.root.CreateRoomRootNode
|
import io.element.android.features.createroom.impl.configureroom.ConfigureRoomNode
|
||||||
import io.element.android.libraries.architecture.BackstackView
|
import io.element.android.libraries.architecture.BackstackView
|
||||||
import io.element.android.libraries.architecture.BaseFlowNode
|
import io.element.android.libraries.architecture.BaseFlowNode
|
||||||
import io.element.android.libraries.architecture.OverlayView
|
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
|
|
@ -38,53 +35,48 @@ class CreateRoomFlowNode @AssistedInject constructor(
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
) : BaseFlowNode<CreateRoomFlowNode.NavTarget>(
|
) : BaseFlowNode<CreateRoomFlowNode.NavTarget>(
|
||||||
backstack = BackStack(
|
backstack = BackStack(
|
||||||
initialElement = NavTarget.Root,
|
initialElement = NavTarget.ConfigureRoom,
|
||||||
savedStateMap = buildContext.savedStateMap,
|
savedStateMap = buildContext.savedStateMap,
|
||||||
),
|
),
|
||||||
buildContext = buildContext,
|
buildContext = buildContext,
|
||||||
plugins = plugins
|
plugins = plugins
|
||||||
) {
|
) {
|
||||||
sealed interface NavTarget : Parcelable {
|
private fun onRoomCreated(roomId: RoomId) {
|
||||||
@Parcelize
|
plugins<CreateRoomEntryPoint.Callback>().forEach { it.onRoomCreated(roomId) }
|
||||||
data object Root : NavTarget
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
data object NewRoom : NavTarget
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
data object JoinByAddress : NavTarget
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val navigator = DefaultCreateRoomNavigator(
|
|
||||||
backstack = backstack,
|
|
||||||
overlay = overlay,
|
|
||||||
openRoom = { roomIdOrAlias, viaServers ->
|
|
||||||
plugins<CreateRoomEntryPoint.Callback>().forEach { it.onOpenRoom(roomIdOrAlias, viaServers) }
|
|
||||||
},
|
|
||||||
openRoomDirectory = {
|
|
||||||
plugins<CreateRoomEntryPoint.Callback>().forEach { it.onOpenRoomDirectory() }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||||
return when (navTarget) {
|
return when (navTarget) {
|
||||||
NavTarget.Root -> {
|
NavTarget.ConfigureRoom -> {
|
||||||
createNode<CreateRoomRootNode>(buildContext = buildContext, plugins = listOf(navigator))
|
val callback = object : ConfigureRoomNode.Callback {
|
||||||
|
override fun onCreateRoomSuccess(roomId: RoomId) {
|
||||||
|
backstack.replace(NavTarget.AddPeople(roomId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createNode<ConfigureRoomNode>(buildContext, plugins = listOf(callback))
|
||||||
}
|
}
|
||||||
NavTarget.NewRoom -> {
|
is NavTarget.AddPeople -> {
|
||||||
createNode<ConfigureRoomFlowNode>(buildContext = buildContext, plugins = listOf(navigator))
|
val inputs = AddPeopleNode.Inputs(navTarget.roomId)
|
||||||
}
|
val callback: AddPeopleNode.Callback = object : AddPeopleNode.Callback {
|
||||||
NavTarget.JoinByAddress -> {
|
override fun onFinish() {
|
||||||
createNode<JoinRoomByAddressNode>(buildContext = buildContext, plugins = listOf(navigator))
|
onRoomCreated(navTarget.roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createNode<AddPeopleNode>(buildContext, plugins = listOf(inputs, callback))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
Box(modifier = modifier) {
|
BackstackView()
|
||||||
BackstackView()
|
}
|
||||||
OverlayView(transitionHandler = remember { JumpToEndTransitionHandler() })
|
|
||||||
}
|
sealed interface NavTarget : Parcelable {
|
||||||
|
@Parcelize
|
||||||
|
data object ConfigureRoom : NavTarget
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class AddPeople(val roomId: RoomId) : NavTarget
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
* Copyright 2025 New Vector Ltd.
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
|
@ -13,10 +13,10 @@ import com.bumble.appyx.core.plugin.Plugin
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(SessionScope::class)
|
||||||
class DefaultCreateRoomEntryPoint @Inject constructor() : CreateRoomEntryPoint {
|
class DefaultCreateRoomEntryPoint @Inject constructor() : CreateRoomEntryPoint {
|
||||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): CreateRoomEntryPoint.NodeBuilder {
|
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): CreateRoomEntryPoint.NodeBuilder {
|
||||||
val plugins = ArrayList<Plugin>()
|
val plugins = ArrayList<Plugin>()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
* Copyright 2025 New Vector Ltd.
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
|
@ -16,30 +16,46 @@ import com.bumble.appyx.core.plugin.plugins
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.features.createroom.impl.di.CreateRoomScope
|
import io.element.android.features.invitepeople.api.InvitePeoplePresenter
|
||||||
|
import io.element.android.features.invitepeople.api.InvitePeopleRenderer
|
||||||
|
import io.element.android.libraries.architecture.NodeInputs
|
||||||
|
import io.element.android.libraries.architecture.inputs
|
||||||
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
@ContributesNode(CreateRoomScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
class AddPeopleNode @AssistedInject constructor(
|
class AddPeopleNode @AssistedInject constructor(
|
||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val presenter: AddPeoplePresenter,
|
invitePeoplePresenterFactory: InvitePeoplePresenter.Factory,
|
||||||
|
private val invitePeopleRenderer: InvitePeopleRenderer,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
|
data class Inputs(
|
||||||
|
val roomId: RoomId,
|
||||||
|
) : NodeInputs
|
||||||
|
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun onContinue()
|
fun onFinish()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onContinue() {
|
private fun onFinish() {
|
||||||
plugins<Callback>().forEach { it.onContinue() }
|
plugins<Callback>().forEach { it.onFinish() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val roomId = inputs<Inputs>().roomId
|
||||||
|
private val invitePeoplePresenter = invitePeoplePresenterFactory.create(
|
||||||
|
joinedRoom = null,
|
||||||
|
roomId = roomId,
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state = presenter.present()
|
val state = invitePeoplePresenter.present()
|
||||||
AddPeopleView(
|
AddPeopleView(
|
||||||
state = state,
|
state = state,
|
||||||
modifier = modifier,
|
onFinish = ::onFinish,
|
||||||
onBackClick = this::navigateUp,
|
) {
|
||||||
onNextClick = this::onContinue,
|
invitePeopleRenderer.Render(state, Modifier)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.createroom.impl.addpeople
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import io.element.android.features.createroom.impl.CreateRoomDataStore
|
|
||||||
import io.element.android.features.createroom.impl.userlist.SelectionMode
|
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListPresenter
|
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListPresenterArgs
|
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListState
|
|
||||||
import io.element.android.libraries.architecture.Presenter
|
|
||||||
import io.element.android.libraries.usersearch.api.UserRepository
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class AddPeoplePresenter @Inject constructor(
|
|
||||||
userListPresenterFactory: UserListPresenter.Factory,
|
|
||||||
userRepository: UserRepository,
|
|
||||||
dataStore: CreateRoomDataStore,
|
|
||||||
) : Presenter<UserListState> {
|
|
||||||
private val userListPresenter = userListPresenterFactory.create(
|
|
||||||
UserListPresenterArgs(
|
|
||||||
selectionMode = SelectionMode.Multiple,
|
|
||||||
),
|
|
||||||
userRepository,
|
|
||||||
dataStore.selectedUserListDataStore,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun present(): UserListState {
|
|
||||||
return userListPresenter.present()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.createroom.impl.addpeople
|
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|
||||||
import io.element.android.features.createroom.impl.userlist.SelectionMode
|
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListState
|
|
||||||
import io.element.android.features.createroom.impl.userlist.aRecentDirectRoomList
|
|
||||||
import io.element.android.features.createroom.impl.userlist.aUserListState
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
|
||||||
import io.element.android.libraries.usersearch.api.UserSearchResult
|
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
|
||||||
|
|
||||||
open class AddPeopleUserListStateProvider : PreviewParameterProvider<UserListState> {
|
|
||||||
override val values: Sequence<UserListState>
|
|
||||||
get() = sequenceOf(
|
|
||||||
aUserListState(),
|
|
||||||
aUserListState(
|
|
||||||
searchResults = SearchBarResultState.Results(aMatrixUserList().toImmutableList()),
|
|
||||||
selectedUsers = aMatrixUserList().toImmutableList(),
|
|
||||||
isSearchActive = false,
|
|
||||||
selectionMode = SelectionMode.Multiple,
|
|
||||||
),
|
|
||||||
aUserListState(
|
|
||||||
searchResults = SearchBarResultState.Results(
|
|
||||||
aMatrixUserList()
|
|
||||||
.mapIndexed { index, matrixUser ->
|
|
||||||
UserSearchResult(matrixUser, index % 2 == 0)
|
|
||||||
}
|
|
||||||
.toImmutableList()
|
|
||||||
),
|
|
||||||
selectedUsers = aMatrixUserList().toImmutableList(),
|
|
||||||
isSearchActive = true,
|
|
||||||
selectionMode = SelectionMode.Multiple,
|
|
||||||
),
|
|
||||||
aUserListState(
|
|
||||||
recentDirectRooms = aRecentDirectRoomList(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2022-2024 New Vector Ltd.
|
* Copyright 2025 New Vector Ltd.
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
|
@ -7,77 +7,68 @@
|
||||||
|
|
||||||
package io.element.android.features.createroom.impl.addpeople
|
package io.element.android.features.createroom.impl.addpeople
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.features.createroom.impl.R
|
import io.element.android.features.createroom.impl.R
|
||||||
import io.element.android.features.createroom.impl.components.UserListView
|
import io.element.android.features.invitepeople.api.InvitePeopleEvents
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListEvents
|
import io.element.android.features.invitepeople.api.InvitePeopleState
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListState
|
import io.element.android.features.invitepeople.api.InvitePeopleStateProvider
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AddPeopleView(
|
fun AddPeopleView(
|
||||||
state: UserListState,
|
state: InvitePeopleState,
|
||||||
onBackClick: () -> Unit,
|
onFinish: () -> Unit,
|
||||||
onNextClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
invitePeopleView: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
HeaderFooterPage(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
contentPadding = PaddingValues(0.dp),
|
||||||
topBar = {
|
topBar = {
|
||||||
AddPeopleViewTopBar(
|
AddPeopleTopBar(onSkipClick = onFinish)
|
||||||
hasSelectedUsers = state.selectedUsers.isNotEmpty(),
|
},
|
||||||
onBackClick = {
|
footer = {
|
||||||
if (state.isSearchActive) {
|
Button(
|
||||||
state.eventSink(UserListEvents.OnSearchActiveChanged(false))
|
text = stringResource(CommonStrings.action_finish),
|
||||||
} else {
|
onClick = {
|
||||||
onBackClick()
|
state.eventSink(InvitePeopleEvents.SendInvites)
|
||||||
}
|
onFinish()
|
||||||
},
|
},
|
||||||
onNextClick = onNextClick,
|
enabled = state.canInvite,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 16.dp)
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
) { padding ->
|
content = invitePeopleView
|
||||||
UserListView(
|
)
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(padding)
|
|
||||||
.consumeWindowInsets(padding),
|
|
||||||
state = state,
|
|
||||||
showBackButton = false,
|
|
||||||
onSelectUser = {},
|
|
||||||
onDeselectUser = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun AddPeopleViewTopBar(
|
private fun AddPeopleTopBar(
|
||||||
hasSelectedUsers: Boolean,
|
onSkipClick: () -> Unit,
|
||||||
onBackClick: () -> Unit,
|
|
||||||
onNextClick: () -> Unit,
|
|
||||||
) {
|
) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
titleStr = stringResource(id = R.string.screen_create_room_add_people_title),
|
titleStr = stringResource(R.string.screen_create_room_add_people_title),
|
||||||
navigationIcon = { BackButton(onClick = onBackClick) },
|
|
||||||
actions = {
|
actions = {
|
||||||
val textActionResId = if (hasSelectedUsers) CommonStrings.action_next else CommonStrings.action_skip
|
|
||||||
TextButton(
|
TextButton(
|
||||||
text = stringResource(id = textActionResId),
|
text = stringResource(CommonStrings.action_skip),
|
||||||
onClick = onNextClick,
|
onClick = onSkipClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -85,10 +76,10 @@ private fun AddPeopleViewTopBar(
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun AddPeopleViewPreview(@PreviewParameter(AddPeopleUserListStateProvider::class) state: UserListState) = ElementPreview {
|
internal fun AddPeopleViewPreview(@PreviewParameter(InvitePeopleStateProvider::class) state: InvitePeopleState) = ElementPreview {
|
||||||
AddPeopleView(
|
AddPeopleView(
|
||||||
state = state,
|
state = state,
|
||||||
onBackClick = {},
|
invitePeopleView = {},
|
||||||
onNextClick = {},
|
onFinish = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
package io.element.android.features.createroom.impl.configureroom
|
package io.element.android.features.createroom.impl.configureroom
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
|
||||||
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
||||||
|
|
||||||
sealed interface ConfigureRoomEvents {
|
sealed interface ConfigureRoomEvents {
|
||||||
|
|
@ -16,7 +15,6 @@ sealed interface ConfigureRoomEvents {
|
||||||
data class RoomVisibilityChanged(val visibilityItem: RoomVisibilityItem) : ConfigureRoomEvents
|
data class RoomVisibilityChanged(val visibilityItem: RoomVisibilityItem) : ConfigureRoomEvents
|
||||||
data class RoomAccessChanged(val roomAccess: RoomAccessItem) : ConfigureRoomEvents
|
data class RoomAccessChanged(val roomAccess: RoomAccessItem) : ConfigureRoomEvents
|
||||||
data class RoomAddressChanged(val roomAddress: String) : ConfigureRoomEvents
|
data class RoomAddressChanged(val roomAddress: String) : ConfigureRoomEvents
|
||||||
data class RemoveUserFromSelection(val matrixUser: MatrixUser) : ConfigureRoomEvents
|
|
||||||
data object CreateRoom : ConfigureRoomEvents
|
data object CreateRoom : ConfigureRoomEvents
|
||||||
data class HandleAvatarAction(val action: AvatarAction) : ConfigureRoomEvents
|
data class HandleAvatarAction(val action: AvatarAction) : ConfigureRoomEvents
|
||||||
data object CancelCreateRoom : ConfigureRoomEvents
|
data object CancelCreateRoom : ConfigureRoomEvents
|
||||||
|
|
|
||||||
|
|
@ -18,19 +18,20 @@ import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import im.vector.app.features.analytics.plan.MobileScreen
|
import im.vector.app.features.analytics.plan.MobileScreen
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.features.createroom.CreateRoomNavigator
|
import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.features.createroom.impl.di.CreateRoomScope
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
|
|
||||||
@ContributesNode(CreateRoomScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
class ConfigureRoomNode @AssistedInject constructor(
|
class ConfigureRoomNode @AssistedInject constructor(
|
||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val presenter: ConfigureRoomPresenter,
|
private val presenter: ConfigureRoomPresenter,
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
private val navigator = plugins<CreateRoomNavigator>().first()
|
interface Callback : Plugin {
|
||||||
|
fun onCreateRoomSuccess(roomId: RoomId)
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
lifecycle.subscribe(
|
lifecycle.subscribe(
|
||||||
|
|
@ -40,6 +41,10 @@ class ConfigureRoomNode @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onCreateRoomSuccess(roomId: RoomId) {
|
||||||
|
plugins<Callback>().forEach { it.onCreateRoomSuccess(roomId) }
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state = presenter.present()
|
val state = presenter.present()
|
||||||
|
|
@ -47,9 +52,7 @@ class ConfigureRoomNode @AssistedInject constructor(
|
||||||
state = state,
|
state = state,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onBackClick = this::navigateUp,
|
onBackClick = this::navigateUp,
|
||||||
onCreateRoomSuccess = {
|
onCreateRoomSuccess = ::onCreateRoomSuccess,
|
||||||
navigator.onOpenRoom(roomIdOrAlias = it.toRoomIdOrAlias(), serverNames = emptyList())
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,6 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import im.vector.app.features.analytics.plan.CreatedRoom
|
import im.vector.app.features.analytics.plan.CreatedRoom
|
||||||
import io.element.android.features.createroom.impl.CreateRoomConfig
|
|
||||||
import io.element.android.features.createroom.impl.CreateRoomDataStore
|
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
|
|
@ -50,7 +48,7 @@ import javax.inject.Inject
|
||||||
import kotlin.jvm.optionals.getOrDefault
|
import kotlin.jvm.optionals.getOrDefault
|
||||||
|
|
||||||
class ConfigureRoomPresenter @Inject constructor(
|
class ConfigureRoomPresenter @Inject constructor(
|
||||||
private val dataStore: CreateRoomDataStore,
|
private val dataStore: CreateRoomConfigStore,
|
||||||
private val matrixClient: MatrixClient,
|
private val matrixClient: MatrixClient,
|
||||||
private val mediaPickerProvider: PickerProvider,
|
private val mediaPickerProvider: PickerProvider,
|
||||||
private val mediaPreProcessor: MediaPreProcessor,
|
private val mediaPreProcessor: MediaPreProcessor,
|
||||||
|
|
@ -66,7 +64,7 @@ class ConfigureRoomPresenter @Inject constructor(
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): ConfigureRoomState {
|
override fun present(): ConfigureRoomState {
|
||||||
val cameraPermissionState = cameraPermissionPresenter.present()
|
val cameraPermissionState = cameraPermissionPresenter.present()
|
||||||
val createRoomConfig by dataStore.createRoomConfigWithInvites.collectAsState(CreateRoomConfig())
|
val createRoomConfig by dataStore.getCreateRoomConfigFlow().collectAsState(CreateRoomConfig())
|
||||||
val homeserverName = remember { matrixClient.userIdServerName() }
|
val homeserverName = remember { matrixClient.userIdServerName() }
|
||||||
val isKnockFeatureEnabled by remember {
|
val isKnockFeatureEnabled by remember {
|
||||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock)
|
featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock)
|
||||||
|
|
@ -121,7 +119,6 @@ class ConfigureRoomPresenter @Inject constructor(
|
||||||
is ConfigureRoomEvents.RoomNameChanged -> dataStore.setRoomName(event.name)
|
is ConfigureRoomEvents.RoomNameChanged -> dataStore.setRoomName(event.name)
|
||||||
is ConfigureRoomEvents.TopicChanged -> dataStore.setTopic(event.topic)
|
is ConfigureRoomEvents.TopicChanged -> dataStore.setTopic(event.topic)
|
||||||
is ConfigureRoomEvents.RoomVisibilityChanged -> dataStore.setRoomVisibility(event.visibilityItem)
|
is ConfigureRoomEvents.RoomVisibilityChanged -> dataStore.setRoomVisibility(event.visibilityItem)
|
||||||
is ConfigureRoomEvents.RemoveUserFromSelection -> dataStore.selectedUserListDataStore.removeUserFromSelection(event.matrixUser)
|
|
||||||
is ConfigureRoomEvents.RoomAccessChanged -> dataStore.setRoomAccess(event.roomAccess)
|
is ConfigureRoomEvents.RoomAccessChanged -> dataStore.setRoomAccess(event.roomAccess)
|
||||||
is ConfigureRoomEvents.RoomAddressChanged -> dataStore.setRoomAddress(event.roomAddress)
|
is ConfigureRoomEvents.RoomAddressChanged -> dataStore.setRoomAddress(event.roomAddress)
|
||||||
is ConfigureRoomEvents.CreateRoom -> createRoom(createRoomConfig)
|
is ConfigureRoomEvents.CreateRoom -> createRoom(createRoomConfig)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
package io.element.android.features.createroom.impl.configureroom
|
package io.element.android.features.createroom.impl.configureroom
|
||||||
|
|
||||||
import io.element.android.features.createroom.impl.CreateRoomConfig
|
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
package io.element.android.features.createroom.impl.configureroom
|
package io.element.android.features.createroom.impl.configureroom
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.createroom.impl.CreateRoomConfig
|
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||||
|
|
@ -58,7 +57,6 @@ import io.element.android.libraries.designsystem.theme.components.TextField
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet
|
import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet
|
||||||
import io.element.android.libraries.matrix.ui.components.SelectedUsersRowList
|
|
||||||
import io.element.android.libraries.matrix.ui.components.UnsavedAvatar
|
import io.element.android.libraries.matrix.ui.components.UnsavedAvatar
|
||||||
import io.element.android.libraries.matrix.ui.room.address.RoomAddressField
|
import io.element.android.libraries.matrix.ui.room.address.RoomAddressField
|
||||||
import io.element.android.libraries.permissions.api.PermissionsView
|
import io.element.android.libraries.permissions.api.PermissionsView
|
||||||
|
|
@ -112,16 +110,6 @@ fun ConfigureRoomView(
|
||||||
topic = state.config.topic.orEmpty(),
|
topic = state.config.topic.orEmpty(),
|
||||||
onTopicChange = { state.eventSink(ConfigureRoomEvents.TopicChanged(it)) },
|
onTopicChange = { state.eventSink(ConfigureRoomEvents.TopicChanged(it)) },
|
||||||
)
|
)
|
||||||
if (state.config.invites.isNotEmpty()) {
|
|
||||||
SelectedUsersRowList(
|
|
||||||
contentPadding = PaddingValues(horizontal = 24.dp),
|
|
||||||
selectedUsers = state.config.invites,
|
|
||||||
onUserRemove = {
|
|
||||||
focusManager.clearFocus()
|
|
||||||
state.eventSink(ConfigureRoomEvents.RemoveUserFromSelection(it))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
RoomVisibilityOptions(
|
RoomVisibilityOptions(
|
||||||
selected = when (state.config.roomVisibility) {
|
selected = when (state.config.roomVisibility) {
|
||||||
is RoomVisibilityState.Private -> RoomVisibilityItem.Private
|
is RoomVisibilityState.Private -> RoomVisibilityItem.Private
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,9 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.createroom.impl
|
package io.element.android.features.createroom.impl.configureroom
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import io.element.android.features.createroom.impl.configureroom.RoomVisibilityState
|
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
@ -5,45 +5,29 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.createroom.impl
|
package io.element.android.features.createroom.impl.configureroom
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import io.element.android.features.createroom.impl.configureroom.RoomAccess
|
|
||||||
import io.element.android.features.createroom.impl.configureroom.RoomAccessItem
|
|
||||||
import io.element.android.features.createroom.impl.configureroom.RoomAddress
|
|
||||||
import io.element.android.features.createroom.impl.configureroom.RoomVisibilityItem
|
|
||||||
import io.element.android.features.createroom.impl.configureroom.RoomVisibilityState
|
|
||||||
import io.element.android.features.createroom.impl.di.CreateRoomScope
|
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListDataStore
|
|
||||||
import io.element.android.libraries.androidutils.file.safeDelete
|
import io.element.android.libraries.androidutils.file.safeDelete
|
||||||
import io.element.android.libraries.di.SingleIn
|
|
||||||
import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper
|
import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.getAndUpdate
|
import kotlinx.coroutines.flow.getAndUpdate
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@SingleIn(CreateRoomScope::class)
|
class CreateRoomConfigStore @Inject constructor(
|
||||||
class CreateRoomDataStore @Inject constructor(
|
|
||||||
val selectedUserListDataStore: UserListDataStore,
|
|
||||||
private val roomAliasHelper: RoomAliasHelper,
|
private val roomAliasHelper: RoomAliasHelper,
|
||||||
) {
|
) {
|
||||||
private val createRoomConfigFlow: MutableStateFlow<CreateRoomConfig> = MutableStateFlow(CreateRoomConfig())
|
private val createRoomConfigFlow: MutableStateFlow<CreateRoomConfig> = MutableStateFlow(CreateRoomConfig())
|
||||||
|
|
||||||
private var cachedAvatarUri: Uri? = null
|
private var cachedAvatarUri: Uri? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
field?.path?.let { File(it) }?.safeDelete()
|
field?.path?.let { File(it) }?.safeDelete()
|
||||||
field = value
|
field = value
|
||||||
}
|
}
|
||||||
|
|
||||||
val createRoomConfigWithInvites: Flow<CreateRoomConfig> = combine(
|
fun getCreateRoomConfigFlow(): StateFlow<CreateRoomConfig> = createRoomConfigFlow
|
||||||
selectedUserListDataStore.selectedUsers,
|
|
||||||
createRoomConfigFlow,
|
|
||||||
) { selectedUsers, config ->
|
|
||||||
config.copy(invites = selectedUsers.toImmutableList())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setRoomName(roomName: String) {
|
fun setRoomName(roomName: String) {
|
||||||
createRoomConfigFlow.getAndUpdate { config ->
|
createRoomConfigFlow.getAndUpdate { config ->
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.createroom.impl.di
|
|
||||||
|
|
||||||
import com.squareup.anvil.annotations.ContributesTo
|
|
||||||
import com.squareup.anvil.annotations.MergeSubcomponent
|
|
||||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
|
||||||
import io.element.android.libraries.di.SessionScope
|
|
||||||
import io.element.android.libraries.di.SingleIn
|
|
||||||
|
|
||||||
@SingleIn(CreateRoomScope::class)
|
|
||||||
@MergeSubcomponent(CreateRoomScope::class)
|
|
||||||
interface CreateRoomComponent : NodeFactoriesBindings {
|
|
||||||
@MergeSubcomponent.Builder
|
|
||||||
interface Builder {
|
|
||||||
fun build(): CreateRoomComponent
|
|
||||||
}
|
|
||||||
|
|
||||||
@ContributesTo(SessionScope::class)
|
|
||||||
interface ParentBindings {
|
|
||||||
fun createRoomComponentBuilder(): Builder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.createroom.impl.di
|
|
||||||
|
|
||||||
abstract class CreateRoomScope private constructor()
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.createroom.impl.root
|
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
|
||||||
|
|
||||||
sealed interface CreateRoomRootEvents {
|
|
||||||
data class StartDM(val matrixUser: MatrixUser) : CreateRoomRootEvents
|
|
||||||
data object CancelStartDM : CreateRoomRootEvents
|
|
||||||
}
|
|
||||||
|
|
@ -14,6 +14,4 @@
|
||||||
<string name="screen_create_room_room_name_label">"Назва пакоя"</string>
|
<string name="screen_create_room_room_name_label">"Назва пакоя"</string>
|
||||||
<string name="screen_create_room_title">"Стварыце пакой"</string>
|
<string name="screen_create_room_title">"Стварыце пакой"</string>
|
||||||
<string name="screen_create_room_topic_label">"Тэма (неабавязкова)"</string>
|
<string name="screen_create_room_topic_label">"Тэма (неабавязкова)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Каталог пакояў"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Пры спробе пачаць чат адбылася памылка"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,4 @@
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Видимост на стаята"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Видимост на стаята"</string>
|
||||||
<string name="screen_create_room_title">"Създаване на стая"</string>
|
<string name="screen_create_room_title">"Създаване на стая"</string>
|
||||||
<string name="screen_create_room_topic_label">"Тема за разговор (незадължително)"</string>
|
<string name="screen_create_room_topic_label">"Тема за разговор (незадължително)"</string>
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Присъединяване към стая по адрес"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Не е валиден адрес"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Въведете…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Стаята не е намерена"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"напр. #room-name:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@ To můžete kdykoli změnit v nastavení místnosti."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Viditelnost místnosti"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Viditelnost místnosti"</string>
|
||||||
<string name="screen_create_room_title">"Vytvořit místnost"</string>
|
<string name="screen_create_room_title">"Vytvořit místnost"</string>
|
||||||
<string name="screen_create_room_topic_label">"Téma (nepovinné)"</string>
|
<string name="screen_create_room_topic_label">"Téma (nepovinné)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Adresář místností"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Při pokusu o zahájení chatu došlo k chybě"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Vstoupit do místnosti pomocí adresy"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Neplatná adresa"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Zadejte…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Odpovídající místnost nalezena"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Místnost nebyla nalezena"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"např. #room-name:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@ Gallwch newid hyn unrhyw bryd yng ngosodiadau ystafell."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Gwelededd yr ystafell"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Gwelededd yr ystafell"</string>
|
||||||
<string name="screen_create_room_title">"Creu ystafell"</string>
|
<string name="screen_create_room_title">"Creu ystafell"</string>
|
||||||
<string name="screen_create_room_topic_label">"Pwnc (dewisol)"</string>
|
<string name="screen_create_room_topic_label">"Pwnc (dewisol)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Cyfeiriadur ystafelloedd"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Digwyddodd gwall wrth geisio cychwyn sgwrs"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Ymuno â\'r ystafell yn ôl cyfeiriad"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Ddim yn gyfeiriad dilys"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Ewch i mewn…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Cafwyd hyd i ystafell gyfatebol"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Heb ganfod yr ystafell"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"e.e. #enw-ystafell:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@ Du kan ændre dette når som helst i rummets indstillinger."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Rummets synlighed"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Rummets synlighed"</string>
|
||||||
<string name="screen_create_room_title">"Opret et rum"</string>
|
<string name="screen_create_room_title">"Opret et rum"</string>
|
||||||
<string name="screen_create_room_topic_label">"Emne (valgfrit)"</string>
|
<string name="screen_create_room_topic_label">"Emne (valgfrit)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Register over rum"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Der opstod en fejl under forsøget på at starte en samtale"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Tilslut dig rummet med adressen"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Ikke en gyldig adresse"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Indtast…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Matchende rum fundet"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Rum ikke fundet"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"f.eks. #rummets-navn:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@ Sie können dies aber jederzeit in den Chatroomeinstellungen ändern."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">" Sichtbarkeit des Chatrooms"</string>
|
<string name="screen_create_room_room_visibility_section_title">" Sichtbarkeit des Chatrooms"</string>
|
||||||
<string name="screen_create_room_title">"Raum erstellen"</string>
|
<string name="screen_create_room_title">"Raum erstellen"</string>
|
||||||
<string name="screen_create_room_topic_label">"Thema (optional)"</string>
|
<string name="screen_create_room_topic_label">"Thema (optional)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Raum-Verzeichnis"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Beim Versuch, einen Chat zu starten, ist ein Fehler aufgetreten"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Raum per Adresse betreten"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Keine gültige Adresse"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Eintreten…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Passender Raum gefunden"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Raum nicht gefunden"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"z. B. #room -name:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Ορατότητα αίθουσας"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Ορατότητα αίθουσας"</string>
|
||||||
<string name="screen_create_room_title">"Δημιουργία αίθουσας"</string>
|
<string name="screen_create_room_title">"Δημιουργία αίθουσας"</string>
|
||||||
<string name="screen_create_room_topic_label">"Θέμα (προαιρετικό)"</string>
|
<string name="screen_create_room_topic_label">"Θέμα (προαιρετικό)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Κατάλογος αιθουσών"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Παρουσιάστηκε σφάλμα κατά την προσπάθεια έναρξης μιας συνομιλίας"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Συμμετοχή σε αίθουσα μέσω διεύθυνσης"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Μη έγκυρη διεύθυνση"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Εισάγετε…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Βρέθηκε η αντίστοιχη αίθουσα"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Η αίθουσα δεν βρέθηκε"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"π.χ. #όνομα-αίθουσας:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@ Puedes cambiar esto en cualquier momento en los ajustes de la sala."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Visibilidad de la sala"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Visibilidad de la sala"</string>
|
||||||
<string name="screen_create_room_title">"Crear una sala"</string>
|
<string name="screen_create_room_title">"Crear una sala"</string>
|
||||||
<string name="screen_create_room_topic_label">"Tema (opcional)"</string>
|
<string name="screen_create_room_topic_label">"Tema (opcional)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Directorio de salas"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Se ha producido un error al intentar iniciar un chat"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Unirse a una sala por su dirección"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Dirección no válida"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Introducir…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Sala encontrada"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"No se encontró la sala"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"p. ej., #nombre-de-la-sala:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@ Sa võid seda jututoa seadistustest alati muuta."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Jututoa nähtavus"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Jututoa nähtavus"</string>
|
||||||
<string name="screen_create_room_title">"Loo jututuba"</string>
|
<string name="screen_create_room_title">"Loo jututuba"</string>
|
||||||
<string name="screen_create_room_topic_label">"Teema (kui soovid lisada)"</string>
|
<string name="screen_create_room_topic_label">"Teema (kui soovid lisada)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Jututubade kataloog"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Vestluse alustamisel tekkis viga"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Liitu jututoaga aadressi alusel"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"See pole kehtiv aadress"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Sisene…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Leidsime vastava jututoa"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Jututuba ei leidu"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"nt. #jututoa-nimi:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,4 @@ Gelaren ezarpenetan aldatu dezakezu hobespena."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Gelaren ikusgarritasuna"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Gelaren ikusgarritasuna"</string>
|
||||||
<string name="screen_create_room_title">"Sortu gela"</string>
|
<string name="screen_create_room_title">"Sortu gela"</string>
|
||||||
<string name="screen_create_room_topic_label">"Mintzagaia (aukerakoa)"</string>
|
<string name="screen_create_room_topic_label">"Mintzagaia (aukerakoa)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Gelen direktorioa"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Errorea gertatu da txata hasten saiatzean"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Sartu…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Ez da gela aurkitu"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,4 @@
|
||||||
<string name="screen_create_room_room_visibility_section_title">"نمایانی اتاق"</string>
|
<string name="screen_create_room_room_visibility_section_title">"نمایانی اتاق"</string>
|
||||||
<string name="screen_create_room_title">"ایجاد اتاق"</string>
|
<string name="screen_create_room_title">"ایجاد اتاق"</string>
|
||||||
<string name="screen_create_room_topic_label">"موضوع (اختیاری)"</string>
|
<string name="screen_create_room_topic_label">"موضوع (اختیاری)"</string>
|
||||||
<string name="screen_room_directory_search_title">"فهرست اتاقها"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"هنگام تلاش برای شروع چت خطایی روی داد"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"پیوستن به اتاق با نشانی"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"نشانی معتبری نیست"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"ورود…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"اتاق مطابق پیدا شد"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"اتاق پیدا نشد"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"نمونه: #room-name:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@ Voit muuttaa tämän milloin tahansa huoneen asetuksista."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Huoneen näkyvyys"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Huoneen näkyvyys"</string>
|
||||||
<string name="screen_create_room_title">"Luo huone"</string>
|
<string name="screen_create_room_title">"Luo huone"</string>
|
||||||
<string name="screen_create_room_topic_label">"Aihe (valinnainen)"</string>
|
<string name="screen_create_room_topic_label">"Aihe (valinnainen)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Huoneluettelo"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Keskustelun aloituksessa tapahtui virhe"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Liity huoneeseen osoitteella"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Osoite ei ole kelvollinen"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Syötä…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Täsmäävä huone löytyi"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Huonetta ei löytynyt"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"esim. #huoneen-nimi:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@ Vous pouvez modifier cela à tout moment dans les paramètres du salon."</string
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Visibilité du salon"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Visibilité du salon"</string>
|
||||||
<string name="screen_create_room_title">"Créer un salon"</string>
|
<string name="screen_create_room_title">"Créer un salon"</string>
|
||||||
<string name="screen_create_room_topic_label">"Sujet (facultatif)"</string>
|
<string name="screen_create_room_topic_label">"Sujet (facultatif)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Annuaire des salons"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Une erreur s’est produite lors de la tentative de création de la discussion"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Saisir une adresse de salon"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Ce n’est pas une adresse valide"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Saisir…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Ce salon existe"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Salon non trouvé"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"ex: #nom-du-salon:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@ Ezt bármikor módosíthatja a szobabeállításokban."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Szoba láthatósága"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Szoba láthatósága"</string>
|
||||||
<string name="screen_create_room_title">"Szoba létrehozása"</string>
|
<string name="screen_create_room_title">"Szoba létrehozása"</string>
|
||||||
<string name="screen_create_room_topic_label">"Téma (nem kötelező)"</string>
|
<string name="screen_create_room_topic_label">"Téma (nem kötelező)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Szobakatalógus"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Hiba történt a csevegés indításakor"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Csatlakozás a szobához cím szerint"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Nem érvényes cím"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Írja be…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Megfelelő szoba található"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Szoba nem található"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"pl. #szoba-neve:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@ Anda dapat mengubah ini kapan pun dalam pengaturan ruangan."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Keterlihatan ruangan"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Keterlihatan ruangan"</string>
|
||||||
<string name="screen_create_room_title">"Buat ruangan"</string>
|
<string name="screen_create_room_title">"Buat ruangan"</string>
|
||||||
<string name="screen_create_room_topic_label">"Topik (opsional)"</string>
|
<string name="screen_create_room_topic_label">"Topik (opsional)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Direktori ruangan"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Terjadi kesalahan saat mencoba memulai obrolan"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Bergabung dalam ruangan berdasarkan alamat"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Bukan alamat yang valid"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Masuk…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Ruangan yang cocok ditemukan"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Ruangan tidak ditemukan"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"mis. #nama-ruangan:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@ Puoi modificarlo in qualsiasi momento nelle impostazioni della stanza."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Visibilità della stanza"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Visibilità della stanza"</string>
|
||||||
<string name="screen_create_room_title">"Crea una stanza"</string>
|
<string name="screen_create_room_title">"Crea una stanza"</string>
|
||||||
<string name="screen_create_room_topic_label">"Argomento (facoltativo)"</string>
|
<string name="screen_create_room_topic_label">"Argomento (facoltativo)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Elenco delle stanze"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Si è verificato un errore durante il tentativo di avviare una chat"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Accedi alla stanza tramite indirizzo"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Indirizzo non valido"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Inserisci…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Stanza trovata"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Stanza non trovata"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"ad esempio #room -name:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,4 @@
|
||||||
<string name="screen_create_room_room_name_label">"ოთახის სახელი"</string>
|
<string name="screen_create_room_room_name_label">"ოთახის სახელი"</string>
|
||||||
<string name="screen_create_room_title">"ოთახის შექმნა"</string>
|
<string name="screen_create_room_title">"ოთახის შექმნა"</string>
|
||||||
<string name="screen_create_room_topic_label">"თემა (სურვილისამებრ)"</string>
|
<string name="screen_create_room_topic_label">"თემა (სურვილისამებრ)"</string>
|
||||||
<string name="screen_room_directory_search_title">"ოთახის კატალოგი"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"ჩატის დაწყების მცდელობისას შეცდომა მოხდა"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -10,5 +10,4 @@ Tai galite bet kada pakeisti kambario nustatymuose."</string>
|
||||||
<string name="screen_create_room_room_name_label">"Kambario pavadinimas"</string>
|
<string name="screen_create_room_room_name_label">"Kambario pavadinimas"</string>
|
||||||
<string name="screen_create_room_title">"Kurti kambarį"</string>
|
<string name="screen_create_room_title">"Kurti kambarį"</string>
|
||||||
<string name="screen_create_room_topic_label">"Tema (nebūtina)"</string>
|
<string name="screen_create_room_topic_label">"Tema (nebūtina)"</string>
|
||||||
<string name="screen_start_chat_error_starting_chat">"Bandant pradėti pokalbį įvyko klaida"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@ Du kan endre dette når som helst i rominnstillingene."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Romsynlighet"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Romsynlighet"</string>
|
||||||
<string name="screen_create_room_title">"Opprett et rom"</string>
|
<string name="screen_create_room_title">"Opprett et rom"</string>
|
||||||
<string name="screen_create_room_topic_label">"Emne (valgfritt)"</string>
|
<string name="screen_create_room_topic_label">"Emne (valgfritt)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Romkatalog"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Det oppstod en feil når du prøvde å starte en chat"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Bli med i rommet med adresse"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Ikke en gyldig adresse"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Gå inn…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Matchende rom funnet"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Rom ikke funnet"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"f.eks. #rom-navn:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,4 @@ Je kunt dit op elk gewenst moment wijzigen in de kamerinstellingen."</string>
|
||||||
<string name="screen_create_room_room_name_label">"Naam van de kamer"</string>
|
<string name="screen_create_room_room_name_label">"Naam van de kamer"</string>
|
||||||
<string name="screen_create_room_title">"Creëer een kamer"</string>
|
<string name="screen_create_room_title">"Creëer een kamer"</string>
|
||||||
<string name="screen_create_room_topic_label">"Onderwerp (optioneel)"</string>
|
<string name="screen_create_room_topic_label">"Onderwerp (optioneel)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Kamergids"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Er is een fout opgetreden bij het starten van een chat"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@ Możesz to zmienić w ustawieniach pokoju."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Widoczność pomieszczenia"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Widoczność pomieszczenia"</string>
|
||||||
<string name="screen_create_room_title">"Utwórz pokój"</string>
|
<string name="screen_create_room_title">"Utwórz pokój"</string>
|
||||||
<string name="screen_create_room_topic_label">"Temat (opcjonalnie)"</string>
|
<string name="screen_create_room_topic_label">"Temat (opcjonalnie)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Katalog pokoi"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Wystąpił błąd podczas próby rozpoczęcia czatu"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Dołącz do pokoju za pomocą adresu"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Nieprawidłowy adres"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Wprowadź…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Znaleziono pasujący pokój"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Nie znaleziono pokoju"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"np. #room-name:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@ Você pode mudar isso a qualquer momento nas configurações da sala."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Visibilidade da sala"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Visibilidade da sala"</string>
|
||||||
<string name="screen_create_room_title">"Criar uma sala"</string>
|
<string name="screen_create_room_title">"Criar uma sala"</string>
|
||||||
<string name="screen_create_room_topic_label">"Tópico (opcional)"</string>
|
<string name="screen_create_room_topic_label">"Tópico (opcional)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Diretório de salas"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Ocorreu um erro ao tentar iniciar um chat"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Entrar na sala pelo endereço"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Não é um endereço válido"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Entrar…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Foi encontrada uma sala correspondente"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Sala não encontrada"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"Por exemplo, #nome-da-sala:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@ Pode alterar esta opção nas definições da sala."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Visibilidade da sala"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Visibilidade da sala"</string>
|
||||||
<string name="screen_create_room_title">"Criar uma sala"</string>
|
<string name="screen_create_room_title">"Criar uma sala"</string>
|
||||||
<string name="screen_create_room_topic_label">"Descrição (opcional)"</string>
|
<string name="screen_create_room_topic_label">"Descrição (opcional)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Diretório de salas"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Ocorreu um erro ao tentar iniciar uma conversa"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Entrar na sala pelo endereço"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Não é um endereço válido"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Entrar…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Sala correspondente encontrado"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Sala não encontrada"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"por exemplo, #sala:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,4 @@ Puteți modifica acest lucru oricând în setări."</string>
|
||||||
<string name="screen_create_room_room_name_label">"Numele camerei"</string>
|
<string name="screen_create_room_room_name_label">"Numele camerei"</string>
|
||||||
<string name="screen_create_room_title">"Creați o cameră"</string>
|
<string name="screen_create_room_title">"Creați o cameră"</string>
|
||||||
<string name="screen_create_room_topic_label">"Subiect (opțional)"</string>
|
<string name="screen_create_room_topic_label">"Subiect (opțional)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Director de camere"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"A apărut o eroare la încercarea începerii conversației"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Видимость комнаты"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Видимость комнаты"</string>
|
||||||
<string name="screen_create_room_title">"Создать комнату"</string>
|
<string name="screen_create_room_title">"Создать комнату"</string>
|
||||||
<string name="screen_create_room_topic_label">"Тема (необязательно)"</string>
|
<string name="screen_create_room_topic_label">"Тема (необязательно)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Каталог комнат"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Произошла ошибка при запуске чата"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Присоединиться к комнате по адресу"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Недействительный адрес"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Ввести…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Соответствующая комната найдена"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Комната не найдена"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"прим. #room-name:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@ Môžete to kedykoľvek zmeniť v nastaveniach miestnosti."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Viditeľnosť miestnosti"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Viditeľnosť miestnosti"</string>
|
||||||
<string name="screen_create_room_title">"Vytvoriť miestnosť"</string>
|
<string name="screen_create_room_title">"Vytvoriť miestnosť"</string>
|
||||||
<string name="screen_create_room_topic_label">"Téma (voliteľné)"</string>
|
<string name="screen_create_room_topic_label">"Téma (voliteľné)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Adresár miestností"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Pri pokuse o spustenie konverzácie sa vyskytla chyba"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Pripojte sa do miestnosti podľa adresy"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Neplatná adresa"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Zadajte…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Nájdená zodpovedajúca miestnosť"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Miestnosť sa nenašla"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"napr. #nazov-miestnosti:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@ Du kan ändra detta när som helst i rumsinställningarna."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Rumssynlighet"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Rumssynlighet"</string>
|
||||||
<string name="screen_create_room_title">"Skapa ett rum"</string>
|
<string name="screen_create_room_title">"Skapa ett rum"</string>
|
||||||
<string name="screen_create_room_topic_label">"Ämne (valfritt)"</string>
|
<string name="screen_create_room_topic_label">"Ämne (valfritt)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Rumskatalog"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Ett fel uppstod när du försökte starta en chatt"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Gå med i rum med adress"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Inte en giltig adress"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Ange …"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Matchande rum hittades"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Rummet hittades inte"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"t.ex. #rumsnamn:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,4 @@ Bunu istediğiniz zaman oda ayarlarından değiştirebilirsiniz."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Oda görünürlüğü"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Oda görünürlüğü"</string>
|
||||||
<string name="screen_create_room_title">"Bir oda oluştur"</string>
|
<string name="screen_create_room_title">"Bir oda oluştur"</string>
|
||||||
<string name="screen_create_room_topic_label">"Konu (isteğe bağlı)"</string>
|
<string name="screen_create_room_topic_label">"Konu (isteğe bağlı)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Oda dizini"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Sohbet başlatmaya çalışırken bir hata oluştu"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Видимість кімнати"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Видимість кімнати"</string>
|
||||||
<string name="screen_create_room_title">"Створити кімнату"</string>
|
<string name="screen_create_room_title">"Створити кімнату"</string>
|
||||||
<string name="screen_create_room_topic_label">"Тема (необов\'язково)"</string>
|
<string name="screen_create_room_topic_label">"Тема (необов\'язково)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Каталог кімнат"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"Під час спроби почати бесіду сталася помилка"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Приєднатися до кімнати за адресою"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Недійсна адреса"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Введіть…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Знайдено відповідну кімнату"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Кімната не знайдена"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"наприклад, #room-name:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,4 @@
|
||||||
<string name="screen_create_room_room_name_label">"کمرے کا نام"</string>
|
<string name="screen_create_room_room_name_label">"کمرے کا نام"</string>
|
||||||
<string name="screen_create_room_title">"ایک کمرہ بنائیں"</string>
|
<string name="screen_create_room_title">"ایک کمرہ بنائیں"</string>
|
||||||
<string name="screen_create_room_topic_label">"موضوع (اختیاری)"</string>
|
<string name="screen_create_room_topic_label">"موضوع (اختیاری)"</string>
|
||||||
<string name="screen_room_directory_search_title">"کمرے کا راہنامچہ"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"گفتگو شروع کرنے کی کوشش کرتے وقت ایک خرابی واقع ہوگئی"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -9,5 +9,4 @@
|
||||||
<string name="screen_create_room_room_name_label">"Xona nomi"</string>
|
<string name="screen_create_room_room_name_label">"Xona nomi"</string>
|
||||||
<string name="screen_create_room_title">"Xonani yaratish"</string>
|
<string name="screen_create_room_title">"Xonani yaratish"</string>
|
||||||
<string name="screen_create_room_topic_label">"Mavzu (ixtiyoriy)"</string>
|
<string name="screen_create_room_topic_label">"Mavzu (ixtiyoriy)"</string>
|
||||||
<string name="screen_start_chat_error_starting_chat">"Suhbatni boshlashda xatolik yuz berdi"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@
|
||||||
<string name="screen_create_room_room_visibility_section_title">"聊天室能見度"</string>
|
<string name="screen_create_room_room_visibility_section_title">"聊天室能見度"</string>
|
||||||
<string name="screen_create_room_title">"建立聊天室"</string>
|
<string name="screen_create_room_title">"建立聊天室"</string>
|
||||||
<string name="screen_create_room_topic_label">"主題(非必填)"</string>
|
<string name="screen_create_room_topic_label">"主題(非必填)"</string>
|
||||||
<string name="screen_room_directory_search_title">"聊天室目錄"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"嘗試開始聊天時發生錯誤"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"按地址加入聊天室"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"不是有效的位址"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"輸入……"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"找到相符的聊天室"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"找不到聊天室"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"例如 #room-name:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,4 @@
|
||||||
<string name="screen_create_room_room_visibility_section_title">"房间可见性"</string>
|
<string name="screen_create_room_room_visibility_section_title">"房间可见性"</string>
|
||||||
<string name="screen_create_room_title">"创建聊天室"</string>
|
<string name="screen_create_room_title">"创建聊天室"</string>
|
||||||
<string name="screen_create_room_topic_label">"主题(可选)"</string>
|
<string name="screen_create_room_topic_label">"主题(可选)"</string>
|
||||||
<string name="screen_room_directory_search_title">"聊天室目录"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"在开始聊天时发生了错误"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@ You can change this anytime in room settings."</string>
|
||||||
<string name="screen_create_room_room_visibility_section_title">"Room visibility"</string>
|
<string name="screen_create_room_room_visibility_section_title">"Room visibility"</string>
|
||||||
<string name="screen_create_room_title">"Create a room"</string>
|
<string name="screen_create_room_title">"Create a room"</string>
|
||||||
<string name="screen_create_room_topic_label">"Topic (optional)"</string>
|
<string name="screen_create_room_topic_label">"Topic (optional)"</string>
|
||||||
<string name="screen_room_directory_search_title">"Room directory"</string>
|
|
||||||
<string name="screen_start_chat_error_starting_chat">"An error occurred when trying to start a chat"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_action">"Join room by address"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Not a valid address"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Enter…"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_found">"Matching room found"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Room not found"</string>
|
|
||||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"e.g. #room-name:matrix.org"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.createroom.impl.addpeople
|
|
||||||
|
|
||||||
import app.cash.molecule.RecompositionMode
|
|
||||||
import app.cash.molecule.moleculeFlow
|
|
||||||
import app.cash.turbine.test
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import io.element.android.features.createroom.impl.CreateRoomDataStore
|
|
||||||
import io.element.android.features.createroom.impl.userlist.FakeUserListPresenterFactory
|
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListDataStore
|
|
||||||
import io.element.android.libraries.matrix.test.room.alias.FakeRoomAliasHelper
|
|
||||||
import io.element.android.libraries.usersearch.test.FakeUserRepository
|
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class AddPeoplePresenterTest {
|
|
||||||
@get:Rule
|
|
||||||
val warmUpRule = WarmUpRule()
|
|
||||||
|
|
||||||
private lateinit var presenter: AddPeoplePresenter
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
presenter = AddPeoplePresenter(
|
|
||||||
FakeUserListPresenterFactory(),
|
|
||||||
FakeUserRepository(),
|
|
||||||
CreateRoomDataStore(UserListDataStore(), FakeRoomAliasHelper())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - initial state`() = runTest {
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
// TODO This doesn't actually test anything...
|
|
||||||
val initialState = awaitItem()
|
|
||||||
assertThat(initialState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.createroom.impl.addpeople
|
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListEvents
|
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListState
|
|
||||||
import io.element.android.features.createroom.impl.userlist.aUserListState
|
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
|
||||||
import io.element.android.tests.testutils.clickOn
|
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
|
||||||
import io.element.android.tests.testutils.pressBack
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
class AddPeopleViewTest {
|
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `clicking on back invokes the expected callback`() {
|
|
||||||
val eventsRecorder = EventsRecorder<UserListEvents>()
|
|
||||||
ensureCalledOnce {
|
|
||||||
rule.setAddPeopleView(
|
|
||||||
aUserListState(
|
|
||||||
eventSink = eventsRecorder,
|
|
||||||
),
|
|
||||||
onBackClick = it
|
|
||||||
)
|
|
||||||
rule.pressBack()
|
|
||||||
}
|
|
||||||
eventsRecorder.assertSingle(UserListEvents.UpdateSearchQuery(""))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `clicking on back during search emits the expected Event`() {
|
|
||||||
val eventsRecorder = EventsRecorder<UserListEvents>()
|
|
||||||
rule.setAddPeopleView(
|
|
||||||
aUserListState(
|
|
||||||
isSearchActive = true,
|
|
||||||
eventSink = eventsRecorder,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
rule.pressBack()
|
|
||||||
eventsRecorder.assertSingle(UserListEvents.OnSearchActiveChanged(false))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `clicking on skip invokes the expected callback`() {
|
|
||||||
val eventsRecorder = EventsRecorder<UserListEvents>()
|
|
||||||
ensureCalledOnce {
|
|
||||||
rule.setAddPeopleView(
|
|
||||||
aUserListState(
|
|
||||||
eventSink = eventsRecorder,
|
|
||||||
),
|
|
||||||
onNextClick = it
|
|
||||||
)
|
|
||||||
rule.clickOn(CommonStrings.action_skip)
|
|
||||||
}
|
|
||||||
eventsRecorder.assertSingle(UserListEvents.UpdateSearchQuery(""))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setAddPeopleView(
|
|
||||||
state: UserListState,
|
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
|
||||||
onNextClick: () -> Unit = EnsureNeverCalled(),
|
|
||||||
) {
|
|
||||||
setContent {
|
|
||||||
AddPeopleView(
|
|
||||||
state = state,
|
|
||||||
onBackClick = onBackClick,
|
|
||||||
onNextClick = onNextClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,15 +5,21 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.createroom.impl.configureroom
|
package io.element.android.features.startchat.impl.configureroom
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import app.cash.turbine.TurbineTestContext
|
import app.cash.turbine.TurbineTestContext
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import im.vector.app.features.analytics.plan.CreatedRoom
|
import im.vector.app.features.analytics.plan.CreatedRoom
|
||||||
import io.element.android.features.createroom.impl.CreateRoomConfig
|
import io.element.android.features.createroom.impl.configureroom.ConfigureRoomEvents
|
||||||
import io.element.android.features.createroom.impl.CreateRoomDataStore
|
import io.element.android.features.createroom.impl.configureroom.ConfigureRoomPresenter
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListDataStore
|
import io.element.android.features.createroom.impl.configureroom.ConfigureRoomState
|
||||||
|
import io.element.android.features.createroom.impl.configureroom.CreateRoomConfig
|
||||||
|
import io.element.android.features.createroom.impl.configureroom.CreateRoomConfigStore
|
||||||
|
import io.element.android.features.createroom.impl.configureroom.RoomAccess
|
||||||
|
import io.element.android.features.createroom.impl.configureroom.RoomAddress
|
||||||
|
import io.element.android.features.createroom.impl.configureroom.RoomVisibilityItem
|
||||||
|
import io.element.android.features.createroom.impl.configureroom.RoomVisibilityState
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
|
|
@ -28,7 +34,6 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.room.alias.FakeRoomAliasHelper
|
import io.element.android.libraries.matrix.test.room.alias.FakeRoomAliasHelper
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
|
||||||
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
||||||
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
|
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
|
||||||
import io.element.android.libraries.mediapickers.api.PickerProvider
|
import io.element.android.libraries.mediapickers.api.PickerProvider
|
||||||
|
|
@ -48,8 +53,6 @@ import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.mockkStatic
|
import io.mockk.mockkStatic
|
||||||
import io.mockk.unmockkAll
|
import io.mockk.unmockkAll
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.advanceUntilIdle
|
import kotlinx.coroutines.test.advanceUntilIdle
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -67,7 +70,7 @@ private const val AN_URI_FROM_CAMERA_2 = "content://uri_from_camera_2"
|
||||||
private const val AN_URI_FROM_GALLERY = "content://uri_from_gallery"
|
private const val AN_URI_FROM_GALLERY = "content://uri_from_gallery"
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner::class)
|
@RunWith(RobolectricTestRunner::class)
|
||||||
class ConfigureBaseRoomPresenterTest {
|
class ConfigureRoomPresenterTest {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|
||||||
|
|
@ -124,12 +127,11 @@ class ConfigureBaseRoomPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - state is updated when fields are changed`() = runTest {
|
fun `present - state is updated when fields are changed`() = runTest {
|
||||||
val userListDataStore = UserListDataStore()
|
|
||||||
val pickerProvider = FakePickerProvider()
|
val pickerProvider = FakePickerProvider()
|
||||||
val permissionsPresenter = FakePermissionsPresenter()
|
val permissionsPresenter = FakePermissionsPresenter()
|
||||||
val roomAliasHelper = FakeRoomAliasHelper()
|
val roomAliasHelper = FakeRoomAliasHelper()
|
||||||
val presenter = createConfigureRoomPresenter(
|
val presenter = createConfigureRoomPresenter(
|
||||||
createRoomDataStore = CreateRoomDataStore(userListDataStore, roomAliasHelper),
|
dataStore = CreateRoomConfigStore(roomAliasHelper),
|
||||||
pickerProvider = pickerProvider,
|
pickerProvider = pickerProvider,
|
||||||
permissionsPresenter = permissionsPresenter,
|
permissionsPresenter = permissionsPresenter,
|
||||||
)
|
)
|
||||||
|
|
@ -137,20 +139,9 @@ class ConfigureBaseRoomPresenterTest {
|
||||||
val initialState = initialState()
|
val initialState = initialState()
|
||||||
var expectedConfig = CreateRoomConfig()
|
var expectedConfig = CreateRoomConfig()
|
||||||
assertThat(initialState.config).isEqualTo(expectedConfig)
|
assertThat(initialState.config).isEqualTo(expectedConfig)
|
||||||
|
|
||||||
// Select User
|
|
||||||
val selectedUser1 = aMatrixUser()
|
|
||||||
val selectedUser2 = aMatrixUser("@id_of_bob:server.org", "Bob")
|
|
||||||
userListDataStore.selectUser(selectedUser1)
|
|
||||||
skipItems(1)
|
|
||||||
userListDataStore.selectUser(selectedUser2)
|
|
||||||
var newState = awaitItem()
|
|
||||||
expectedConfig = expectedConfig.copy(invites = persistentListOf(selectedUser1, selectedUser2))
|
|
||||||
assertThat(newState.config).isEqualTo(expectedConfig)
|
|
||||||
|
|
||||||
// Room name
|
// Room name
|
||||||
initialState.eventSink(ConfigureRoomEvents.RoomNameChanged(A_ROOM_NAME))
|
initialState.eventSink(ConfigureRoomEvents.RoomNameChanged(A_ROOM_NAME))
|
||||||
newState = awaitItem()
|
var newState = awaitItem()
|
||||||
expectedConfig = expectedConfig.copy(roomName = A_ROOM_NAME)
|
expectedConfig = expectedConfig.copy(roomName = A_ROOM_NAME)
|
||||||
assertThat(newState.config).isEqualTo(expectedConfig)
|
assertThat(newState.config).isEqualTo(expectedConfig)
|
||||||
|
|
||||||
|
|
@ -206,12 +197,6 @@ class ConfigureBaseRoomPresenterTest {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
assertThat(newState.config).isEqualTo(expectedConfig)
|
assertThat(newState.config).isEqualTo(expectedConfig)
|
||||||
|
|
||||||
// Remove user
|
|
||||||
newState.eventSink(ConfigureRoomEvents.RemoveUserFromSelection(selectedUser1))
|
|
||||||
newState = awaitItem()
|
|
||||||
expectedConfig = expectedConfig.copy(invites = expectedConfig.invites.minus(selectedUser1).toImmutableList())
|
|
||||||
assertThat(newState.config).isEqualTo(expectedConfig)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -263,16 +248,16 @@ class ConfigureBaseRoomPresenterTest {
|
||||||
val matrixClient = createMatrixClient()
|
val matrixClient = createMatrixClient()
|
||||||
val analyticsService = FakeAnalyticsService()
|
val analyticsService = FakeAnalyticsService()
|
||||||
val mediaPreProcessor = FakeMediaPreProcessor()
|
val mediaPreProcessor = FakeMediaPreProcessor()
|
||||||
val createRoomDataStore = CreateRoomDataStore(UserListDataStore(), FakeRoomAliasHelper())
|
val dataStore = CreateRoomConfigStore(FakeRoomAliasHelper())
|
||||||
val presenter = createConfigureRoomPresenter(
|
val presenter = createConfigureRoomPresenter(
|
||||||
createRoomDataStore = createRoomDataStore,
|
dataStore = dataStore,
|
||||||
mediaPreProcessor = mediaPreProcessor,
|
mediaPreProcessor = mediaPreProcessor,
|
||||||
matrixClient = matrixClient,
|
matrixClient = matrixClient,
|
||||||
analyticsService = analyticsService
|
analyticsService = analyticsService
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = initialState()
|
val initialState = initialState()
|
||||||
createRoomDataStore.setAvatarUri(Uri.parse(AN_URI_FROM_GALLERY))
|
dataStore.setAvatarUri(Uri.parse(AN_URI_FROM_GALLERY))
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
mediaPreProcessor.givenResult(Result.success(MediaUploadInfo.Image(mockk(), mockk(), mockk())))
|
mediaPreProcessor.givenResult(Result.success(MediaUploadInfo.Image(mockk(), mockk(), mockk())))
|
||||||
matrixClient.givenUploadMediaResult(Result.failure(AN_EXCEPTION))
|
matrixClient.givenUploadMediaResult(Result.failure(AN_EXCEPTION))
|
||||||
|
|
@ -405,7 +390,7 @@ class ConfigureBaseRoomPresenterTest {
|
||||||
|
|
||||||
private fun createConfigureRoomPresenter(
|
private fun createConfigureRoomPresenter(
|
||||||
roomAliasHelper: RoomAliasHelper = FakeRoomAliasHelper(),
|
roomAliasHelper: RoomAliasHelper = FakeRoomAliasHelper(),
|
||||||
createRoomDataStore: CreateRoomDataStore = CreateRoomDataStore(UserListDataStore(), roomAliasHelper),
|
dataStore: CreateRoomConfigStore = CreateRoomConfigStore(roomAliasHelper),
|
||||||
matrixClient: MatrixClient = createMatrixClient(),
|
matrixClient: MatrixClient = createMatrixClient(),
|
||||||
pickerProvider: PickerProvider = FakePickerProvider(),
|
pickerProvider: PickerProvider = FakePickerProvider(),
|
||||||
mediaPreProcessor: MediaPreProcessor = FakeMediaPreProcessor(),
|
mediaPreProcessor: MediaPreProcessor = FakeMediaPreProcessor(),
|
||||||
|
|
@ -414,7 +399,7 @@ class ConfigureBaseRoomPresenterTest {
|
||||||
isKnockFeatureEnabled: Boolean = true,
|
isKnockFeatureEnabled: Boolean = true,
|
||||||
mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(),
|
mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(),
|
||||||
) = ConfigureRoomPresenter(
|
) = ConfigureRoomPresenter(
|
||||||
dataStore = createRoomDataStore,
|
dataStore = dataStore,
|
||||||
matrixClient = matrixClient,
|
matrixClient = matrixClient,
|
||||||
mediaPickerProvider = pickerProvider,
|
mediaPickerProvider = pickerProvider,
|
||||||
mediaPreProcessor = mediaPreProcessor,
|
mediaPreProcessor = mediaPreProcessor,
|
||||||
|
|
@ -22,7 +22,7 @@ interface HomeEntryPoint : FeatureEntryPoint {
|
||||||
|
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun onRoomClick(roomId: RoomId)
|
fun onRoomClick(roomId: RoomId)
|
||||||
fun onCreateRoomClick()
|
fun onStartChatClick()
|
||||||
fun onSettingsClick()
|
fun onSettingsClick()
|
||||||
fun onSetUpRecoveryClick()
|
fun onSetUpRecoveryClick()
|
||||||
fun onSessionConfirmRecoveryKeyClick()
|
fun onSessionConfirmRecoveryKeyClick()
|
||||||
|
|
|
||||||
|
|
@ -120,8 +120,8 @@ class HomeFlowNode @AssistedInject constructor(
|
||||||
plugins<HomeEntryPoint.Callback>().forEach { it.onSettingsClick() }
|
plugins<HomeEntryPoint.Callback>().forEach { it.onSettingsClick() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onCreateRoomClick() {
|
private fun onStartChatClick() {
|
||||||
plugins<HomeEntryPoint.Callback>().forEach { it.onCreateRoomClick() }
|
plugins<HomeEntryPoint.Callback>().forEach { it.onStartChatClick() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onSetUpRecoveryClick() {
|
private fun onSetUpRecoveryClick() {
|
||||||
|
|
@ -171,7 +171,7 @@ class HomeFlowNode @AssistedInject constructor(
|
||||||
homeState = state,
|
homeState = state,
|
||||||
onRoomClick = this::onRoomClick,
|
onRoomClick = this::onRoomClick,
|
||||||
onSettingsClick = this::onOpenSettings,
|
onSettingsClick = this::onOpenSettings,
|
||||||
onCreateRoomClick = this::onCreateRoomClick,
|
onStartChatClick = this::onStartChatClick,
|
||||||
onSetUpRecoveryClick = this::onSetUpRecoveryClick,
|
onSetUpRecoveryClick = this::onSetUpRecoveryClick,
|
||||||
onConfirmRecoveryKeyClick = this::onSessionConfirmRecoveryKeyClick,
|
onConfirmRecoveryKeyClick = this::onSessionConfirmRecoveryKeyClick,
|
||||||
onRoomSettingsClick = this::onRoomSettingsClick,
|
onRoomSettingsClick = this::onRoomSettingsClick,
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ fun HomeView(
|
||||||
onSettingsClick: () -> Unit,
|
onSettingsClick: () -> Unit,
|
||||||
onSetUpRecoveryClick: () -> Unit,
|
onSetUpRecoveryClick: () -> Unit,
|
||||||
onConfirmRecoveryKeyClick: () -> Unit,
|
onConfirmRecoveryKeyClick: () -> Unit,
|
||||||
onCreateRoomClick: () -> Unit,
|
onStartChatClick: () -> Unit,
|
||||||
onRoomSettingsClick: (roomId: RoomId) -> Unit,
|
onRoomSettingsClick: (roomId: RoomId) -> Unit,
|
||||||
onMenuActionClick: (RoomListMenuAction) -> Unit,
|
onMenuActionClick: (RoomListMenuAction) -> Unit,
|
||||||
onReportRoomClick: (roomId: RoomId) -> Unit,
|
onReportRoomClick: (roomId: RoomId) -> Unit,
|
||||||
|
|
@ -116,7 +116,7 @@ fun HomeView(
|
||||||
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
||||||
onRoomClick = { if (firstThrottler.canHandle()) onRoomClick(it) },
|
onRoomClick = { if (firstThrottler.canHandle()) onRoomClick(it) },
|
||||||
onOpenSettings = { if (firstThrottler.canHandle()) onSettingsClick() },
|
onOpenSettings = { if (firstThrottler.canHandle()) onSettingsClick() },
|
||||||
onCreateRoomClick = { if (firstThrottler.canHandle()) onCreateRoomClick() },
|
onStartChatClick = { if (firstThrottler.canHandle()) onStartChatClick() },
|
||||||
onMenuActionClick = onMenuActionClick,
|
onMenuActionClick = onMenuActionClick,
|
||||||
modifier = Modifier.padding(top = topPadding),
|
modifier = Modifier.padding(top = topPadding),
|
||||||
)
|
)
|
||||||
|
|
@ -145,7 +145,7 @@ private fun HomeScaffold(
|
||||||
onConfirmRecoveryKeyClick: () -> Unit,
|
onConfirmRecoveryKeyClick: () -> Unit,
|
||||||
onRoomClick: (RoomId) -> Unit,
|
onRoomClick: (RoomId) -> Unit,
|
||||||
onOpenSettings: () -> Unit,
|
onOpenSettings: () -> Unit,
|
||||||
onCreateRoomClick: () -> Unit,
|
onStartChatClick: () -> Unit,
|
||||||
onMenuActionClick: (RoomListMenuAction) -> Unit,
|
onMenuActionClick: (RoomListMenuAction) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
@ -236,7 +236,7 @@ private fun HomeScaffold(
|
||||||
onSetUpRecoveryClick = onSetUpRecoveryClick,
|
onSetUpRecoveryClick = onSetUpRecoveryClick,
|
||||||
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
||||||
onRoomClick = ::onRoomClick,
|
onRoomClick = ::onRoomClick,
|
||||||
onCreateRoomClick = onCreateRoomClick,
|
onCreateRoomClick = onStartChatClick,
|
||||||
contentPadding = PaddingValues(
|
contentPadding = PaddingValues(
|
||||||
// FAB height is 56dp, bottom padding is 16dp, we add 8dp as extra margin -> 56+16+8 = 80,
|
// FAB height is 56dp, bottom padding is 16dp, we add 8dp as extra margin -> 56+16+8 = 80,
|
||||||
// and include provided bottom padding
|
// and include provided bottom padding
|
||||||
|
|
@ -280,7 +280,7 @@ private fun HomeScaffold(
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
if (state.displayActions) {
|
if (state.displayActions) {
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = onCreateRoomClick,
|
onClick = onStartChatClick,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = CompoundIcons.Plus(),
|
imageVector = CompoundIcons.Plus(),
|
||||||
|
|
@ -304,7 +304,7 @@ internal fun HomeViewPreview(@PreviewParameter(HomeStateProvider::class) state:
|
||||||
onSettingsClick = {},
|
onSettingsClick = {},
|
||||||
onSetUpRecoveryClick = {},
|
onSetUpRecoveryClick = {},
|
||||||
onConfirmRecoveryKeyClick = {},
|
onConfirmRecoveryKeyClick = {},
|
||||||
onCreateRoomClick = {},
|
onStartChatClick = {},
|
||||||
onRoomSettingsClick = {},
|
onRoomSettingsClick = {},
|
||||||
onReportRoomClick = {},
|
onReportRoomClick = {},
|
||||||
onMenuActionClick = {},
|
onMenuActionClick = {},
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ For now, you can deselect filters in order to see your other chats"</string>
|
||||||
<string name="screen_roomlist_filter_invites">"Invites"</string>
|
<string name="screen_roomlist_filter_invites">"Invites"</string>
|
||||||
<string name="screen_roomlist_filter_invites_empty_state_title">"You don\'t have any pending invites."</string>
|
<string name="screen_roomlist_filter_invites_empty_state_title">"You don\'t have any pending invites."</string>
|
||||||
<string name="screen_roomlist_filter_low_priority">"Low Priority"</string>
|
<string name="screen_roomlist_filter_low_priority">"Low Priority"</string>
|
||||||
|
<string name="screen_roomlist_filter_low_priority_empty_state_title">"You don’t have any low priority chats yet"</string>
|
||||||
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"You can deselect filters in order to see your other chats"</string>
|
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"You can deselect filters in order to see your other chats"</string>
|
||||||
<string name="screen_roomlist_filter_mixed_empty_state_title">"You don’t have chats for this selection"</string>
|
<string name="screen_roomlist_filter_mixed_empty_state_title">"You don’t have chats for this selection"</string>
|
||||||
<string name="screen_roomlist_filter_people">"People"</string>
|
<string name="screen_roomlist_filter_people">"People"</string>
|
||||||
|
|
|
||||||
|
|
@ -284,7 +284,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomL
|
||||||
onSettingsClick = onSettingsClick,
|
onSettingsClick = onSettingsClick,
|
||||||
onSetUpRecoveryClick = onSetUpRecoveryClick,
|
onSetUpRecoveryClick = onSetUpRecoveryClick,
|
||||||
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
||||||
onCreateRoomClick = onCreateRoomClick,
|
onStartChatClick = onCreateRoomClick,
|
||||||
onRoomSettingsClick = onRoomSettingsClick,
|
onRoomSettingsClick = onRoomSettingsClick,
|
||||||
onMenuActionClick = onMenuActionClick,
|
onMenuActionClick = onMenuActionClick,
|
||||||
onDeclineInviteAndBlockUser = onDeclineInviteAndBlockUser,
|
onDeclineInviteAndBlockUser = onDeclineInviteAndBlockUser,
|
||||||
|
|
|
||||||
18
features/invitepeople/api/build.gradle.kts
Normal file
18
features/invitepeople/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
plugins {
|
||||||
|
id("io.element.android-compose-library")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "io.element.android.features.invitepeople.api"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(projects.libraries.architecture)
|
||||||
|
implementation(projects.libraries.matrix.api)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.invitepeople.api
|
||||||
|
|
||||||
|
interface InvitePeopleEvents {
|
||||||
|
data object SendInvites : InvitePeopleEvents
|
||||||
|
data object CloseSearch : InvitePeopleEvents
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.invitepeople.api
|
||||||
|
|
||||||
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
|
|
||||||
|
interface InvitePeoplePresenter : Presenter<InvitePeopleState> {
|
||||||
|
interface Factory {
|
||||||
|
fun create(
|
||||||
|
joinedRoom: JoinedRoom?,
|
||||||
|
roomId: RoomId,
|
||||||
|
): InvitePeoplePresenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.invitepeople.api
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
|
interface InvitePeopleRenderer {
|
||||||
|
@Composable
|
||||||
|
fun Render(
|
||||||
|
state: InvitePeopleState,
|
||||||
|
modifier: Modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.invitepeople.api
|
||||||
|
|
||||||
|
interface InvitePeopleState {
|
||||||
|
val canInvite: Boolean
|
||||||
|
val isSearchActive: Boolean
|
||||||
|
val eventSink: (InvitePeopleEvents) -> Unit
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.invitepeople.api
|
||||||
|
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
|
||||||
|
class InvitePeopleStateProvider : PreviewParameterProvider<InvitePeopleState> {
|
||||||
|
override val values: Sequence<InvitePeopleState>
|
||||||
|
get() = sequenceOf(
|
||||||
|
aPreviewInvitePeopleState(),
|
||||||
|
aPreviewInvitePeopleState(canInvite = true),
|
||||||
|
aPreviewInvitePeopleState(isSearchActive = true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class PreviewInvitePeopleState(
|
||||||
|
override val canInvite: Boolean,
|
||||||
|
override val isSearchActive: Boolean,
|
||||||
|
override val eventSink: (InvitePeopleEvents) -> Unit,
|
||||||
|
) : InvitePeopleState
|
||||||
|
|
||||||
|
private fun aPreviewInvitePeopleState(
|
||||||
|
canInvite: Boolean = false,
|
||||||
|
isSearchActive: Boolean = false,
|
||||||
|
eventSink: (InvitePeopleEvents) -> Unit = {},
|
||||||
|
) = PreviewInvitePeopleState(
|
||||||
|
canInvite = canInvite,
|
||||||
|
isSearchActive = isSearchActive,
|
||||||
|
eventSink = eventSink
|
||||||
|
)
|
||||||
53
features/invitepeople/impl/build.gradle.kts
Normal file
53
features/invitepeople/impl/build.gradle.kts
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2022-2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("io.element.android-compose-library")
|
||||||
|
id("kotlin-parcelize")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "io.element.android.features.invitepeople.impl"
|
||||||
|
|
||||||
|
testOptions {
|
||||||
|
unitTests {
|
||||||
|
isIncludeAndroidResources = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupAnvil()
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(projects.libraries.core)
|
||||||
|
implementation(projects.libraries.architecture)
|
||||||
|
implementation(projects.libraries.matrix.api)
|
||||||
|
implementation(projects.libraries.matrixui)
|
||||||
|
implementation(projects.libraries.designsystem)
|
||||||
|
implementation(projects.libraries.uiStrings)
|
||||||
|
implementation(projects.libraries.androidutils)
|
||||||
|
implementation(projects.libraries.usersearch.impl)
|
||||||
|
implementation(libs.coil.compose)
|
||||||
|
implementation(projects.services.apperror.api)
|
||||||
|
api(projects.features.invitepeople.api)
|
||||||
|
|
||||||
|
testImplementation(libs.test.junit)
|
||||||
|
testImplementation(libs.test.mockk)
|
||||||
|
testImplementation(libs.coroutines.test)
|
||||||
|
testImplementation(libs.molecule.runtime)
|
||||||
|
testImplementation(libs.test.truth)
|
||||||
|
testImplementation(libs.test.turbine)
|
||||||
|
testImplementation(libs.test.robolectric)
|
||||||
|
testImplementation(projects.libraries.matrix.test)
|
||||||
|
testImplementation(projects.libraries.usersearch.test)
|
||||||
|
testImplementation(projects.services.apperror.test)
|
||||||
|
testImplementation(projects.tests.testutils)
|
||||||
|
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||||
|
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.invitepeople.impl
|
||||||
|
|
||||||
|
import io.element.android.features.invitepeople.api.InvitePeopleEvents
|
||||||
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
|
||||||
|
sealed interface DefaultInvitePeopleEvents : InvitePeopleEvents {
|
||||||
|
data class ToggleUser(val user: MatrixUser) : DefaultInvitePeopleEvents
|
||||||
|
data class UpdateSearchQuery(val query: String) : DefaultInvitePeopleEvents
|
||||||
|
data class OnSearchActiveChanged(val active: Boolean) : DefaultInvitePeopleEvents
|
||||||
|
}
|
||||||
|
|
@ -5,50 +5,88 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.invite
|
package io.element.android.features.invitepeople.impl
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import io.element.android.features.roomdetails.impl.members.RoomMemberListDataSource
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import io.element.android.features.invitepeople.api.InvitePeopleEvents
|
||||||
|
import io.element.android.features.invitepeople.api.InvitePeoplePresenter
|
||||||
|
import io.element.android.features.invitepeople.api.InvitePeopleState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.map
|
||||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||||
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||||
|
import io.element.android.libraries.matrix.api.room.filterMembers
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.libraries.usersearch.api.UserRepository
|
import io.element.android.libraries.usersearch.api.UserRepository
|
||||||
|
import io.element.android.services.apperror.api.AppErrorStateService
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class RoomInviteMembersPresenter @Inject constructor(
|
class DefaultInvitePeoplePresenter @AssistedInject constructor(
|
||||||
|
@Assisted private val joinedRoom: JoinedRoom?,
|
||||||
|
@Assisted private val roomId: RoomId,
|
||||||
private val userRepository: UserRepository,
|
private val userRepository: UserRepository,
|
||||||
private val roomMemberListDataSource: RoomMemberListDataSource,
|
|
||||||
private val coroutineDispatchers: CoroutineDispatchers,
|
private val coroutineDispatchers: CoroutineDispatchers,
|
||||||
) : Presenter<RoomInviteMembersState> {
|
@SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope,
|
||||||
|
private val appErrorStateService: AppErrorStateService,
|
||||||
|
private val matrixClient: MatrixClient,
|
||||||
|
) : InvitePeoplePresenter {
|
||||||
|
@AssistedFactory
|
||||||
|
@ContributesBinding(SessionScope::class)
|
||||||
|
interface Factory : InvitePeoplePresenter.Factory {
|
||||||
|
override fun create(joinedRoom: JoinedRoom?, roomId: RoomId): DefaultInvitePeoplePresenter
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): RoomInviteMembersState {
|
override fun present(): InvitePeopleState {
|
||||||
val roomMembers = remember { mutableStateOf<AsyncData<ImmutableList<RoomMember>>>(AsyncData.Loading()) }
|
val roomMembers = remember { mutableStateOf<AsyncData<ImmutableList<RoomMember>>>(AsyncData.Loading()) }
|
||||||
val selectedUsers = remember { mutableStateOf<ImmutableList<MatrixUser>>(persistentListOf()) }
|
val selectedUsers = remember { mutableStateOf<ImmutableList<MatrixUser>>(persistentListOf()) }
|
||||||
val searchResults = remember { mutableStateOf<SearchBarResultState<ImmutableList<InvitableUser>>>(SearchBarResultState.Initial()) }
|
val searchResults = remember { mutableStateOf<SearchBarResultState<ImmutableList<InvitableUser>>>(SearchBarResultState.Initial()) }
|
||||||
var searchQuery by rememberSaveable { mutableStateOf("") }
|
var searchQuery by rememberSaveable { mutableStateOf("") }
|
||||||
var searchActive by rememberSaveable { mutableStateOf(false) }
|
var searchActive by rememberSaveable { mutableStateOf(false) }
|
||||||
val showSearchLoader = rememberSaveable { mutableStateOf(false) }
|
val showSearchLoader = rememberSaveable { mutableStateOf(false) }
|
||||||
|
val room by produceState(if (joinedRoom != null) AsyncData.Success(joinedRoom) else AsyncData.Loading()) {
|
||||||
|
if (joinedRoom == null) {
|
||||||
|
val result = matrixClient.getJoinedRoom(roomId)
|
||||||
|
value = if (result == null) {
|
||||||
|
AsyncData.Failure(Exception("Room not found"))
|
||||||
|
} else {
|
||||||
|
AsyncData.Success(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(room.isSuccess()) {
|
||||||
fetchMembers(roomMembers)
|
room.dataOrNull()?.let {
|
||||||
|
fetchMembers(it, roomMembers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
LaunchedEffect(searchQuery, roomMembers) {
|
LaunchedEffect(searchQuery, roomMembers) {
|
||||||
performSearch(
|
performSearch(
|
||||||
|
|
@ -60,33 +98,61 @@ class RoomInviteMembersPresenter @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return RoomInviteMembersState(
|
fun handleEvents(event: InvitePeopleEvents) {
|
||||||
|
when (event) {
|
||||||
|
is DefaultInvitePeopleEvents.OnSearchActiveChanged -> {
|
||||||
|
searchActive = event.active
|
||||||
|
searchQuery = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
is DefaultInvitePeopleEvents.UpdateSearchQuery -> {
|
||||||
|
searchQuery = event.query
|
||||||
|
}
|
||||||
|
|
||||||
|
is DefaultInvitePeopleEvents.ToggleUser -> {
|
||||||
|
selectedUsers.toggleUser(event.user)
|
||||||
|
searchResults.toggleUser(event.user)
|
||||||
|
}
|
||||||
|
is InvitePeopleEvents.SendInvites -> {
|
||||||
|
room.dataOrNull()?.let {
|
||||||
|
sessionCoroutineScope.sendInvites(it, selectedUsers.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is InvitePeopleEvents.CloseSearch -> {
|
||||||
|
searchActive = false
|
||||||
|
searchQuery = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefaultInvitePeopleState(
|
||||||
|
room = room.map { },
|
||||||
canInvite = selectedUsers.value.isNotEmpty(),
|
canInvite = selectedUsers.value.isNotEmpty(),
|
||||||
selectedUsers = selectedUsers.value,
|
selectedUsers = selectedUsers.value,
|
||||||
searchQuery = searchQuery,
|
searchQuery = searchQuery,
|
||||||
isSearchActive = searchActive,
|
isSearchActive = searchActive,
|
||||||
searchResults = searchResults.value,
|
searchResults = searchResults.value,
|
||||||
showSearchLoader = showSearchLoader.value,
|
showSearchLoader = showSearchLoader.value,
|
||||||
eventSink = {
|
eventSink = ::handleEvents,
|
||||||
when (it) {
|
|
||||||
is RoomInviteMembersEvents.OnSearchActiveChanged -> {
|
|
||||||
searchActive = it.active
|
|
||||||
searchQuery = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
is RoomInviteMembersEvents.UpdateSearchQuery -> {
|
|
||||||
searchQuery = it.query
|
|
||||||
}
|
|
||||||
|
|
||||||
is RoomInviteMembersEvents.ToggleUser -> {
|
|
||||||
selectedUsers.toggleUser(it.user)
|
|
||||||
searchResults.toggleUser(it.user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun CoroutineScope.sendInvites(
|
||||||
|
room: JoinedRoom,
|
||||||
|
selectedUsers: List<MatrixUser>,
|
||||||
|
) = launch {
|
||||||
|
val anyInviteFailed = selectedUsers
|
||||||
|
.map { room.inviteUserById(it.userId) }
|
||||||
|
.any { it.isFailure }
|
||||||
|
|
||||||
|
if (anyInviteFailed) {
|
||||||
|
appErrorStateService.showError(
|
||||||
|
titleRes = CommonStrings.common_unable_to_invite_title,
|
||||||
|
bodyRes = CommonStrings.common_unable_to_invite_message,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@JvmName("toggleUserInSelectedUsers")
|
@JvmName("toggleUserInSelectedUsers")
|
||||||
private fun MutableState<ImmutableList<MatrixUser>>.toggleUser(user: MatrixUser) {
|
private fun MutableState<ImmutableList<MatrixUser>>.toggleUser(user: MatrixUser) {
|
||||||
value = if (value.contains(user)) {
|
value = if (value.contains(user)) {
|
||||||
|
|
@ -134,7 +200,7 @@ class RoomInviteMembersPresenter @Inject constructor(
|
||||||
val isInvited = existingMembership == RoomMembershipState.INVITE
|
val isInvited = existingMembership == RoomMembershipState.INVITE
|
||||||
InvitableUser(
|
InvitableUser(
|
||||||
matrixUser = result.matrixUser,
|
matrixUser = result.matrixUser,
|
||||||
isSelected = selectedUsers.value.contains(result.matrixUser) || isJoined || isInvited,
|
isSelected = selectedUsers.value.contains(result.matrixUser),
|
||||||
isAlreadyJoined = isJoined,
|
isAlreadyJoined = isJoined,
|
||||||
isAlreadyInvited = isInvited,
|
isAlreadyInvited = isInvited,
|
||||||
isUnresolved = result.isUnresolved,
|
isUnresolved = result.isUnresolved,
|
||||||
|
|
@ -144,11 +210,12 @@ class RoomInviteMembersPresenter @Inject constructor(
|
||||||
}.launchIn(this)
|
}.launchIn(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchMembers(roomMembers: MutableState<AsyncData<ImmutableList<RoomMember>>>) {
|
private suspend fun fetchMembers(
|
||||||
|
room: JoinedRoom,
|
||||||
|
roomMembers: MutableState<AsyncData<ImmutableList<RoomMember>>>
|
||||||
|
) {
|
||||||
suspend {
|
suspend {
|
||||||
withContext(coroutineDispatchers.io) {
|
room.filterMembers("", coroutineDispatchers.io).toImmutableList()
|
||||||
roomMemberListDataSource.search("").toImmutableList()
|
|
||||||
}
|
|
||||||
}.runCatchingUpdatingState(roomMembers)
|
}.runCatchingUpdatingState(roomMembers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.invitepeople.impl
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
|
import io.element.android.features.invitepeople.api.InvitePeopleRenderer
|
||||||
|
import io.element.android.features.invitepeople.api.InvitePeopleState
|
||||||
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ContributesBinding(SessionScope::class)
|
||||||
|
class DefaultInvitePeopleRenderer @Inject constructor() : InvitePeopleRenderer {
|
||||||
|
@Composable
|
||||||
|
override fun Render(state: InvitePeopleState, modifier: Modifier) {
|
||||||
|
if (state is DefaultInvitePeopleState) {
|
||||||
|
InvitePeopleView(
|
||||||
|
state = state,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
error("Unsupported state type: ${state::javaClass}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,30 +1,26 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
* Copyright 2025 New Vector Ltd.
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.invite
|
package io.element.android.features.invitepeople.impl
|
||||||
|
|
||||||
|
import io.element.android.features.invitepeople.api.InvitePeopleEvents
|
||||||
|
import io.element.android.features.invitepeople.api.InvitePeopleState
|
||||||
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
data class RoomInviteMembersState(
|
data class DefaultInvitePeopleState(
|
||||||
val canInvite: Boolean,
|
val room: AsyncData<Unit>,
|
||||||
|
override val canInvite: Boolean,
|
||||||
val searchQuery: String,
|
val searchQuery: String,
|
||||||
val showSearchLoader: Boolean,
|
val showSearchLoader: Boolean,
|
||||||
val searchResults: SearchBarResultState<ImmutableList<InvitableUser>>,
|
val searchResults: SearchBarResultState<ImmutableList<InvitableUser>>,
|
||||||
val selectedUsers: ImmutableList<MatrixUser>,
|
val selectedUsers: ImmutableList<MatrixUser>,
|
||||||
val isSearchActive: Boolean,
|
override val isSearchActive: Boolean,
|
||||||
val eventSink: (RoomInviteMembersEvents) -> Unit,
|
override val eventSink: (InvitePeopleEvents) -> Unit
|
||||||
)
|
) : InvitePeopleState
|
||||||
|
|
||||||
data class InvitableUser(
|
|
||||||
val matrixUser: MatrixUser,
|
|
||||||
val isSelected: Boolean = false,
|
|
||||||
val isAlreadyJoined: Boolean = false,
|
|
||||||
val isAlreadyInvited: Boolean = false,
|
|
||||||
val isUnresolved: Boolean = false,
|
|
||||||
)
|
|
||||||
|
|
@ -5,9 +5,10 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.invite
|
package io.element.android.features.invitepeople.impl
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||||
|
|
@ -16,15 +17,15 @@ import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
internal class RoomInviteMembersStateProvider : PreviewParameterProvider<RoomInviteMembersState> {
|
internal class DefaultInvitePeopleStateProvider : PreviewParameterProvider<DefaultInvitePeopleState> {
|
||||||
override val values: Sequence<RoomInviteMembersState>
|
override val values: Sequence<DefaultInvitePeopleState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aRoomInviteMembersState(),
|
aDefaultInvitePeopleState(),
|
||||||
aRoomInviteMembersState(canInvite = true, selectedUsers = aMatrixUserList().toImmutableList()),
|
aDefaultInvitePeopleState(canInvite = true, selectedUsers = aMatrixUserList().toImmutableList()),
|
||||||
aRoomInviteMembersState(isSearchActive = true, searchQuery = "some query"),
|
aDefaultInvitePeopleState(isSearchActive = true, searchQuery = "some query"),
|
||||||
aRoomInviteMembersState(isSearchActive = true, searchQuery = "some query", selectedUsers = aMatrixUserList().toImmutableList()),
|
aDefaultInvitePeopleState(isSearchActive = true, searchQuery = "some query", selectedUsers = aMatrixUserList().toImmutableList()),
|
||||||
aRoomInviteMembersState(isSearchActive = true, searchQuery = "some query", searchResults = SearchBarResultState.NoResultsFound()),
|
aDefaultInvitePeopleState(isSearchActive = true, searchQuery = "some query", searchResults = SearchBarResultState.NoResultsFound()),
|
||||||
aRoomInviteMembersState(
|
aDefaultInvitePeopleState(
|
||||||
isSearchActive = true,
|
isSearchActive = true,
|
||||||
canInvite = true,
|
canInvite = true,
|
||||||
searchQuery = "some query",
|
searchQuery = "some query",
|
||||||
|
|
@ -33,15 +34,15 @@ internal class RoomInviteMembersStateProvider : PreviewParameterProvider<RoomInv
|
||||||
),
|
),
|
||||||
searchResults = SearchBarResultState.Results(
|
searchResults = SearchBarResultState.Results(
|
||||||
persistentListOf(
|
persistentListOf(
|
||||||
InvitableUser(aMatrixUser("@alice:server.org")),
|
anInvitableUser(aMatrixUser("@alice:server.org")),
|
||||||
InvitableUser(aMatrixUser("@bob:server.org", "Bob")),
|
anInvitableUser(aMatrixUser("@bob:server.org", "Bob")),
|
||||||
InvitableUser(aMatrixUser("@carol:server.org", "Carol"), isSelected = true),
|
anInvitableUser(aMatrixUser("@carol:server.org", "Carol"), isSelected = true),
|
||||||
InvitableUser(aMatrixUser("@eve:server.org", "Eve"), isSelected = true, isAlreadyJoined = true),
|
anInvitableUser(aMatrixUser("@eve:server.org", "Eve"), isSelected = true, isAlreadyJoined = true),
|
||||||
InvitableUser(aMatrixUser("@justin:server.org", "Justin"), isSelected = true, isAlreadyInvited = true),
|
anInvitableUser(aMatrixUser("@justin:server.org", "Justin"), isSelected = true, isAlreadyInvited = true),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
aRoomInviteMembersState(
|
aDefaultInvitePeopleState(
|
||||||
isSearchActive = true,
|
isSearchActive = true,
|
||||||
canInvite = true,
|
canInvite = true,
|
||||||
searchQuery = "@alice:server.org",
|
searchQuery = "@alice:server.org",
|
||||||
|
|
@ -50,34 +51,51 @@ internal class RoomInviteMembersStateProvider : PreviewParameterProvider<RoomInv
|
||||||
),
|
),
|
||||||
searchResults = SearchBarResultState.Results(
|
searchResults = SearchBarResultState.Results(
|
||||||
persistentListOf(
|
persistentListOf(
|
||||||
InvitableUser(aMatrixUser("@alice:server.org"), isUnresolved = true),
|
anInvitableUser(aMatrixUser("@alice:server.org"), isUnresolved = true),
|
||||||
InvitableUser(aMatrixUser("@bob:server.org", "Bob")),
|
anInvitableUser(aMatrixUser("@bob:server.org", "Bob")),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
aRoomInviteMembersState(
|
aDefaultInvitePeopleState(
|
||||||
isSearchActive = true,
|
isSearchActive = true,
|
||||||
canInvite = true,
|
canInvite = true,
|
||||||
searchQuery = "@alice:server.org",
|
searchQuery = "@alice:server.org",
|
||||||
searchResults = SearchBarResultState.Results(
|
searchResults = SearchBarResultState.Results(
|
||||||
persistentListOf(
|
persistentListOf(
|
||||||
InvitableUser(aMatrixUser("@alice:server.org"), isUnresolved = true),
|
anInvitableUser(aMatrixUser("@alice:server.org"), isUnresolved = true),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
showSearchLoader = true,
|
showSearchLoader = true,
|
||||||
),
|
),
|
||||||
|
aDefaultInvitePeopleState(room = AsyncData.Failure(Exception("Room not found"))),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun aRoomInviteMembersState(
|
private fun anInvitableUser(
|
||||||
|
matrixUser: MatrixUser,
|
||||||
|
isSelected: Boolean = false,
|
||||||
|
isAlreadyJoined: Boolean = false,
|
||||||
|
isAlreadyInvited: Boolean = false,
|
||||||
|
isUnresolved: Boolean = false,
|
||||||
|
) = InvitableUser(
|
||||||
|
matrixUser = matrixUser,
|
||||||
|
isSelected = isSelected,
|
||||||
|
isAlreadyJoined = isAlreadyJoined,
|
||||||
|
isAlreadyInvited = isAlreadyInvited,
|
||||||
|
isUnresolved = isUnresolved,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun aDefaultInvitePeopleState(
|
||||||
|
room: AsyncData<Unit> = AsyncData.Success(Unit),
|
||||||
canInvite: Boolean = false,
|
canInvite: Boolean = false,
|
||||||
searchQuery: String = "",
|
searchQuery: String = "",
|
||||||
searchResults: SearchBarResultState<ImmutableList<InvitableUser>> = SearchBarResultState.Initial(),
|
searchResults: SearchBarResultState<ImmutableList<InvitableUser>> = SearchBarResultState.Initial(),
|
||||||
selectedUsers: ImmutableList<MatrixUser> = persistentListOf(),
|
selectedUsers: ImmutableList<MatrixUser> = persistentListOf(),
|
||||||
isSearchActive: Boolean = false,
|
isSearchActive: Boolean = false,
|
||||||
showSearchLoader: Boolean = false,
|
showSearchLoader: Boolean = false,
|
||||||
): RoomInviteMembersState {
|
): DefaultInvitePeopleState {
|
||||||
return RoomInviteMembersState(
|
return DefaultInvitePeopleState(
|
||||||
|
room = room,
|
||||||
canInvite = canInvite,
|
canInvite = canInvite,
|
||||||
searchQuery = searchQuery,
|
searchQuery = searchQuery,
|
||||||
searchResults = searchResults,
|
searchResults = searchResults,
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.invitepeople.impl
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
|
||||||
|
data class InvitableUser(
|
||||||
|
val matrixUser: MatrixUser,
|
||||||
|
val isSelected: Boolean,
|
||||||
|
val isAlreadyJoined: Boolean,
|
||||||
|
val isAlreadyInvited: Boolean,
|
||||||
|
val isUnresolved: Boolean,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.invitepeople.impl
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.designsystem.components.async.AsyncFailure
|
||||||
|
import io.element.android.libraries.designsystem.components.async.AsyncLoading
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.SearchBar
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
import io.element.android.libraries.matrix.ui.components.CheckableUserRow
|
||||||
|
import io.element.android.libraries.matrix.ui.components.CheckableUserRowData
|
||||||
|
import io.element.android.libraries.matrix.ui.components.SelectedUsersRowList
|
||||||
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
|
import io.element.android.libraries.matrix.ui.model.getBestName
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InvitePeopleView(
|
||||||
|
state: DefaultInvitePeopleState,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
when (state.room) {
|
||||||
|
is AsyncData.Failure -> InvitePeopleViewError(state.room.error, modifier)
|
||||||
|
AsyncData.Uninitialized,
|
||||||
|
is AsyncData.Loading,
|
||||||
|
is AsyncData.Success -> InvitePeopleContentView(state, modifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun InvitePeopleViewError(
|
||||||
|
error: Throwable,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
AsyncFailure(
|
||||||
|
throwable = error,
|
||||||
|
onRetry = null,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun InvitePeopleContentView(
|
||||||
|
state: DefaultInvitePeopleState,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier.fillMaxSize(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
) {
|
||||||
|
InvitePeopleSearchBar(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
query = state.searchQuery,
|
||||||
|
showLoader = state.showSearchLoader,
|
||||||
|
selectedUsers = state.selectedUsers,
|
||||||
|
state = state.searchResults,
|
||||||
|
active = state.isSearchActive,
|
||||||
|
onActiveChange = {
|
||||||
|
state.eventSink(
|
||||||
|
DefaultInvitePeopleEvents.OnSearchActiveChanged(
|
||||||
|
it
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onTextChange = { state.eventSink(DefaultInvitePeopleEvents.UpdateSearchQuery(it)) },
|
||||||
|
onToggleUser = { state.eventSink(DefaultInvitePeopleEvents.ToggleUser(it)) },
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!state.isSearchActive) {
|
||||||
|
SelectedUsersRowList(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
selectedUsers = state.selectedUsers,
|
||||||
|
autoScroll = true,
|
||||||
|
onUserRemove = { state.eventSink(DefaultInvitePeopleEvents.ToggleUser(it)) },
|
||||||
|
contentPadding = PaddingValues(16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun InvitePeopleSearchBar(
|
||||||
|
query: String,
|
||||||
|
state: SearchBarResultState<ImmutableList<InvitableUser>>,
|
||||||
|
showLoader: Boolean,
|
||||||
|
selectedUsers: ImmutableList<MatrixUser>,
|
||||||
|
active: Boolean,
|
||||||
|
onActiveChange: (Boolean) -> Unit,
|
||||||
|
onTextChange: (String) -> Unit,
|
||||||
|
onToggleUser: (MatrixUser) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
placeHolderTitle: String = stringResource(CommonStrings.common_search_for_someone),
|
||||||
|
) {
|
||||||
|
SearchBar(
|
||||||
|
query = query,
|
||||||
|
onQueryChange = onTextChange,
|
||||||
|
active = active,
|
||||||
|
onActiveChange = onActiveChange,
|
||||||
|
modifier = modifier,
|
||||||
|
placeHolderTitle = placeHolderTitle,
|
||||||
|
contentPrefix = {
|
||||||
|
if (selectedUsers.isNotEmpty()) {
|
||||||
|
SelectedUsersRowList(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
selectedUsers = selectedUsers,
|
||||||
|
autoScroll = true,
|
||||||
|
onUserRemove = onToggleUser,
|
||||||
|
contentPadding = PaddingValues(16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showBackButton = false,
|
||||||
|
resultState = state,
|
||||||
|
contentSuffix = {
|
||||||
|
if (showLoader) {
|
||||||
|
AsyncLoading()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resultHandler = { results ->
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = CommonStrings.common_search_results),
|
||||||
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 16.dp, top = 12.dp, end = 16.dp, bottom = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
LazyColumn {
|
||||||
|
itemsIndexed(results) { index, invitableUser ->
|
||||||
|
val invitedOrJoined = invitableUser.isAlreadyInvited || invitableUser.isAlreadyJoined
|
||||||
|
val isUnresolved = invitableUser.isUnresolved && !invitedOrJoined
|
||||||
|
val enabled = isUnresolved || !invitedOrJoined
|
||||||
|
val data = if (isUnresolved) {
|
||||||
|
CheckableUserRowData.Unresolved(
|
||||||
|
avatarData = invitableUser.matrixUser.getAvatarData(AvatarSize.UserListItem),
|
||||||
|
id = invitableUser.matrixUser.userId.value,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
CheckableUserRowData.Resolved(
|
||||||
|
avatarData = invitableUser.matrixUser.getAvatarData(AvatarSize.UserListItem),
|
||||||
|
name = invitableUser.matrixUser.getBestName(),
|
||||||
|
subtext = when {
|
||||||
|
// If they're already invited or joined we show that information
|
||||||
|
invitableUser.isAlreadyJoined -> stringResource(R.string.screen_invite_users_already_a_member)
|
||||||
|
invitableUser.isAlreadyInvited -> stringResource(R.string.screen_invite_users_already_invited)
|
||||||
|
// Otherwise show the ID, unless that's already used for their name
|
||||||
|
invitableUser.matrixUser.displayName.isNullOrEmpty()
|
||||||
|
.not() -> invitableUser.matrixUser.userId.value
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
CheckableUserRow(
|
||||||
|
checked = invitableUser.isSelected || invitedOrJoined,
|
||||||
|
enabled = enabled,
|
||||||
|
data = data,
|
||||||
|
onCheckedChange = { onToggleUser(invitableUser.matrixUser) },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
if (index < results.lastIndex) {
|
||||||
|
HorizontalDivider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun InvitePeopleViewPreview(@PreviewParameter(DefaultInvitePeopleStateProvider::class) state: DefaultInvitePeopleState) =
|
||||||
|
ElementPreview {
|
||||||
|
InvitePeopleView(state = state)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"Ужо ўдзельнік"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"Ужо запрасілі"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"Вече е член"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"Вече е бил поканен"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"Již členem"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"Již pozván(a)"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"Eisoes yn aelod"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"Wedi gwahodd yn barod"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"Allerede medlem"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"Allerede inviteret"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"Bereits Mitglied"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"Bereits eingeladen"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"Ήδη μέλος"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"Ήδη προσκεκλημένος"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"Ya eres miembro"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"Ya estás invitado"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"Sa juba oled jututoa liige"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"Sa juba oled kutse saanud"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"Kidea da dagoeneko"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"Lehendik ere gonbidatuta"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"از پیش عضو است"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"از پیش دعوت شده"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"On jo jäsen"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"On jo kutsuttu"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"Déjà membre"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"Déjà invité(e)"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"Már tag"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"Már meghívták"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"Sudah menjadi anggota"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"Sudah diundang"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"Già membro"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"Già invitato"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"უკვე წევრია"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"უკვე მოწვეულია"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"Jau narys"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"Jau pakviestas"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"Allerede medlem"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"Allerede invitert"</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_invite_users_already_a_member">"Reeds lid"</string>
|
||||||
|
<string name="screen_invite_users_already_invited">"Reeds uitgenodigd"</string>
|
||||||
|
</resources>
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue