Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,35 +24,64 @@ import com.wire.android.feature.meetings.ui.util.PreviewMultipleThemes
import com.wire.android.ui.common.WireDialog
import com.wire.android.ui.common.WireDialogButtonProperties
import com.wire.android.ui.common.WireDialogButtonType
import com.wire.android.ui.common.button.WireButtonState
import com.wire.android.ui.theme.WireTheme
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.android.ui.common.R as commonR

@Composable
fun NewMeetingErrorDialog(
error: NewMeetingState.SubmitError,
type: NewMeetingType,
isSubmitting: Boolean,
onDismiss: () -> Unit,
onRetryUpdateConversationName: (conversationId: ConversationId) -> Unit
) {
// TODO: specific errors to be handled later, for now we just show single generic one
val (titleResId, descriptionResId) = when (type) {
NewMeetingType.MeetNow -> R.string.new_meeting_now_failure_title to R.string.new_meeting_now_failure_description
NewMeetingType.Schedule -> R.string.new_meeting_schedule_failure_title to R.string.new_meeting_schedule_failure_description
is NewMeetingType.Edit -> R.string.new_meeting_edit_failure_title to R.string.new_meeting_edit_failure_description
val (titleResId, descriptionResId) = when (error) {
is NewMeetingState.SubmitError.UpdateConversationNameFailure ->
R.string.new_meeting_edit_conversation_name_failure_title to R.string.new_meeting_edit_conversation_name_failure_description

is NewMeetingState.SubmitError.Other -> when (type) {
NewMeetingType.MeetNow -> R.string.new_meeting_now_failure_title to R.string.new_meeting_now_failure_description
NewMeetingType.Schedule -> R.string.new_meeting_schedule_failure_title to R.string.new_meeting_schedule_failure_description
is NewMeetingType.Edit -> R.string.new_meeting_edit_failure_title to R.string.new_meeting_edit_failure_description
}
}

WireDialog(
title = stringResource(titleResId),
text = stringResource(descriptionResId),
onDismiss = onDismiss,
buttonsHorizontalAlignment = false,
optionButton1Properties = WireDialogButtonProperties(
onClick = onDismiss,
text = stringResource(commonR.string.label_ok),
type = WireDialogButtonType.Primary,
),
optionButton1Properties = when (error) {
is NewMeetingState.SubmitError.Other -> WireDialogButtonProperties(
onClick = onDismiss,
text = stringResource(commonR.string.label_ok),
type = WireDialogButtonType.Primary,
)

is NewMeetingState.SubmitError.UpdateConversationNameFailure -> WireDialogButtonProperties(
onClick = {
onRetryUpdateConversationName(error.conversationId)
},
text = stringResource(R.string.new_meeting_edit_conversation_name_failure_action),
type = WireDialogButtonType.Primary,
state = if (isSubmitting) WireButtonState.Disabled else WireButtonState.Default,
loading = isSubmitting,
)
}
)
}

@PreviewMultipleThemes
@Composable
private fun PreviewCreateGroupErrorDialogLackingConnection() = WireTheme {
NewMeetingErrorDialog(type = NewMeetingType.MeetNow, onDismiss = {})
NewMeetingErrorDialog(
error = NewMeetingState.SubmitError.Other,
type = NewMeetingType.MeetNow,
isSubmitting = false,
onDismiss = {},
onRetryUpdateConversationName = {},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,13 @@ fun NewMeetingScreen(
onRepeatingIntervalChanged = newMeetingViewModel::updateRepeatingInterval,
)

if (newMeetingViewModel.state.submitError != null) {
newMeetingViewModel.state.submitError?.let { submitError ->
NewMeetingErrorDialog(
error = submitError,
type = newMeetingViewModel.type,
onDismiss = newMeetingViewModel::dismissCreationError
isSubmitting = newMeetingViewModel.state.isSubmitting,
onDismiss = newMeetingViewModel::dismissCreationError,
onRetryUpdateConversationName = newMeetingViewModel::retryUpdateConversationName,
)
}
if (newMeetingViewModel.state.initialLoading == NewMeetingState.InitialLoadingState.Error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,14 @@
import com.wire.android.ui.common.ActionsViewModel
import com.wire.android.ui.common.textfield.textAsFlow
import com.wire.android.util.CurrentTimeProvider
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.meeting.Meeting
import com.wire.kalium.logic.data.meeting.UpsertMeeting
import com.wire.kalium.logic.data.user.OtherUser
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.conversation.ObserveConversationMembersUseCase
import com.wire.kalium.logic.feature.conversation.RenameConversationUseCase
import com.wire.kalium.logic.feature.conversation.RenamingResult
import com.wire.kalium.logic.feature.meeting.CreateNewMeetingUseCase
import com.wire.kalium.logic.feature.meeting.GetNextMeetingOccurrenceUseCase
import com.wire.kalium.logic.feature.meeting.UpdateMeetingUseCase
Expand Down Expand Up @@ -79,6 +82,7 @@
fun submitCreation() {}
fun submitUpdate() {}
fun dismissCreationError() {}
fun retryUpdateConversationName(conversationId: ConversationId) {}

Check warning on line 85 in features/meetings/src/main/java/com/wire/android/feature/meetings/ui/create/NewMeetingViewModel.kt

View check run for this annotation

Codecov / codecov/patch

features/meetings/src/main/java/com/wire/android/feature/meetings/ui/create/NewMeetingViewModel.kt#L85

Added line #L85 was not covered by tests

companion object {
const val MEETING_NAME_MAX_COUNT = 64
Expand All @@ -93,19 +97,22 @@
override val state: NewMeetingState = initialState(currentTimeProvider)
}

@Suppress("TooManyFunctions")
class NewMeetingViewModelImpl @AssistedInject constructor(
@Assisted savedStateHandle: SavedStateHandle,
override val currentTimeProvider: CurrentTimeProvider,
private val createNewMeeting: CreateNewMeetingUseCase,
private val updateMeeting: UpdateMeetingUseCase,
private val getNextMeetingOccurrence: GetNextMeetingOccurrenceUseCase,
private val observeConversationMembers: ObserveConversationMembersUseCase,
private val renameConversationUseCase: RenameConversationUseCase,
private val contactMapper: ContactMapper,
) : ActionsViewModel<NewMeetingViewActions>(), NewMeetingViewModel {
@AssistedFactory
interface Factory {
fun create(savedStateHandle: SavedStateHandle): NewMeetingViewModelImpl
}

val navArgs: NewMeetingNavArgs = savedStateHandle.navArgs()
override val type: NewMeetingType = navArgs.type
override val titleTextState: TextFieldState = TextFieldState()
Expand Down Expand Up @@ -279,7 +286,28 @@
state = state.copy(isSubmitting = false, continueButtonEnabled = true)
when (updateResult) {
is UpdateMeetingUseCase.Result.Success -> sendAction(NewMeetingViewActions.Success)
is UpdateMeetingUseCase.Result.Failure -> state = state.copy(submitError = NewMeetingState.SubmitError.Other)
is UpdateMeetingUseCase.Result.Failure -> state = state.copy(
submitError = when (updateResult) {
is UpdateMeetingUseCase.Result.Failure.UpdateConversationNameFailure ->
NewMeetingState.SubmitError.UpdateConversationNameFailure(updateResult.conversationId)

else -> NewMeetingState.SubmitError.Other
}
)
}
}
}
}

override fun retryUpdateConversationName(conversationId: ConversationId) {
viewModelScope.launch {
state = state.copy(isSubmitting = true, continueButtonEnabled = false)
renameConversationUseCase(conversationId = conversationId, conversationName = titleTextState.text.trim().toString()).let {
state = state.copy(isSubmitting = false, continueButtonEnabled = true)
when (it) {
is RenamingResult.Failure ->
state = state.copy(submitError = NewMeetingState.SubmitError.UpdateConversationNameFailure(conversationId))
Comment thread
ohassine marked this conversation as resolved.
RenamingResult.Success -> sendAction(NewMeetingViewActions.Success)
}
}
}
Expand Down Expand Up @@ -334,6 +362,7 @@
}

sealed interface SubmitError {
data class UpdateConversationNameFailure(val conversationId: ConversationId) : SubmitError
data object Other : SubmitError // TODO Add more specific error types in the future
}

Expand Down
3 changes: 3 additions & 0 deletions features/meetings/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@
<string name="new_meeting_edit_failure_description">Something went wrong while updating the meeting. Please try again.</string>
<string name="new_meeting_edit_init_failure_title">Could not load meeting</string>
<string name="new_meeting_edit_init_failure_description">Something went wrong while loading the meeting for update. Please try again.</string>
<string name="new_meeting_edit_conversation_name_failure_title">Could not update call name</string>
<string name="new_meeting_edit_conversation_name_failure_description">The meeting title was updated, but the name shown in the call could not be updated. Please try again.</string>
<string name="new_meeting_edit_conversation_name_failure_action">Retry</string>
<string name="meeting_join_failure_title">Could not join meeting</string>
<string name="meeting_join_failure_description">Something went wrong while joining the meeting. Please try again.</string>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.wire.android.mapper.ContactMapper
import com.wire.android.model.Contact
import com.wire.android.ui.home.conversationslist.model.Membership
import com.wire.android.util.CurrentTimeProvider
import com.wire.kalium.common.error.CoreFailure
import com.wire.kalium.logic.data.conversation.Conversation
import com.wire.kalium.logic.data.conversation.MemberDetails
import com.wire.kalium.logic.data.id.ConversationId
Expand All @@ -42,6 +43,8 @@ import com.wire.kalium.logic.data.user.ConnectionState
import com.wire.kalium.logic.data.user.OtherUser
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.conversation.ObserveConversationMembersUseCase
import com.wire.kalium.logic.feature.conversation.RenameConversationUseCase
import com.wire.kalium.logic.feature.conversation.RenamingResult
import com.wire.kalium.logic.feature.meeting.CreateNewMeetingUseCase
import com.wire.kalium.logic.feature.meeting.GetNextMeetingOccurrenceUseCase
import com.wire.kalium.logic.feature.meeting.UpdateMeetingUseCase
Expand Down Expand Up @@ -373,7 +376,7 @@ class NewMeetingViewModelTest {
Arrangement(dispatcher)
.withNewMeetingType(editType)
.withNextMeetingOccurrence(nextOccurrence)
.withUpdateMeetingResult(nextOccurrence.meeting.meetingId, UpdateMeetingUseCase.Result.Failure)
.withUpdateMeetingResult(nextOccurrence.meeting.meetingId, UpdateMeetingUseCase.Result.Failure.Other)
)

enterTitle(viewModel, "Weekly sync")
Expand All @@ -390,6 +393,144 @@ class NewMeetingViewModelTest {
}
}

@Test
fun givenConversationNameEditionFails_whenSubmitUpdateIsCalled_thenErrorIsShownAndSuccessActionIsNotSent() = runTest(dispatcher) {
val currentTime = Instant.parse("2026-01-01T12:00:00Z")
val editType = NewMeetingType.Edit(MeetingId("meeting-id", "domain"))
val nextOccurrence = MEETING_OCCURRENCE.copy(
meeting = MEETING_OCCURRENCE.meeting.copy(
startTime = currentTime + 1.hours,
endTime = currentTime + 2.hours,
recurrence = Meeting.Recurrence(frequency = Meeting.Recurrence.Frequency.DAILY, interval = 1L, until = null),
),
occurrenceStartTime = currentTime + 1.hours,
occurrenceEndTime = currentTime + 2.hours,
)
val (arrangement, viewModel) = arrangeViewModel(
Arrangement(dispatcher)
.withNewMeetingType(editType)
.withNextMeetingOccurrence(nextOccurrence)
.withUpdateMeetingResult(
nextOccurrence.meeting.meetingId,
UpdateMeetingUseCase.Result.Failure.UpdateConversationNameFailure(nextOccurrence.meeting.conversationId)
)
)

enterTitle(viewModel, "Weekly sync")

viewModel.actions.test {
viewModel.submitUpdate()
advanceUntilIdle()

coVerify(exactly = 1) { arrangement.updateMeeting(editType.id, any()) }
expectNoEvents()
assertFalse(viewModel.state.isSubmitting)
assertEquals(true, viewModel.state.continueButtonEnabled)
assertEquals(
NewMeetingState.SubmitError.UpdateConversationNameFailure(nextOccurrence.meeting.conversationId),
viewModel.state.submitError
)
}
}

@Test
fun givenUpdateActionReturnsUpdateConversationNameFailure_whenSubmitUpdateIsCalled_thenReturnedConversationErrorIsShown() =
runTest(dispatcher) {
val currentTime = Instant.parse("2026-01-01T12:00:00Z")
val editType = NewMeetingType.Edit(MeetingId("meeting-id", "domain"))
val nextOccurrence = MEETING_OCCURRENCE.copy(
meeting = MEETING_OCCURRENCE.meeting.copy(
startTime = currentTime + 1.hours,
endTime = currentTime + 2.hours,
recurrence = Meeting.Recurrence(frequency = Meeting.Recurrence.Frequency.DAILY, interval = 1L, until = null),
),
occurrenceStartTime = currentTime + 1.hours,
occurrenceEndTime = currentTime + 2.hours,
)
val failedConversationId = ConversationId("failed-conversation-id", "domain")
val (arrangement, viewModel) = arrangeViewModel(
Arrangement(dispatcher)
.withNewMeetingType(editType)
.withNextMeetingOccurrence(nextOccurrence)
.withUpdateMeetingResult(
nextOccurrence.meeting.meetingId,
UpdateMeetingUseCase.Result.Failure.UpdateConversationNameFailure(failedConversationId)
)
)

enterTitle(viewModel, "Weekly sync")

viewModel.actions.test {
viewModel.submitUpdate()
advanceUntilIdle()

coVerify(exactly = 1) { arrangement.updateMeeting(editType.id, any()) }
expectNoEvents()
assertFalse(viewModel.state.isSubmitting)
assertEquals(true, viewModel.state.continueButtonEnabled)
assertEquals(
NewMeetingState.SubmitError.UpdateConversationNameFailure(failedConversationId),
viewModel.state.submitError
)
}
}

@Test
fun givenRetryUpdateConversationNameSucceeds_whenRetryUpdateConversationNameIsCalled_thenSuccessActionIsSent() = runTest(dispatcher) {
val conversationId = ConversationId("conversation-id", "domain")
val (arrangement, viewModel) = arrangeViewModel(
Arrangement(dispatcher)
.withRenameConversationResult(conversationId, "Weekly sync", RenamingResult.Success)
)

enterTitle(viewModel, " Weekly sync ")

viewModel.actions.test {
viewModel.retryUpdateConversationName(conversationId)
advanceUntilIdle()

coVerify(exactly = 1) {
arrangement.renameConversationUseCase(
conversationId = conversationId,
conversationName = "Weekly sync"
)
}
assertNull(viewModel.state.submitError)
assertEquals(NewMeetingViewActions.Success, awaitItem())
cancelAndConsumeRemainingEvents()
}
}

@Test
fun givenRetryUpdateConversationNameFails_whenRetryUpdateConversationNameIsCalled_thenErrorIsShownAndSuccessActionIsNotSent() =
runTest(dispatcher) {
val conversationId = ConversationId("conversation-id", "domain")
val (arrangement, viewModel) = arrangeViewModel(
Arrangement(dispatcher)
.withRenameConversationResult(
conversationId = conversationId,
conversationName = "Weekly sync",
result = RenamingResult.Failure(CoreFailure.Unknown(RuntimeException("Failed to rename conversation")))
)
)

enterTitle(viewModel, " Weekly sync ")

viewModel.actions.test {
viewModel.retryUpdateConversationName(conversationId)
advanceUntilIdle()

coVerify(exactly = 1) {
arrangement.renameConversationUseCase(
conversationId = conversationId,
conversationName = "Weekly sync"
)
}
expectNoEvents()
assertEquals(NewMeetingState.SubmitError.UpdateConversationNameFailure(conversationId), viewModel.state.submitError)
}
}

@Test
fun givenInvalidTitle_whenSubmitCreationIsCalled_thenTitleErrorIsShownAndSuccessActionIsNotSent() = runTest(dispatcher) {
val (arrangement, viewModel) = arrangeViewModel()
Expand Down Expand Up @@ -563,6 +704,9 @@ class NewMeetingViewModelTest {
@MockK
lateinit var observeConversationMembers: ObserveConversationMembersUseCase

@MockK
lateinit var renameConversationUseCase: RenameConversationUseCase

@MockK
lateinit var contactMapper: ContactMapper

Expand Down Expand Up @@ -593,6 +737,10 @@ class NewMeetingViewModelTest {
coEvery { updateMeeting(meetingId, any()) } returns result
}

fun withRenameConversationResult(conversationId: ConversationId, conversationName: String, result: RenamingResult) = apply {
coEvery { renameConversationUseCase(conversationId, conversationName) } returns result
}

fun withNextMeetingOccurrence(nextMeetingOccurrence: MeetingOccurrence?) = apply {
coEvery { getNextMeetingOccurrence(any(), any()) } returns nextMeetingOccurrence
}
Expand All @@ -616,6 +764,7 @@ class NewMeetingViewModelTest {
updateMeeting = updateMeeting,
getNextMeetingOccurrence = getNextMeetingOccurrence,
observeConversationMembers = observeConversationMembers,
renameConversationUseCase = renameConversationUseCase,
contactMapper = contactMapper,
)
}
Expand Down
2 changes: 1 addition & 1 deletion kalium
Submodule kalium updated 18 files
+6 −0 data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/meeting/MeetingDao.kt
+41 −0 data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/meeting/MeetingDaoTest.kt
+19 −4 domain/calling/src/appleAvsMain/kotlin/com/wire/kalium/calling/AppleAvs.kt
+3 −2 domain/calling/src/appleMain/kotlin/com/wire/kalium/calling/AppleAvsBridge.kt
+7 −4 logic/src/appleMain/kotlin/com/wire/kalium/logic/feature/call/CallManagerImpl.kt
+1 −0 logic/src/appleMain/kotlin/com/wire/kalium/logic/feature/call/GlobalCallManager.kt
+6 −5 logic/src/commonJvmAndroid/kotlin/com/wire/kalium/logic/feature/call/CallManagerImpl.kt
+2 −1 logic/src/commonJvmAndroid/kotlin/com/wire/kalium/logic/util/DummyCallManager.kt
+98 −45 logic/src/commonMain/kotlin/com/wire/kalium/logic/data/meeting/MeetingRepository.kt
+2 −1 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/call/CallManager.kt
+17 −5 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/call/usecase/GetCallConversationTypeProvider.kt
+3 −2 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/call/usecase/StartCallUseCase.kt
+13 −5 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/meeting/UpdateMeetingUseCase.kt
+389 −20 logic/src/commonTest/kotlin/com/wire/kalium/logic/data/meeting/MeetingRepositoryTest.kt
+48 −23 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/usecase/GetCallConversationTypeProviderTest.kt
+54 −32 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/usecase/StartCallUseCaseTest.kt
+29 −0 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/meeting/UpdateMeetingUseCaseTest.kt
+74 −5 logic/src/jvmTest/kotlin/com/wire/kalium/logic/feature/call/CallManagerTest.kt
Loading