From 74f6a83219256f8da5be5968c92ffa092b5eab45 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 8 Aug 2025 19:06:19 +0200 Subject: [PATCH] refactor (start chat) : use invite people module in room details screen --- .../createroom/impl/CreateRoomFlowNode.kt | 3 +- .../impl/addpeople/AddPeopleNode.kt | 11 +- .../impl/addpeople/AddPeopleView.kt | 64 +++++++ .../invitepeople/api/InvitePeopleEvents.kt | 1 + .../invitepeople/api/InvitePeopleState.kt | 2 + .../api/InvitePeopleStateProvider.kt | 25 +++ features/invitepeople/impl/build.gradle.kts | 1 + .../impl/DefaultInvitePeoplePresenter.kt | 25 +++ .../impl/DefaultInvitePeopleRenderer.kt | 2 - .../impl/DefaultInvitePeopleState.kt | 4 +- .../invitepeople/impl/InvitePeopleView.kt | 111 ++++--------- features/roomdetails/impl/build.gradle.kts | 1 + .../impl/invite/RoomInviteMembersEvents.kt | 16 -- .../impl/invite/RoomInviteMembersNode.kt | 38 +---- .../impl/invite/RoomInviteMembersPresenter.kt | 154 ----------------- .../impl/invite/RoomInviteMembersState.kt | 30 ---- .../invite/RoomInviteMembersStateProvider.kt | 89 ---------- .../impl/invite/RoomInviteMembersView.kt | 157 +++--------------- .../apperror/api/AppErrorStateService.kt | 4 + .../impl/DefaultAppErrorStateService.kt | 12 +- 20 files changed, 207 insertions(+), 543 deletions(-) create mode 100644 features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt create mode 100644 features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleStateProvider.kt delete mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersEvents.kt delete mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenter.kt delete mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersState.kt delete mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersStateProvider.kt diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt index 7d0d556e18..c4d085b95c 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt @@ -15,6 +15,7 @@ import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.push +import com.bumble.appyx.navmodel.backstack.operation.replace import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode @@ -48,7 +49,7 @@ class CreateRoomFlowNode @AssistedInject constructor( NavTarget.ConfigureRoom -> { val callback = object : ConfigureRoomNode.Callback { override fun onCreateRoomSuccess(roomId: RoomId) { - backstack.push(NavTarget.AddPeople(roomId)) + backstack.replace(NavTarget.AddPeople(roomId)) } } createNode(buildContext, plugins = listOf(callback)) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleNode.kt index 914c316e1c..cba1516168 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleNode.kt @@ -12,7 +12,6 @@ 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.squareup.anvil.annotations.ContributesBinding import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode @@ -27,13 +26,13 @@ import io.element.android.libraries.matrix.api.room.JoinedRoom class AddPeopleNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, - private val invitePeoplePresenterFactory: InvitePeoplePresenter.Factory, + invitePeoplePresenterFactory: InvitePeoplePresenter.Factory, private val invitePeopleRenderer: InvitePeopleRenderer, ) : Node(buildContext, plugins = plugins) { data class Inputs( val joinedRoom: JoinedRoom - ): NodeInputs + ) : NodeInputs private val joinedRoom = inputs().joinedRoom private val invitePeoplePresenter = invitePeoplePresenterFactory.create(joinedRoom) @@ -41,6 +40,10 @@ class AddPeopleNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { val state = invitePeoplePresenter.present() - invitePeopleRenderer.Render(state, Modifier) + AddPeopleView( + state = state, + invitePeopleView = { invitePeopleRenderer.Render(state, Modifier) }, + onFinish = {} + ) } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt new file mode 100644 index 0000000000..ddcdc7e9b1 --- /dev/null +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt @@ -0,0 +1,64 @@ +/* + * 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.createroom.impl.addpeople + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import io.element.android.features.invitepeople.api.InvitePeopleEvents +import io.element.android.features.invitepeople.api.InvitePeopleState +import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage +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.TopAppBar +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun AddPeopleView( + state: InvitePeopleState, + invitePeopleView: @Composable () -> Unit, + onFinish: () -> Unit, + modifier: Modifier = Modifier, +) { + HeaderFooterPage( + modifier = modifier, + contentPadding = PaddingValues(0.dp), + topBar = { + AddPeopleTopBar(onSkipClick = onFinish) + }, + footer = { + Button( + text = "Finish", + onClick = { state.eventSink(InvitePeopleEvents.SendInvites) }, + enabled = state.canInvite, + modifier = Modifier.padding(bottom = 16.dp) + ) + }, + content = invitePeopleView + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun AddPeopleTopBar( + onSkipClick: () -> Unit, +) { + TopAppBar( + titleStr = "Invite people", + actions = { + TextButton( + text = stringResource(CommonStrings.action_skip), + onClick = onSkipClick, + ) + } + ) +} diff --git a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleEvents.kt b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleEvents.kt index 60f9512651..0ab097462b 100644 --- a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleEvents.kt +++ b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleEvents.kt @@ -9,4 +9,5 @@ package io.element.android.features.invitepeople.api interface InvitePeopleEvents { data object SendInvites : InvitePeopleEvents + data object CloseSearch : InvitePeopleEvents } diff --git a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleState.kt b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleState.kt index fb76dc0c1a..db8b9ffbd2 100644 --- a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleState.kt +++ b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleState.kt @@ -8,5 +8,7 @@ package io.element.android.features.invitepeople.api interface InvitePeopleState { + val canInvite: Boolean + val isSearchActive: Boolean val eventSink: (InvitePeopleEvents) -> Unit } diff --git a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleStateProvider.kt b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleStateProvider.kt new file mode 100644 index 0000000000..70967580ac --- /dev/null +++ b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleStateProvider.kt @@ -0,0 +1,25 @@ +/* + * 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 { + override val values: Sequence + get() = sequenceOf( + PreviewInvitePeopleState(), + PreviewInvitePeopleState(canInvite = true), + PreviewInvitePeopleState(isSearchActive = true) + ) +} + +private data class PreviewInvitePeopleState( + override val canInvite: Boolean = false, + override val isSearchActive: Boolean = false, + override val eventSink: (InvitePeopleEvents) -> Unit = {} +) : InvitePeopleState diff --git a/features/invitepeople/impl/build.gradle.kts b/features/invitepeople/impl/build.gradle.kts index 80f91f2eff..82a716897e 100644 --- a/features/invitepeople/impl/build.gradle.kts +++ b/features/invitepeople/impl/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { 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) diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt index 7eb7af377a..7f3d0893ac 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt @@ -27,23 +27,30 @@ import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.di.annotations.AppCoroutineScope 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.RoomMembershipState import io.element.android.libraries.matrix.api.room.filterMembers 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.services.apperror.api.AppErrorStateService import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class DefaultInvitePeoplePresenter @AssistedInject constructor( @Assisted private val room: JoinedRoom, private val userRepository: UserRepository, private val coroutineDispatchers: CoroutineDispatchers, + @AppCoroutineScope private val coroutineScope: CoroutineScope, + private val appErrorStateService: AppErrorStateService, ) : InvitePeoplePresenter { @AssistedFactory @@ -97,12 +104,30 @@ class DefaultInvitePeoplePresenter @AssistedInject constructor( searchResults.toggleUser(it.user) } is InvitePeopleEvents.SendInvites -> { + coroutineScope.sendInvites(selectedUsers.value) + } + is InvitePeopleEvents.CloseSearch -> { + searchActive = false + searchQuery = "" } } } ) } + private fun CoroutineScope.sendInvites(selectedUsers: List) = 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") private fun MutableState>.toggleUser(user: MatrixUser) { value = if (value.contains(user)) { diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleRenderer.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleRenderer.kt index 371b2fa84b..8207e75fd5 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleRenderer.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleRenderer.kt @@ -22,8 +22,6 @@ class DefaultInvitePeopleRenderer @Inject constructor() : InvitePeopleRenderer { if (state is DefaultInvitePeopleState) { InvitePeopleView( state = state, - onBackClick = {}, - onSubmitClick = {}, modifier = modifier ) } else { diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleState.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleState.kt index d8104b748c..41f49e74c0 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleState.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleState.kt @@ -14,11 +14,11 @@ import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.collections.immutable.ImmutableList data class DefaultInvitePeopleState( - val canInvite: Boolean, + override val canInvite: Boolean, val searchQuery: String, val showSearchLoader: Boolean, val searchResults: SearchBarResultState>, val selectedUsers: ImmutableList, - val isSearchActive: Boolean, + override val isSearchActive: Boolean, override val eventSink: (InvitePeopleEvents) -> Unit ): InvitePeopleState diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt index b87f92526a..6c9a6845a2 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt @@ -7,10 +7,10 @@ package io.element.android.features.invitepeople.impl +import androidx.activity.OnBackPressedDispatcher import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -21,6 +21,7 @@ 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 com.bumble.appyx.core.plugin.BackPressHandler import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.components.async.AsyncLoading import io.element.android.libraries.designsystem.components.avatar.AvatarSize @@ -28,7 +29,6 @@ import io.element.android.libraries.designsystem.components.button.BackButton 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.Scaffold 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 @@ -46,87 +46,46 @@ import kotlinx.collections.immutable.ImmutableList @Composable fun InvitePeopleView( state: DefaultInvitePeopleState, - onBackClick: () -> Unit, - onSubmitClick: (List) -> Unit, modifier: Modifier = Modifier, ) { - Scaffold( - modifier = modifier, - topBar = { - RoomInviteMembersTopBar( - onBackClick = { - if (state.isSearchActive) { - state.eventSink(DefaultInvitePeopleEvents.OnSearchActiveChanged(false)) - } else { - onBackClick() - } - }, - onSubmitClick = { onSubmitClick(state.selectedUsers) }, - canSend = state.canInvite, - ) - } - ) { padding -> - Column( - modifier = Modifier - .fillMaxWidth() - .padding(padding) - .consumeWindowInsets(padding), - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - RoomInviteMembersSearchBar( - 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), + Column( + modifier = modifier.fillMaxWidth(), + 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 RoomInviteMembersTopBar( - canSend: Boolean, - onBackClick: () -> Unit, - onSubmitClick: () -> Unit, -) { - TopAppBar( - titleStr = "Invite people", - navigationIcon = { BackButton(onClick = onBackClick) }, - actions = { - TextButton( - text = stringResource(CommonStrings.action_invite), - onClick = onSubmitClick, - enabled = canSend, - ) - } - ) -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun RoomInviteMembersSearchBar( +private fun InvitePeopleSearchBar( query: String, state: SearchBarResultState>, showLoader: Boolean, @@ -219,9 +178,5 @@ private fun RoomInviteMembersSearchBar( @Composable internal fun RoomInviteMembersViewPreview(@PreviewParameter(DefaultInvitePeopleStateProvider::class) state: DefaultInvitePeopleState) = ElementPreview { - InvitePeopleView( - state = state, - onBackClick = {}, - onSubmitClick = {}, - ) + InvitePeopleView(state = state) } diff --git a/features/roomdetails/impl/build.gradle.kts b/features/roomdetails/impl/build.gradle.kts index 91bb79c8b7..302ae7d8c6 100644 --- a/features/roomdetails/impl/build.gradle.kts +++ b/features/roomdetails/impl/build.gradle.kts @@ -56,6 +56,7 @@ dependencies { implementation(projects.features.reportroom.api) implementation(projects.features.roommembermoderation.api) implementation(projects.features.changeroommemberroles.api) + implementation(projects.features.invitepeople.api) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersEvents.kt deleted file mode 100644 index a06ae82c49..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersEvents.kt +++ /dev/null @@ -1,16 +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.roomdetails.impl.invite - -import io.element.android.libraries.matrix.api.user.MatrixUser - -sealed interface RoomInviteMembersEvents { - data class ToggleUser(val user: MatrixUser) : RoomInviteMembersEvents - data class UpdateSearchQuery(val query: String) : RoomInviteMembersEvents - data class OnSearchActiveChanged(val active: Boolean) : RoomInviteMembersEvents -} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersNode.kt index ebfa3dcab8..270290f05d 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersNode.kt @@ -9,7 +9,6 @@ package io.element.android.features.roomdetails.impl.invite import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node @@ -18,27 +17,22 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.invitepeople.api.InvitePeoplePresenter +import io.element.android.features.invitepeople.api.InvitePeopleRenderer import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.room.JoinedRoom -import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.analytics.api.AnalyticsService -import io.element.android.services.apperror.api.AppErrorStateService -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.launch @ContributesNode(RoomScope::class) class RoomInviteMembersNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, - coroutineDispatchers: CoroutineDispatchers, private val room: JoinedRoom, - private val presenter: RoomInviteMembersPresenter, - private val appErrorStateService: AppErrorStateService, private val analyticsService: AnalyticsService, + private val invitePeopleRenderer: InvitePeopleRenderer, + private val invitePeoplePresenterFactory: InvitePeoplePresenter.Factory, ) : Node(buildContext, plugins = plugins) { - private val coroutineScope = CoroutineScope(SupervisorJob() + coroutineDispatchers.io) init { lifecycle.subscribe( @@ -48,31 +42,17 @@ class RoomInviteMembersNode @AssistedInject constructor( ) } + private val invitePeoplePresenter = invitePeoplePresenterFactory.create(room) + @Composable override fun View(modifier: Modifier) { - val state = presenter.present() - val context = LocalContext.current.applicationContext - + val state = invitePeoplePresenter.present() RoomInviteMembersView( state = state, modifier = modifier, + invitePeopleView = { invitePeopleRenderer.Render(state, Modifier) }, onBackClick = { navigateUp() }, - onSubmitClick = { users -> - navigateUp() - - coroutineScope.launch { - val anyInviteFailed = users - .map { room.inviteUserById(it.userId) } - .any { it.isFailure } - - if (anyInviteFailed) { - appErrorStateService.showError( - title = context.getString(CommonStrings.common_unable_to_invite_title), - body = context.getString(CommonStrings.common_unable_to_invite_message), - ) - } - } - } + onSubmitClick = { navigateUp() } ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenter.kt deleted file mode 100644 index ed5d13f70b..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenter.kt +++ /dev/null @@ -1,154 +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.roomdetails.impl.invite - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import io.element.android.features.roomdetails.impl.members.RoomMemberListDataSource -import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.architecture.runCatchingUpdatingState -import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.designsystem.theme.components.SearchBarResultState -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.user.MatrixUser -import io.element.android.libraries.usersearch.api.UserRepository -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.withContext -import javax.inject.Inject - -class RoomInviteMembersPresenter @Inject constructor( - private val userRepository: UserRepository, - private val roomMemberListDataSource: RoomMemberListDataSource, - private val coroutineDispatchers: CoroutineDispatchers, -) : Presenter { - @Composable - override fun present(): RoomInviteMembersState { - val roomMembers = remember { mutableStateOf>>(AsyncData.Loading()) } - val selectedUsers = remember { mutableStateOf>(persistentListOf()) } - val searchResults = remember { mutableStateOf>>(SearchBarResultState.Initial()) } - var searchQuery by rememberSaveable { mutableStateOf("") } - var searchActive by rememberSaveable { mutableStateOf(false) } - val showSearchLoader = rememberSaveable { mutableStateOf(false) } - - LaunchedEffect(Unit) { - fetchMembers(roomMembers) - } - LaunchedEffect(searchQuery, roomMembers) { - performSearch( - searchResults = searchResults, - roomMembers = roomMembers, - selectedUsers = selectedUsers, - showSearchLoader = showSearchLoader, - searchQuery = searchQuery - ) - } - - return RoomInviteMembersState( - canInvite = selectedUsers.value.isNotEmpty(), - selectedUsers = selectedUsers.value, - searchQuery = searchQuery, - isSearchActive = searchActive, - searchResults = searchResults.value, - showSearchLoader = showSearchLoader.value, - eventSink = { - 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) - } - } - } - ) - } - - @JvmName("toggleUserInSelectedUsers") - private fun MutableState>.toggleUser(user: MatrixUser) { - value = if (value.contains(user)) { - value.filterNot { it.userId == user.userId } - } else { - value + user - }.toImmutableList() - } - - @JvmName("toggleUserInSearchResults") - private fun MutableState>>.toggleUser(user: MatrixUser) { - val existingResults = value - if (existingResults is SearchBarResultState.Results) { - value = SearchBarResultState.Results( - existingResults.results.map { iu -> - if (iu.matrixUser == user) { - iu.copy(isSelected = !iu.isSelected) - } else { - iu - } - }.toImmutableList() - ) - } - } - - private suspend fun performSearch( - searchResults: MutableState>>, - roomMembers: MutableState>>, - selectedUsers: MutableState>, - showSearchLoader: MutableState, - searchQuery: String, - ) = withContext(coroutineDispatchers.io) { - searchResults.value = SearchBarResultState.Initial() - showSearchLoader.value = false - val joinedMembers = roomMembers.value.dataOrNull().orEmpty() - - userRepository.search(searchQuery).onEach { state -> - showSearchLoader.value = state.isSearching - searchResults.value = when { - state.results.isEmpty() && state.isSearching -> SearchBarResultState.Initial() - state.results.isEmpty() && !state.isSearching -> SearchBarResultState.NoResultsFound() - else -> SearchBarResultState.Results(state.results.map { result -> - val existingMembership = joinedMembers.firstOrNull { j -> j.userId == result.matrixUser.userId }?.membership - val isJoined = existingMembership == RoomMembershipState.JOIN - val isInvited = existingMembership == RoomMembershipState.INVITE - InvitableUser( - matrixUser = result.matrixUser, - isSelected = selectedUsers.value.contains(result.matrixUser) || isJoined || isInvited, - isAlreadyJoined = isJoined, - isAlreadyInvited = isInvited, - isUnresolved = result.isUnresolved, - ) - }.toImmutableList()) - } - }.launchIn(this) - } - - private suspend fun fetchMembers(roomMembers: MutableState>>) { - suspend { - withContext(coroutineDispatchers.io) { - roomMemberListDataSource.search("").toImmutableList() - } - }.runCatchingUpdatingState(roomMembers) - } -} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersState.kt deleted file mode 100644 index c7927247ef..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersState.kt +++ /dev/null @@ -1,30 +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.roomdetails.impl.invite - -import io.element.android.libraries.designsystem.theme.components.SearchBarResultState -import io.element.android.libraries.matrix.api.user.MatrixUser -import kotlinx.collections.immutable.ImmutableList - -data class RoomInviteMembersState( - val canInvite: Boolean, - val searchQuery: String, - val showSearchLoader: Boolean, - val searchResults: SearchBarResultState>, - val selectedUsers: ImmutableList, - val isSearchActive: Boolean, - val eventSink: (RoomInviteMembersEvents) -> Unit, -) - -data class InvitableUser( - val matrixUser: MatrixUser, - val isSelected: Boolean = false, - val isAlreadyJoined: Boolean = false, - val isAlreadyInvited: Boolean = false, - val isUnresolved: Boolean = false, -) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersStateProvider.kt deleted file mode 100644 index 7675880f37..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersStateProvider.kt +++ /dev/null @@ -1,89 +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.roomdetails.impl.invite - -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.designsystem.theme.components.SearchBarResultState -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.aMatrixUserList -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList - -internal class RoomInviteMembersStateProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf( - aRoomInviteMembersState(), - aRoomInviteMembersState(canInvite = true, selectedUsers = aMatrixUserList().toImmutableList()), - aRoomInviteMembersState(isSearchActive = true, searchQuery = "some query"), - aRoomInviteMembersState(isSearchActive = true, searchQuery = "some query", selectedUsers = aMatrixUserList().toImmutableList()), - aRoomInviteMembersState(isSearchActive = true, searchQuery = "some query", searchResults = SearchBarResultState.NoResultsFound()), - aRoomInviteMembersState( - isSearchActive = true, - canInvite = true, - searchQuery = "some query", - selectedUsers = persistentListOf( - aMatrixUser("@carol:server.org", "Carol") - ), - searchResults = SearchBarResultState.Results( - persistentListOf( - InvitableUser(aMatrixUser("@alice:server.org")), - InvitableUser(aMatrixUser("@bob:server.org", "Bob")), - InvitableUser(aMatrixUser("@carol:server.org", "Carol"), isSelected = true), - InvitableUser(aMatrixUser("@eve:server.org", "Eve"), isSelected = true, isAlreadyJoined = true), - InvitableUser(aMatrixUser("@justin:server.org", "Justin"), isSelected = true, isAlreadyInvited = true), - ) - ) - ), - aRoomInviteMembersState( - isSearchActive = true, - canInvite = true, - searchQuery = "@alice:server.org", - selectedUsers = persistentListOf( - aMatrixUser("@carol:server.org", "Carol") - ), - searchResults = SearchBarResultState.Results( - persistentListOf( - InvitableUser(aMatrixUser("@alice:server.org"), isUnresolved = true), - InvitableUser(aMatrixUser("@bob:server.org", "Bob")), - ) - ) - ), - aRoomInviteMembersState( - isSearchActive = true, - canInvite = true, - searchQuery = "@alice:server.org", - searchResults = SearchBarResultState.Results( - persistentListOf( - InvitableUser(aMatrixUser("@alice:server.org"), isUnresolved = true), - ) - ), - showSearchLoader = true, - ), - ) -} - -private fun aRoomInviteMembersState( - canInvite: Boolean = false, - searchQuery: String = "", - searchResults: SearchBarResultState> = SearchBarResultState.Initial(), - selectedUsers: ImmutableList = persistentListOf(), - isSearchActive: Boolean = false, - showSearchLoader: Boolean = false, -): RoomInviteMembersState { - return RoomInviteMembersState( - canInvite = canInvite, - searchQuery = searchQuery, - searchResults = searchResults, - selectedUsers = selectedUsers, - isSearchActive = isSearchActive, - showSearchLoader = showSearchLoader, - eventSink = {}, - ) -} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt index caae6ff16d..608f9047ae 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt @@ -8,47 +8,35 @@ package io.element.android.features.roomdetails.impl.invite 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.consumeWindowInsets 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.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.features.invitepeople.api.InvitePeopleEvents +import io.element.android.features.invitepeople.api.InvitePeopleState +import io.element.android.features.invitepeople.api.InvitePeopleStateProvider import io.element.android.features.roomdetails.impl.R -import io.element.android.libraries.designsystem.components.async.AsyncLoading -import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.button.BackButton 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.Scaffold -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.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TopAppBar -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 RoomInviteMembersView( - state: RoomInviteMembersState, + state: InvitePeopleState, + invitePeopleView: @Composable () -> Unit, onBackClick: () -> Unit, - onSubmitClick: (List) -> Unit, + onSubmitClick: () -> Unit, modifier: Modifier = Modifier, ) { Scaffold( @@ -57,45 +45,28 @@ fun RoomInviteMembersView( RoomInviteMembersTopBar( onBackClick = { if (state.isSearchActive) { - state.eventSink(RoomInviteMembersEvents.OnSearchActiveChanged(false)) + state.eventSink(InvitePeopleEvents.CloseSearch) } else { onBackClick() } }, - onSubmitClick = { onSubmitClick(state.selectedUsers) }, + onSubmitClick = { + state.eventSink(InvitePeopleEvents.SendInvites) + onSubmitClick() + }, canSend = state.canInvite, ) } ) { padding -> - Column( + Box( modifier = Modifier - .fillMaxWidth() - .padding(padding) - .consumeWindowInsets(padding), - verticalArrangement = Arrangement.spacedBy(16.dp), + .fillMaxWidth() + .padding(padding) + .consumeWindowInsets(padding), ) { - RoomInviteMembersSearchBar( - modifier = Modifier.fillMaxWidth(), - query = state.searchQuery, - showLoader = state.showSearchLoader, - selectedUsers = state.selectedUsers, - state = state.searchResults, - active = state.isSearchActive, - onActiveChange = { state.eventSink(RoomInviteMembersEvents.OnSearchActiveChanged(it)) }, - onTextChange = { state.eventSink(RoomInviteMembersEvents.UpdateSearchQuery(it)) }, - onToggleUser = { state.eventSink(RoomInviteMembersEvents.ToggleUser(it)) }, - ) - - if (!state.isSearchActive) { - SelectedUsersRowList( - modifier = Modifier.fillMaxWidth(), - selectedUsers = state.selectedUsers, - autoScroll = true, - onUserRemove = { state.eventSink(RoomInviteMembersEvents.ToggleUser(it)) }, - contentPadding = PaddingValues(16.dp), - ) - } + invitePeopleView() } + } } @@ -119,100 +90,12 @@ private fun RoomInviteMembersTopBar( ) } -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun RoomInviteMembersSearchBar( - query: String, - state: SearchBarResultState>, - showLoader: Boolean, - selectedUsers: ImmutableList, - 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 notInvitedOrJoined = !(invitableUser.isAlreadyInvited || invitableUser.isAlreadyJoined) - val isUnresolved = invitableUser.isUnresolved && notInvitedOrJoined - val enabled = isUnresolved || notInvitedOrJoined - 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_room_details_already_a_member) - invitableUser.isAlreadyInvited -> stringResource(R.string.screen_room_details_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, - enabled = enabled, - data = data, - onCheckedChange = { onToggleUser(invitableUser.matrixUser) }, - modifier = Modifier.fillMaxWidth() - ) - - if (index < results.lastIndex) { - HorizontalDivider() - } - } - } - }, - ) -} - @PreviewsDayNight @Composable -internal fun RoomInviteMembersViewPreview(@PreviewParameter(RoomInviteMembersStateProvider::class) state: RoomInviteMembersState) = ElementPreview { +internal fun RoomInviteMembersViewPreview(@PreviewParameter(InvitePeopleStateProvider::class) state: InvitePeopleState) = ElementPreview { RoomInviteMembersView( state = state, + invitePeopleView = {}, onBackClick = {}, onSubmitClick = {}, ) diff --git a/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorStateService.kt b/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorStateService.kt index 45031fdf33..6921ba1958 100644 --- a/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorStateService.kt +++ b/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorStateService.kt @@ -7,10 +7,14 @@ package io.element.android.services.apperror.api +import androidx.annotation.StringRes import kotlinx.coroutines.flow.StateFlow interface AppErrorStateService { val appErrorStateFlow: StateFlow fun showError(title: String, body: String) + + fun showError(@StringRes titleRes: Int, @StringRes bodyRes: Int) + } diff --git a/services/apperror/impl/src/main/kotlin/io/element/android/services/apperror/impl/DefaultAppErrorStateService.kt b/services/apperror/impl/src/main/kotlin/io/element/android/services/apperror/impl/DefaultAppErrorStateService.kt index 7341028784..ca6e6d34cf 100644 --- a/services/apperror/impl/src/main/kotlin/io/element/android/services/apperror/impl/DefaultAppErrorStateService.kt +++ b/services/apperror/impl/src/main/kotlin/io/element/android/services/apperror/impl/DefaultAppErrorStateService.kt @@ -7,8 +7,10 @@ package io.element.android.services.apperror.impl +import android.content.Context import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.SingleIn import io.element.android.services.apperror.api.AppErrorState import io.element.android.services.apperror.api.AppErrorStateService @@ -18,7 +20,9 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) -class DefaultAppErrorStateService @Inject constructor() : AppErrorStateService { +class DefaultAppErrorStateService @Inject constructor( + @ApplicationContext private val context: Context, +) : AppErrorStateService { private val currentAppErrorState = MutableStateFlow(AppErrorState.NoError) override val appErrorStateFlow: StateFlow = currentAppErrorState @@ -31,4 +35,10 @@ class DefaultAppErrorStateService @Inject constructor() : AppErrorStateService { }, ) } + + override fun showError(titleRes: Int, bodyRes: Int) { + val title = context.getString(titleRes) + val body = context.getString(bodyRes) + showError(title, body) + } }