From fadd033d44b688ce1e37b9aa10c12364d84db36b Mon Sep 17 00:00:00 2001 From: Jonatan Rhodin Date: Mon, 22 Jul 2024 01:00:35 +0200 Subject: [PATCH] Improve and expand upon result data --- .../CustomListActionResultData.kt | 54 ++++++++++++ .../compose/dialog/CreateCustomListDialog.kt | 4 +- .../DeleteCustomListConfirmationDialog.kt | 4 +- .../dialog/EditCustomListNameDialog.kt | 4 +- .../screen/CustomListLocationsScreen.kt | 7 +- .../compose/screen/CustomListsScreen.kt | 6 +- .../compose/screen/EditCustomListScreen.kt | 6 +- .../compose/screen/SelectLocationScreen.kt | 85 +++++++++---------- .../repository/RelayListRepository.kt | 4 + .../CreateCustomListDialogViewModel.kt | 13 ++- .../viewmodel/CustomListLocationsViewModel.kt | 55 +++++++++--- .../DeleteCustomListConfirmationViewModel.kt | 11 ++- .../EditCustomListNameDialogViewModel.kt | 16 +++- .../viewmodel/SelectLocationViewModel.kt | 67 +++++++++++---- .../resource/src/main/res/values/strings.xml | 1 + 15 files changed, 243 insertions(+), 94 deletions(-) create mode 100644 android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/communication/CustomListActionResultData.kt diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/communication/CustomListActionResultData.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/communication/CustomListActionResultData.kt new file mode 100644 index 000000000000..8de9b616a797 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/communication/CustomListActionResultData.kt @@ -0,0 +1,54 @@ +package net.mullvad.mullvadvpn.compose.communication + +import android.os.Parcelable +import kotlinx.parcelize.IgnoredOnParcel +import kotlinx.parcelize.Parcelize +import net.mullvad.mullvadvpn.lib.model.CustomListName + +sealed interface CustomListActionResultData : Parcelable { + val undo: CustomListAction? + + @Parcelize + data class CreatedWithLocations( + val customListName: CustomListName, + val locationNames: List, + override val undo: CustomListAction + ) : CustomListActionResultData + + @Parcelize + data class Deleted( + val customListName: CustomListName, + override val undo: CustomListAction.Create + ) : CustomListActionResultData + + @Parcelize + data class Renamed(val newName: CustomListName, override val undo: CustomListAction) : + CustomListActionResultData + + @Parcelize + data class LocationAdded( + val customListName: CustomListName, + val locationName: String, + override val undo: CustomListAction + ) : CustomListActionResultData + + @Parcelize + data class LocationRemoved( + val customListName: CustomListName, + val locationName: String, + override val undo: CustomListAction + ) : CustomListActionResultData + + @Parcelize + data class LocationChanged( + val customListName: CustomListName, + override val undo: CustomListAction + ) : CustomListActionResultData + + @Parcelize + data object GenericError : CustomListActionResultData { + @IgnoredOnParcel override val undo: CustomListAction? = null + } + + fun hasUndo() = undo != null +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/CreateCustomListDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/CreateCustomListDialog.kt index db7f664e37b4..cf7e5361c907 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/CreateCustomListDialog.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/CreateCustomListDialog.kt @@ -23,7 +23,7 @@ import com.ramcosta.composedestinations.result.ResultBackNavigator import com.ramcosta.composedestinations.spec.DestinationStyle import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.compose.button.PrimaryButton -import net.mullvad.mullvadvpn.compose.communication.Created +import net.mullvad.mullvadvpn.compose.communication.CustomListActionResultData import net.mullvad.mullvadvpn.compose.component.CustomListNameTextField import net.mullvad.mullvadvpn.compose.state.CreateCustomListUiState import net.mullvad.mullvadvpn.compose.test.CREATE_CUSTOM_LIST_DIALOG_INPUT_TEST_TAG @@ -63,7 +63,7 @@ data class CreateCustomListNavArgs(val locationCode: GeoLocationId?) ) fun CreateCustomList( navigator: DestinationsNavigator, - backNavigator: ResultBackNavigator, + backNavigator: ResultBackNavigator, ) { val vm: CreateCustomListDialogViewModel = koinViewModel() LaunchedEffect(key1 = Unit) { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeleteCustomListConfirmationDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeleteCustomListConfirmationDialog.kt index 0f26bcbe4865..d1dc697981fb 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeleteCustomListConfirmationDialog.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeleteCustomListConfirmationDialog.kt @@ -11,7 +11,7 @@ import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.result.ResultBackNavigator import com.ramcosta.composedestinations.spec.DestinationStyle import net.mullvad.mullvadvpn.R -import net.mullvad.mullvadvpn.compose.communication.Deleted +import net.mullvad.mullvadvpn.compose.communication.CustomListActionResultData import net.mullvad.mullvadvpn.compose.state.DeleteCustomListUiState import net.mullvad.mullvadvpn.compose.util.LaunchedEffectCollect import net.mullvad.mullvadvpn.lib.model.CustomListId @@ -39,7 +39,7 @@ data class DeleteCustomListNavArgs(val customListId: CustomListId, val name: Cus navArgs = DeleteCustomListNavArgs::class ) fun DeleteCustomList( - navigator: ResultBackNavigator, + navigator: ResultBackNavigator, ) { val viewModel: DeleteCustomListConfirmationViewModel = koinViewModel() val state by viewModel.uiState.collectAsStateWithLifecycle() diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/EditCustomListNameDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/EditCustomListNameDialog.kt index 3a6d234fc6b1..970fef88410b 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/EditCustomListNameDialog.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/EditCustomListNameDialog.kt @@ -17,7 +17,7 @@ import com.ramcosta.composedestinations.result.ResultBackNavigator import com.ramcosta.composedestinations.spec.DestinationStyle import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.compose.button.PrimaryButton -import net.mullvad.mullvadvpn.compose.communication.Renamed +import net.mullvad.mullvadvpn.compose.communication.CustomListActionResultData import net.mullvad.mullvadvpn.compose.component.CustomListNameTextField import net.mullvad.mullvadvpn.compose.state.EditCustomListNameUiState import net.mullvad.mullvadvpn.compose.test.EDIT_CUSTOM_LIST_DIALOG_INPUT_TEST_TAG @@ -50,7 +50,7 @@ data class EditCustomListNameNavArgs( navArgs = EditCustomListNameNavArgs::class ) fun EditCustomListName( - backNavigator: ResultBackNavigator, + backNavigator: ResultBackNavigator, ) { val vm: EditCustomListNameDialogViewModel = koinViewModel() LaunchedEffectCollect(vm.uiSideEffect) { sideEffect -> diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/CustomListLocationsScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/CustomListLocationsScreen.kt index 19b548153a6b..a4b7b4f7b04f 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/CustomListLocationsScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/CustomListLocationsScreen.kt @@ -37,7 +37,7 @@ import com.ramcosta.composedestinations.result.ResultRecipient import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.compose.cell.CheckableRelayLocationCell -import net.mullvad.mullvadvpn.compose.communication.LocationsChanged +import net.mullvad.mullvadvpn.compose.communication.CustomListActionResultData import net.mullvad.mullvadvpn.compose.component.LocationsEmptyText import net.mullvad.mullvadvpn.compose.component.MullvadCircularProgressIndicatorLarge import net.mullvad.mullvadvpn.compose.component.NavigateBackIconButton @@ -79,7 +79,7 @@ data class CustomListLocationsNavArgs( ) fun CustomListLocations( navigator: DestinationsNavigator, - backNavigator: ResultBackNavigator, + backNavigator: ResultBackNavigator, discardChangesResultRecipient: ResultRecipient, ) { val customListsViewModel = koinViewModel() @@ -99,9 +99,8 @@ fun CustomListLocations( val context: Context = LocalContext.current LaunchedEffectCollect(customListsViewModel.uiSideEffect) { sideEffect -> when (sideEffect) { - is CustomListLocationsSideEffect.ReturnWithResult -> + is CustomListLocationsSideEffect.ReturnWithResultData -> backNavigator.navigateBack(result = sideEffect.result) - CustomListLocationsSideEffect.CloseScreen -> backNavigator.navigateBack() CustomListLocationsSideEffect.Error -> launch { snackbarHostState.showSnackbarImmediately( diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/CustomListsScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/CustomListsScreen.kt index 5b9f0d4cc1c1..77ab226ea931 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/CustomListsScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/CustomListsScreen.kt @@ -34,6 +34,7 @@ import com.ramcosta.composedestinations.result.ResultRecipient import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.compose.cell.NavigationComposeCell +import net.mullvad.mullvadvpn.compose.communication.CustomListActionResultData import net.mullvad.mullvadvpn.compose.communication.Deleted import net.mullvad.mullvadvpn.compose.component.MullvadCircularProgressIndicatorLarge import net.mullvad.mullvadvpn.compose.component.NavigateBackIconButton @@ -63,7 +64,8 @@ private fun PreviewCustomListsScreen() { @Destination(style = SlideInFromRightTransition::class) fun CustomLists( navigator: DestinationsNavigator, - editCustomListResultRecipient: ResultRecipient + editCustomListResultRecipient: + ResultRecipient ) { val viewModel = koinViewModel() val state by viewModel.uiState.collectAsStateWithLifecycle() @@ -82,7 +84,7 @@ fun CustomLists( message = context.getString( R.string.delete_custom_list_message, - result.value.name + result.value.customListName ), actionLabel = context.getString(R.string.undo), duration = SnackbarDuration.Long, diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/EditCustomListScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/EditCustomListScreen.kt index c3f7662ea250..071821b2bc1c 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/EditCustomListScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/EditCustomListScreen.kt @@ -33,6 +33,7 @@ import com.ramcosta.composedestinations.result.ResultBackNavigator import com.ramcosta.composedestinations.result.ResultRecipient import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.compose.cell.TwoRowCell +import net.mullvad.mullvadvpn.compose.communication.CustomListActionResultData import net.mullvad.mullvadvpn.compose.communication.Deleted import net.mullvad.mullvadvpn.compose.component.MullvadCircularProgressIndicatorLarge import net.mullvad.mullvadvpn.compose.component.NavigateBackIconButton @@ -86,8 +87,9 @@ data class EditCustomListNavArgs(val customListId: CustomListId) ) fun EditCustomList( navigator: DestinationsNavigator, - backNavigator: ResultBackNavigator, - confirmDeleteListResultRecipient: ResultRecipient + backNavigator: ResultBackNavigator, + confirmDeleteListResultRecipient: + ResultRecipient ) { val viewModel = koinViewModel() diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreen.kt index cbf0fe96b36d..b72743c26b38 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreen.kt @@ -68,11 +68,9 @@ import net.mullvad.mullvadvpn.compose.cell.IconCell import net.mullvad.mullvadvpn.compose.cell.StatusRelayLocationCell import net.mullvad.mullvadvpn.compose.cell.SwitchComposeSubtitleCell import net.mullvad.mullvadvpn.compose.cell.ThreeDotCell -import net.mullvad.mullvadvpn.compose.communication.Created import net.mullvad.mullvadvpn.compose.communication.CustomListAction -import net.mullvad.mullvadvpn.compose.communication.CustomListSuccess +import net.mullvad.mullvadvpn.compose.communication.CustomListActionResultData import net.mullvad.mullvadvpn.compose.communication.Deleted -import net.mullvad.mullvadvpn.compose.communication.LocationsChanged import net.mullvad.mullvadvpn.compose.communication.Renamed import net.mullvad.mullvadvpn.compose.component.LocationsEmptyText import net.mullvad.mullvadvpn.compose.component.MullvadCircularProgressIndicatorLarge @@ -139,12 +137,17 @@ private fun PreviewSelectLocationScreen() { fun SelectLocation( navigator: DestinationsNavigator, backNavigator: ResultBackNavigator, - createCustomListDialogResultRecipient: ResultRecipient, + createCustomListDialogResultRecipient: + ResultRecipient< + CreateCustomListDestination, + CustomListActionResultData.CreatedWithLocations + >, editCustomListNameDialogResultRecipient: - ResultRecipient, - deleteCustomListDialogResultRecipient: ResultRecipient, + ResultRecipient, + deleteCustomListDialogResultRecipient: + ResultRecipient, updateCustomListResultRecipient: - ResultRecipient + ResultRecipient ) { val vm = koinViewModel() val state = vm.uiState.collectAsStateWithLifecycle().value @@ -155,19 +158,11 @@ fun SelectLocation( CollectSideEffectWithLifecycle(vm.uiSideEffect) { when (it) { SelectLocationSideEffect.CloseScreen -> backNavigator.navigateBack(result = true) - is SelectLocationSideEffect.LocationAddedToCustomList -> + is SelectLocationSideEffect.CustomListActionToast -> launch { snackbarHostState.showResultSnackbar( context = context, - result = it.result, - onUndo = vm::performAction - ) - } - is SelectLocationSideEffect.LocationRemovedFromCustomList -> - launch { - snackbarHostState.showResultSnackbar( - context = context, - result = it.result, + result = it.resultData, onUndo = vm::performAction ) } @@ -830,45 +825,49 @@ private suspend fun LazyListState.animateScrollAndCentralizeItem(index: Int) { private suspend fun SnackbarHostState.showResultSnackbar( context: Context, - result: CustomListSuccess, + result: CustomListActionResultData, onUndo: (CustomListAction) -> Unit ) { + showSnackbarImmediately( message = result.message(context), - actionLabel = context.getString(R.string.undo), + actionLabel = + if (result.hasUndo()) context.getString(R.string.undo) + else { + null + }, duration = SnackbarDuration.Long, - onAction = { onUndo(result.undo) } + onAction = { result.undo?.let { onUndo(it) } } ) } -private fun CustomListSuccess.message(context: Context): String = +private fun CustomListActionResultData.message(context: Context): String = when (this) { - is Created -> - locationNames.firstOrNull()?.let { locationName -> - context.getString(R.string.location_was_added_to_list, locationName, name) - } ?: context.getString(R.string.locations_were_changed_for, name) - is Deleted -> context.getString(R.string.delete_custom_list_message, name) - is Renamed -> context.getString(R.string.name_was_changed_to, name) - is LocationsChanged -> - when { - addedLocations.size == 1 && removedLocations.isEmpty() -> - context.getString( - R.string.location_was_added_to_list, - addedLocations.first(), - name - ) - removedLocations.size == 1 && addedLocations.isEmpty() -> - context.getString( - R.string.location_was_removed_from_list, - removedLocations.first(), - name - ) - else -> context.getString(R.string.locations_were_changed_for, name) + is CustomListActionResultData.CreatedWithLocations -> + if (locationNames.size == 1) { + context.getString( + R.string.location_was_added_to_list, + locationNames.first(), + customListName + ) + } else { + context.getString(R.string.create_custom_list_message, customListName) } + is CustomListActionResultData.Deleted -> + context.getString(R.string.delete_custom_list_message, customListName) + is CustomListActionResultData.LocationAdded -> + context.getString(R.string.location_was_added_to_list, locationName, customListName) + is CustomListActionResultData.LocationRemoved -> + context.getString(R.string.location_was_removed_from_list, locationName, customListName) + is CustomListActionResultData.LocationChanged -> + context.getString(R.string.locations_were_changed_for, customListName) + is CustomListActionResultData.Renamed -> + context.getString(R.string.name_was_changed_to, newName) + CustomListActionResultData.GenericError -> context.getString(R.string.error_occurred) } @Composable -private fun ResultRecipient +private fun ResultRecipient .OnCustomListNavResult( snackbarHostState: SnackbarHostState, performAction: (action: CustomListAction) -> Unit diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/RelayListRepository.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/RelayListRepository.kt index 7d9846c31bb9..ce41b57c4ca4 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/RelayListRepository.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/RelayListRepository.kt @@ -11,11 +11,13 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService import net.mullvad.mullvadvpn.lib.model.Constraint +import net.mullvad.mullvadvpn.lib.model.GeoLocationId import net.mullvad.mullvadvpn.lib.model.PortRange import net.mullvad.mullvadvpn.lib.model.RelayItem import net.mullvad.mullvadvpn.lib.model.RelayItemId import net.mullvad.mullvadvpn.lib.model.WireguardConstraints import net.mullvad.mullvadvpn.lib.model.WireguardEndpointData +import net.mullvad.mullvadvpn.relaylist.findByGeoLocationId class RelayListRepository( private val managementService: ManagementService, @@ -49,5 +51,7 @@ class RelayListRepository( suspend fun updateSelectedWireguardConstraints(value: WireguardConstraints) = managementService.setWireguardConstraints(value) + fun find(geoLocationId: GeoLocationId) = relayList.value.findByGeoLocationId(geoLocationId) + private fun defaultWireguardEndpointData() = WireguardEndpointData(emptyList()) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/CreateCustomListDialogViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/CreateCustomListDialogViewModel.kt index e367386bf265..bbe51c0a8f24 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/CreateCustomListDialogViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/CreateCustomListDialogViewModel.kt @@ -12,8 +12,8 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import net.mullvad.mullvadvpn.compose.communication.Created import net.mullvad.mullvadvpn.compose.communication.CustomListAction +import net.mullvad.mullvadvpn.compose.communication.CustomListActionResultData import net.mullvad.mullvadvpn.compose.state.CreateCustomListUiState import net.mullvad.mullvadvpn.lib.model.CustomListId import net.mullvad.mullvadvpn.lib.model.CustomListName @@ -58,7 +58,13 @@ class CreateCustomListDialogViewModel( ) } else { _uiSideEffect.send( - CreateCustomListDialogSideEffect.ReturnWithResult(it) + CreateCustomListDialogSideEffect.ReturnWithResult( + CustomListActionResultData.CreatedWithLocations( + customListName = it.name, + locationNames = it.locationNames, + undo = it.undo + ) + ) ) } } @@ -76,5 +82,6 @@ sealed interface CreateCustomListDialogSideEffect { data class NavigateToCustomListLocationsScreen(val customListId: CustomListId) : CreateCustomListDialogSideEffect - data class ReturnWithResult(val result: Created) : CreateCustomListDialogSideEffect + data class ReturnWithResult(val result: CustomListActionResultData.CreatedWithLocations) : + CreateCustomListDialogSideEffect } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModel.kt index 6d738a641707..1b1e4acc82ba 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModel.kt @@ -14,7 +14,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.compose.communication.CustomListAction -import net.mullvad.mullvadvpn.compose.communication.LocationsChanged +import net.mullvad.mullvadvpn.compose.communication.CustomListActionResultData import net.mullvad.mullvadvpn.compose.state.CustomListLocationsUiState import net.mullvad.mullvadvpn.lib.model.RelayItem import net.mullvad.mullvadvpn.relaylist.descendants @@ -25,7 +25,7 @@ import net.mullvad.mullvadvpn.usecase.customlists.CustomListActionUseCase import net.mullvad.mullvadvpn.usecase.customlists.CustomListRelayItemsUseCase class CustomListLocationsViewModel( - relayListRepository: RelayListRepository, + private val relayListRepository: RelayListRepository, private val customListRelayItemsUseCase: CustomListRelayItemsUseCase, private val customListActionUseCase: CustomListActionUseCase, savedStateHandle: SavedStateHandle @@ -81,23 +81,55 @@ class CustomListLocationsViewModel( fun save() { viewModelScope.launch { _selectedLocations.value?.let { selectedLocations -> + val locationsToSave = selectedLocations.calculateLocationsToSave() customListActionUseCase( CustomListAction.UpdateLocations( navArgs.customListId, - selectedLocations.calculateLocationsToSave().map { it.id } + locationsToSave.map { it.id } ) ) .fold( { _uiSideEffect.tryEmit(CustomListLocationsSideEffect.Error) }, - { - _uiSideEffect.tryEmit( - // This is so that we don't show a snackbar after returning to the - // select location screen + { result -> + val resultData = if (navArgs.newList) { - CustomListLocationsSideEffect.CloseScreen + CustomListActionResultData.CreatedWithLocations( + customListName = result.name, + locationNames = locationsToSave.map { it.name }, + undo = CustomListAction.Delete(id = result.id) + ) } else { - CustomListLocationsSideEffect.ReturnWithResult(it) + when { + result.addedLocations.size == 1 && + result.removedLocations.isEmpty() -> + CustomListActionResultData.LocationAdded( + customListName = result.name, + relayListRepository + .find(result.removedLocations.first())!! + .name, + undo = result.undo + ) + result.removedLocations.size == 1 && + result.addedLocations.isEmpty() -> + CustomListActionResultData.LocationRemoved( + customListName = result.name, + locationName = + relayListRepository + .find(result.removedLocations.first())!! + .name, + undo = result.undo + ) + else -> + CustomListActionResultData.LocationChanged( + customListName = result.name, + undo = result.undo + ) + } } + _uiSideEffect.tryEmit( + CustomListLocationsSideEffect.ReturnWithResultData( + result = resultData + ) ) } ) @@ -204,9 +236,8 @@ class CustomListLocationsViewModel( } sealed interface CustomListLocationsSideEffect { - data object CloseScreen : CustomListLocationsSideEffect - - data class ReturnWithResult(val result: LocationsChanged) : CustomListLocationsSideEffect + data class ReturnWithResultData(val result: CustomListActionResultData) : + CustomListLocationsSideEffect data object Error : CustomListLocationsSideEffect } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeleteCustomListConfirmationViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeleteCustomListConfirmationViewModel.kt index c492bd368a30..6a163517a0fe 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeleteCustomListConfirmationViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeleteCustomListConfirmationViewModel.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.compose.communication.CustomListAction +import net.mullvad.mullvadvpn.compose.communication.CustomListActionResultData import net.mullvad.mullvadvpn.compose.communication.Deleted import net.mullvad.mullvadvpn.compose.state.DeleteCustomListUiState import net.mullvad.mullvadvpn.lib.model.CustomListId @@ -49,7 +50,12 @@ class DeleteCustomListConfirmationViewModel( { _error.tryEmit(it) }, { _uiSideEffect.send( - DeleteCustomListConfirmationSideEffect.ReturnWithResult(it) + DeleteCustomListConfirmationSideEffect.ReturnWithResult( + CustomListActionResultData.Deleted( + customListName = it.name, + undo = it.undo + ) + ) ) } ) @@ -58,5 +64,6 @@ class DeleteCustomListConfirmationViewModel( } sealed interface DeleteCustomListConfirmationSideEffect { - data class ReturnWithResult(val result: Deleted) : DeleteCustomListConfirmationSideEffect + data class ReturnWithResult(val result: CustomListActionResultData.Deleted) : + DeleteCustomListConfirmationSideEffect } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListNameDialogViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListNameDialogViewModel.kt index 0b71a5053ead..934cdb7471c6 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListNameDialogViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListNameDialogViewModel.kt @@ -13,7 +13,7 @@ import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.compose.communication.CustomListAction -import net.mullvad.mullvadvpn.compose.communication.Renamed +import net.mullvad.mullvadvpn.compose.communication.CustomListActionResultData import net.mullvad.mullvadvpn.compose.state.EditCustomListNameUiState import net.mullvad.mullvadvpn.lib.model.CustomListName import net.mullvad.mullvadvpn.usecase.customlists.CustomListActionUseCase @@ -52,7 +52,16 @@ class EditCustomListNameDialogViewModel( ) .fold( { _error.emit(it) }, - { _uiSideEffect.send(EditCustomListNameDialogSideEffect.ReturnWithResult(it)) } + { + _uiSideEffect.send( + EditCustomListNameDialogSideEffect.ReturnWithResult( + CustomListActionResultData.Renamed( + newName = it.name, + undo = it.undo + ) + ) + ) + } ) } } @@ -64,5 +73,6 @@ class EditCustomListNameDialogViewModel( } sealed interface EditCustomListNameDialogSideEffect { - data class ReturnWithResult(val result: Renamed) : EditCustomListNameDialogSideEffect + data class ReturnWithResult(val result: CustomListActionResultData.Renamed) : + EditCustomListNameDialogSideEffect } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt index 2509fdc8765d..6babd80e432e 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt @@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.compose.communication.CustomListAction -import net.mullvad.mullvadvpn.compose.communication.LocationsChanged +import net.mullvad.mullvadvpn.compose.communication.CustomListActionResultData import net.mullvad.mullvadvpn.compose.state.SelectLocationUiState import net.mullvad.mullvadvpn.compose.state.toNullableOwnership import net.mullvad.mullvadvpn.compose.state.toSelectedProviders @@ -135,11 +135,28 @@ class SelectLocationViewModel( viewModelScope.launch { val newLocations = (customList.locations + item).filter { it !in item.descendants() }.map { it.id } - customListActionUseCase(CustomListAction.UpdateLocations(customList.id, newLocations)) - .fold( - { _uiSideEffect.send(SelectLocationSideEffect.GenericError) }, - { _uiSideEffect.send(SelectLocationSideEffect.LocationAddedToCustomList(it)) }, - ) + val result = + customListActionUseCase( + CustomListAction.UpdateLocations(customList.id, newLocations) + ) + .fold( + { CustomListActionResultData.GenericError }, + { + if (it.removedLocations.isEmpty()) { + CustomListActionResultData.LocationAdded( + customListName = it.name, + locationName = item.name, + undo = it.undo + ) + } else { + CustomListActionResultData.LocationChanged( + customListName = it.name, + undo = it.undo + ) + } + }, + ) + _uiSideEffect.send(SelectLocationSideEffect.CustomListActionToast(result)) } } @@ -150,15 +167,28 @@ class SelectLocationViewModel( fun removeLocationFromList(item: RelayItem.Location, customList: RelayItem.CustomList) { viewModelScope.launch { val newLocations = (customList.locations - item).map { it.id } - customListActionUseCase(CustomListAction.UpdateLocations(customList.id, newLocations)) - .fold( - { _uiSideEffect.send(SelectLocationSideEffect.GenericError) }, - { - _uiSideEffect.send( - SelectLocationSideEffect.LocationRemovedFromCustomList(it) - ) - } - ) + val result = + customListActionUseCase( + CustomListAction.UpdateLocations(customList.id, newLocations) + ) + .fold( + { CustomListActionResultData.GenericError }, + { + if (it.addedLocations.isEmpty()) { + CustomListActionResultData.LocationRemoved( + customListName = it.name, + locationName = item.name, + undo = it.undo + ) + } else { + CustomListActionResultData.LocationChanged( + customListName = it.name, + undo = it.undo + ) + } + }, + ) + _uiSideEffect.send(SelectLocationSideEffect.CustomListActionToast(result)) } } @@ -177,9 +207,12 @@ class SelectLocationViewModel( sealed interface SelectLocationSideEffect { data object CloseScreen : SelectLocationSideEffect - data class LocationAddedToCustomList(val result: LocationsChanged) : SelectLocationSideEffect + // data class LocationAddedToCustomList(val result: LocationsChanged) : SelectLocationSideEffect + + // class LocationRemovedFromCustomList(val result: LocationsChanged) : SelectLocationSideEffect - class LocationRemovedFromCustomList(val result: LocationsChanged) : SelectLocationSideEffect + data class CustomListActionToast(val resultData: CustomListActionResultData) : + SelectLocationSideEffect data object GenericError : SelectLocationSideEffect } diff --git a/android/lib/resource/src/main/res/values/strings.xml b/android/lib/resource/src/main/res/values/strings.xml index 099d14fda973..531a9b046b94 100644 --- a/android/lib/resource/src/main/res/values/strings.xml +++ b/android/lib/resource/src/main/res/values/strings.xml @@ -384,4 +384,5 @@ Failed to set to current - API not reachable Failed to set to current - Unknown reason %s was removed from \"%s\" + \"%s\" was created