summaryrefslogtreecommitdiffhomepage
path: root/android/app/src
diff options
context:
space:
mode:
authorJonatan Rhodin <jonatan.rhodin@mullvad.net>2024-07-31 14:07:40 +0200
committerDavid Göransson <david.goransson@mullvad.net>2024-07-31 16:05:49 +0200
commita9aa3cfefedb09b3d59b2fa8146e8bd0819d8e7d (patch)
treee06095af859c3bfb5feb915ae119d61fbd708602 /android/app/src
parentd54bcf0bed1d9a40a2aabdef7bd81d91ed158d45 (diff)
downloadmullvadvpn-a9aa3cfefedb09b3d59b2fa8146e8bd0819d8e7d.tar.xz
mullvadvpn-a9aa3cfefedb09b3d59b2fa8146e8bd0819d8e7d.zip
Add support for more detailed location changed messages
Diffstat (limited to 'android/app/src')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/communication/CustomListActionResultData.kt53
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/communication/CustomListSuccess.kt12
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/CreateCustomListDialog.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeleteCustomListConfirmationDialog.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/EditCustomListNameDialog.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/CustomListLocationsScreen.kt26
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/CustomListsScreen.kt6
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/EditCustomListScreen.kt6
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreen.kt84
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/RelayListRepository.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/CustomListActionUseCase.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/CreateCustomListDialogViewModel.kt14
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModel.kt82
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeleteCustomListConfirmationViewModel.kt12
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListNameDialogViewModel.kt16
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt129
16 files changed, 303 insertions, 157 deletions
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 0000000000..441ddf1fc2
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/communication/CustomListActionResultData.kt
@@ -0,0 +1,53 @@
+package net.mullvad.mullvadvpn.compose.communication
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+import net.mullvad.mullvadvpn.lib.model.CustomListName
+
+sealed interface CustomListActionResultData : Parcelable {
+
+ sealed interface Success : CustomListActionResultData, Parcelable {
+ val undo: CustomListAction
+
+ @Parcelize
+ data class CreatedWithLocations(
+ val customListName: CustomListName,
+ val locationNames: List<String>,
+ override val undo: CustomListAction
+ ) : Success
+
+ @Parcelize
+ data class Deleted(
+ val customListName: CustomListName,
+ override val undo: CustomListAction.Create
+ ) : Success
+
+ @Parcelize
+ data class Renamed(
+ val newName: CustomListName,
+ override val undo: CustomListAction.Rename
+ ) : Success
+
+ @Parcelize
+ data class LocationAdded(
+ val customListName: CustomListName,
+ val locationName: String,
+ override val undo: CustomListAction
+ ) : Success
+
+ @Parcelize
+ data class LocationRemoved(
+ val customListName: CustomListName,
+ val locationName: String,
+ override val undo: CustomListAction
+ ) : Success
+
+ @Parcelize
+ data class LocationChanged(
+ val customListName: CustomListName,
+ override val undo: CustomListAction
+ ) : Success
+ }
+
+ @Parcelize data object GenericError : CustomListActionResultData
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/communication/CustomListSuccess.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/communication/CustomListSuccess.kt
index d83cd4c76d..3b6a30bf38 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/communication/CustomListSuccess.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/communication/CustomListSuccess.kt
@@ -1,9 +1,11 @@
package net.mullvad.mullvadvpn.compose.communication
import android.os.Parcelable
+import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import net.mullvad.mullvadvpn.lib.model.CustomListId
import net.mullvad.mullvadvpn.lib.model.CustomListName
+import net.mullvad.mullvadvpn.lib.model.GeoLocationId
sealed interface CustomListSuccess : Parcelable {
val undo: CustomListAction
@@ -31,6 +33,14 @@ data class Renamed(override val undo: CustomListAction.Rename) : CustomListSucce
@Parcelize
data class LocationsChanged(
+ val id: CustomListId,
val name: CustomListName,
+ val locations: List<GeoLocationId>,
+ val oldLocations: List<GeoLocationId>,
+) : CustomListSuccess {
override val undo: CustomListAction.UpdateLocations
-) : CustomListSuccess
+ get() = CustomListAction.UpdateLocations(id, oldLocations)
+
+ @IgnoredOnParcel val addedLocations = locations - oldLocations
+ @IgnoredOnParcel val removedLocations = oldLocations - locations
+}
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 db7f664e37..a94673ef45 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<Created>,
+ backNavigator: ResultBackNavigator<CustomListActionResultData.Success.CreatedWithLocations>,
) {
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 0f26bcbe48..bf0cd49277 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<Deleted>,
+ navigator: ResultBackNavigator<CustomListActionResultData.Success.Deleted>,
) {
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 3a6d234fc6..2b5c4372d0 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<Renamed>,
+ backNavigator: ResultBackNavigator<CustomListActionResultData.Success.Renamed>,
) {
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 cb3767784e..54bdaacb41 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
@@ -1,6 +1,5 @@
package net.mullvad.mullvadvpn.compose.screen
-import android.content.Context
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@@ -13,16 +12,12 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.SnackbarDuration
-import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
@@ -35,10 +30,9 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.result.NavResult
import com.ramcosta.composedestinations.result.ResultBackNavigator
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
@@ -52,7 +46,6 @@ import net.mullvad.mullvadvpn.compose.test.SAVE_BUTTON_TEST_TAG
import net.mullvad.mullvadvpn.compose.textfield.SearchTextField
import net.mullvad.mullvadvpn.compose.transitions.SlideInFromRightTransition
import net.mullvad.mullvadvpn.compose.util.LaunchedEffectCollect
-import net.mullvad.mullvadvpn.compose.util.showSnackbarImmediately
import net.mullvad.mullvadvpn.lib.model.CustomListId
import net.mullvad.mullvadvpn.lib.model.RelayItem
import net.mullvad.mullvadvpn.lib.theme.Dimens
@@ -79,7 +72,7 @@ data class CustomListLocationsNavArgs(
)
fun CustomListLocations(
navigator: DestinationsNavigator,
- backNavigator: ResultBackNavigator<LocationsChanged>,
+ backNavigator: ResultBackNavigator<CustomListActionResultData>,
discardChangesResultRecipient: ResultRecipient<DiscardChangesDestination, Boolean>,
) {
val customListsViewModel = koinViewModel<CustomListLocationsViewModel>()
@@ -95,27 +88,16 @@ fun CustomListLocations(
}
}
- val snackbarHostState = remember { SnackbarHostState() }
- 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(
- message = context.getString(R.string.error_occurred),
- duration = SnackbarDuration.Short
- )
- }
}
}
val state by customListsViewModel.uiState.collectAsStateWithLifecycle()
CustomListLocationsScreen(
state = state,
- snackbarHostState = snackbarHostState,
onSearchTermInput = customListsViewModel::onSearchTermInput,
onSaveClick = customListsViewModel::save,
onRelaySelectionClick = customListsViewModel::onRelaySelectionClick,
@@ -134,7 +116,6 @@ fun CustomListLocations(
@Composable
fun CustomListLocationsScreen(
state: CustomListLocationsUiState,
- snackbarHostState: SnackbarHostState = SnackbarHostState(),
onSearchTermInput: (String) -> Unit = {},
onSaveClick: () -> Unit = {},
onRelaySelectionClick: (RelayItem.Location, selected: Boolean) -> Unit = { _, _ -> },
@@ -142,7 +123,6 @@ fun CustomListLocationsScreen(
onBackClick: () -> Unit = {}
) {
ScaffoldWithSmallTopBar(
- snackbarHostState = snackbarHostState,
appBarTitle =
stringResource(
if (state.newList) {
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 5b9f0d4cc1..8ca728ca4b 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<RootGraph>(style = SlideInFromRightTransition::class)
fun CustomLists(
navigator: DestinationsNavigator,
- editCustomListResultRecipient: ResultRecipient<EditCustomListDestination, Deleted>
+ editCustomListResultRecipient:
+ ResultRecipient<EditCustomListDestination, CustomListActionResultData.Success.Deleted>
) {
val viewModel = koinViewModel<CustomListsViewModel>()
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 365328ea93..3bd6dc0ce1 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
@@ -83,8 +84,9 @@ data class EditCustomListNavArgs(val customListId: CustomListId)
)
fun EditCustomList(
navigator: DestinationsNavigator,
- backNavigator: ResultBackNavigator<Deleted>,
- confirmDeleteListResultRecipient: ResultRecipient<DeleteCustomListDestination, Deleted>
+ backNavigator: ResultBackNavigator<CustomListActionResultData.Success.Deleted>,
+ confirmDeleteListResultRecipient:
+ ResultRecipient<DeleteCustomListDestination, CustomListActionResultData.Success.Deleted>
) {
val viewModel = koinViewModel<EditCustomListViewModel>()
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 8cca835f74..422b7b58ee 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,12 +68,8 @@ import net.mullvad.mullvadvpn.compose.cell.IconCell
import net.mullvad.mullvadvpn.compose.cell.StatusRelayItemCell
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.Deleted
-import net.mullvad.mullvadvpn.compose.communication.LocationsChanged
-import net.mullvad.mullvadvpn.compose.communication.Renamed
+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.MullvadModalBottomSheet
@@ -132,12 +128,17 @@ private fun PreviewSelectLocationScreen() {
fun SelectLocation(
navigator: DestinationsNavigator,
backNavigator: ResultBackNavigator<Boolean>,
- createCustomListDialogResultRecipient: ResultRecipient<CreateCustomListDestination, Created>,
+ createCustomListDialogResultRecipient:
+ ResultRecipient<
+ CreateCustomListDestination,
+ CustomListActionResultData.Success.CreatedWithLocations
+ >,
editCustomListNameDialogResultRecipient:
- ResultRecipient<EditCustomListNameDestination, Renamed>,
- deleteCustomListDialogResultRecipient: ResultRecipient<DeleteCustomListDestination, Deleted>,
+ ResultRecipient<EditCustomListNameDestination, CustomListActionResultData.Success.Renamed>,
+ deleteCustomListDialogResultRecipient:
+ ResultRecipient<DeleteCustomListDestination, CustomListActionResultData.Success.Deleted>,
updateCustomListResultRecipient:
- ResultRecipient<CustomListLocationsDestination, LocationsChanged>
+ ResultRecipient<CustomListLocationsDestination, CustomListActionResultData>
) {
val vm = koinViewModel<SelectLocationViewModel>()
val state = vm.uiState.collectAsStateWithLifecycle()
@@ -147,22 +148,12 @@ fun SelectLocation(
val lazyListState = rememberLazyListState()
CollectSideEffectWithLifecycle(vm.uiSideEffect) {
when (it) {
- SelectLocationSideEffect.CloseScreen -> {
- backNavigator.navigateBack(result = true)
- }
- is SelectLocationSideEffect.LocationAddedToCustomList ->
- launch {
- snackbarHostState.showResultSnackbar(
- context = context,
- result = it.result,
- onUndo = vm::performAction
- )
- }
- is SelectLocationSideEffect.LocationRemovedFromCustomList ->
+ SelectLocationSideEffect.CloseScreen -> backNavigator.navigateBack(result = true)
+ is SelectLocationSideEffect.CustomListActionToast ->
launch {
snackbarHostState.showResultSnackbar(
context = context,
- result = it.result,
+ result = it.resultData,
onUndo = vm::performAction
)
}
@@ -335,7 +326,7 @@ fun SelectLocationScreen(
itemsIndexed(
items = state.relayListItems,
- key = { index: Int, item: RelayListItem -> item.key },
+ key = { _: Int, item: RelayListItem -> item.key },
contentType = { _, item -> item.contentType },
itemContent = { index: Int, listItem: RelayListItem ->
Column(modifier = Modifier.animateItem()) {
@@ -834,30 +825,53 @@ 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 is CustomListActionResultData.Success) context.getString(R.string.undo)
+ else {
+ null
+ },
duration = SnackbarDuration.Long,
- onAction = { onUndo(result.undo) }
+ onAction = {
+ if (result is CustomListActionResultData.Success) {
+ onUndo(result.undo)
+ }
+ }
)
}
-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 -> context.getString(R.string.locations_were_changed_for, name)
+ is CustomListActionResultData.Success.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.Success.Deleted ->
+ context.getString(R.string.delete_custom_list_message, customListName)
+ is CustomListActionResultData.Success.LocationAdded ->
+ context.getString(R.string.location_was_added_to_list, locationName, customListName)
+ is CustomListActionResultData.Success.LocationRemoved ->
+ context.getString(R.string.location_was_removed_from_list, locationName, customListName)
+ is CustomListActionResultData.Success.LocationChanged ->
+ context.getString(R.string.locations_were_changed_for, customListName)
+ is CustomListActionResultData.Success.Renamed ->
+ context.getString(R.string.name_was_changed_to, newName)
+ CustomListActionResultData.GenericError -> context.getString(R.string.error_occurred)
}
@Composable
-private fun <D : DestinationSpec, R : CustomListSuccess> ResultRecipient<D, R>
+private fun <D : DestinationSpec, R : CustomListActionResultData> ResultRecipient<D, R>
.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 7d9846c31b..ce41b57c4c 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/usecase/customlists/CustomListActionUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/CustomListActionUseCase.kt
index 354316793c..a23a6c6de5 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/CustomListActionUseCase.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/CustomListActionUseCase.kt
@@ -107,8 +107,10 @@ class CustomListActionUseCase(
.mapLeft(UpdateLocationsError::UpdateLocations)
.bind()
LocationsChanged(
+ id = action.id,
name = customList.name,
- undo = action.not(locations = customList.locations)
+ locations = action.locations,
+ oldLocations = customList.locations,
)
}
}
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 e367386bf2..5603ab4184 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.Success.CreatedWithLocations(
+ customListName = it.name,
+ locationNames = it.locationNames,
+ undo = it.undo
+ )
+ )
)
}
}
@@ -76,5 +82,7 @@ sealed interface CreateCustomListDialogSideEffect {
data class NavigateToCustomListLocationsScreen(val customListId: CustomListId) :
CreateCustomListDialogSideEffect
- data class ReturnWithResult(val result: Created) : CreateCustomListDialogSideEffect
+ data class ReturnWithResult(
+ val result: CustomListActionResultData.Success.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 7fa101a8e3..ffed0757b0 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
@@ -3,6 +3,8 @@ package net.mullvad.mullvadvpn.viewmodel
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import arrow.core.getOrElse
+import arrow.core.raise.either
import com.ramcosta.composedestinations.generated.destinations.CustomListLocationsDestination
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -16,6 +18,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.CustomListActionResultData
import net.mullvad.mullvadvpn.compose.communication.LocationsChanged
import net.mullvad.mullvadvpn.compose.state.CustomListLocationsUiState
import net.mullvad.mullvadvpn.compose.state.RelayLocationListItem
@@ -87,7 +90,7 @@ class CustomListLocationsViewModel(
viewModelScope.launch { fetchInitialSelectedLocations() }
}
- fun searchRelayListLocations() =
+ private fun searchRelayListLocations() =
combine(
_searchTerm,
relayListRepository.relayList,
@@ -108,26 +111,23 @@ class CustomListLocationsViewModel(
fun save() {
viewModelScope.launch {
_selectedLocations.value?.let { selectedLocations ->
- customListActionUseCase(
- CustomListAction.UpdateLocations(
- navArgs.customListId,
- selectedLocations.calculateLocationsToSave().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
- if (navArgs.newList) {
- CustomListLocationsSideEffect.CloseScreen
- } else {
- CustomListLocationsSideEffect.ReturnWithResult(it)
- }
- )
+ val locationsToSave = selectedLocations.calculateLocationsToSave()
+ val result =
+ either {
+ val success =
+ customListActionUseCase(
+ CustomListAction.UpdateLocations(
+ navArgs.customListId,
+ locationsToSave.map { it.id }
+ )
+ )
+ .bind()
+ calculateResultData(success, locationsToSave)
}
- )
+ .getOrElse { CustomListActionResultData.GenericError }
+ _uiSideEffect.tryEmit(
+ CustomListLocationsSideEffect.ReturnWithResultData(result = result)
+ )
}
}
}
@@ -241,7 +241,7 @@ class CustomListLocationsViewModel(
isExpanded: (RelayItemId) -> Boolean,
depth: Int = 0,
): List<RelayLocationListItem> = flatMap { relayItem ->
- buildList<RelayLocationListItem> {
+ buildList {
val expanded = isExpanded(relayItem.id)
add(
RelayLocationListItem(
@@ -277,15 +277,45 @@ class CustomListLocationsViewModel(
}
}
+ private fun calculateResultData(
+ success: LocationsChanged,
+ locationsToSave: List<RelayItem.Location>
+ ) =
+ if (navArgs.newList) {
+ CustomListActionResultData.Success.CreatedWithLocations(
+ customListName = success.name,
+ locationNames = locationsToSave.map { it.name },
+ undo = CustomListAction.Delete(id = success.id)
+ )
+ } else {
+ when {
+ success.addedLocations.size == 1 && success.removedLocations.isEmpty() ->
+ CustomListActionResultData.Success.LocationAdded(
+ customListName = success.name,
+ relayListRepository.find(success.removedLocations.first())!!.name,
+ undo = success.undo
+ )
+ success.removedLocations.size == 1 && success.addedLocations.isEmpty() ->
+ CustomListActionResultData.Success.LocationRemoved(
+ customListName = success.name,
+ locationName =
+ relayListRepository.find(success.removedLocations.first())!!.name,
+ undo = success.undo
+ )
+ else ->
+ CustomListActionResultData.Success.LocationChanged(
+ customListName = success.name,
+ undo = success.undo
+ )
+ }
+ }
+
companion object {
private const val EMPTY_SEARCH_TERM = ""
}
}
sealed interface CustomListLocationsSideEffect {
- data object CloseScreen : CustomListLocationsSideEffect
-
- data class ReturnWithResult(val result: LocationsChanged) : CustomListLocationsSideEffect
-
- data object Error : CustomListLocationsSideEffect
+ data class ReturnWithResultData(val result: CustomListActionResultData) :
+ 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 c492bd368a..cdb0327d16 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,7 +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.Deleted
+import net.mullvad.mullvadvpn.compose.communication.CustomListActionResultData
import net.mullvad.mullvadvpn.compose.state.DeleteCustomListUiState
import net.mullvad.mullvadvpn.lib.model.CustomListId
import net.mullvad.mullvadvpn.lib.model.CustomListName
@@ -49,7 +49,12 @@ class DeleteCustomListConfirmationViewModel(
{ _error.tryEmit(it) },
{
_uiSideEffect.send(
- DeleteCustomListConfirmationSideEffect.ReturnWithResult(it)
+ DeleteCustomListConfirmationSideEffect.ReturnWithResult(
+ CustomListActionResultData.Success.Deleted(
+ customListName = it.name,
+ undo = it.undo
+ )
+ )
)
}
)
@@ -58,5 +63,6 @@ class DeleteCustomListConfirmationViewModel(
}
sealed interface DeleteCustomListConfirmationSideEffect {
- data class ReturnWithResult(val result: Deleted) : DeleteCustomListConfirmationSideEffect
+ data class ReturnWithResult(val result: CustomListActionResultData.Success.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 0b71a5053e..c4825be9b7 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.Success.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.Success.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 f31fcb3078..a219bb976b 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
@@ -2,6 +2,7 @@ package net.mullvad.mullvadvpn.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import arrow.core.getOrElse
import arrow.core.raise.either
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
@@ -14,7 +15,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.FilterChip
import net.mullvad.mullvadvpn.compose.state.RelayListItem
import net.mullvad.mullvadvpn.compose.state.RelayListItem.CustomListHeader
@@ -44,7 +45,7 @@ import net.mullvad.mullvadvpn.usecase.customlists.FilterCustomListsRelayItemUseC
class SelectLocationViewModel(
private val relayListFilterRepository: RelayListFilterRepository,
private val availableProvidersUseCase: AvailableProvidersUseCase,
- private val customListsRelayItemUseCase: CustomListsRelayItemUseCase,
+ customListsRelayItemUseCase: CustomListsRelayItemUseCase,
private val filteredCustomListRelayItemsUseCase: FilterCustomListsRelayItemUseCase,
private val customListsRepository: CustomListsRepository,
private val customListActionUseCase: CustomListActionUseCase,
@@ -79,8 +80,7 @@ class SelectLocationViewModel(
val uiSideEffect = _uiSideEffect.receiveAsFlow()
private fun initialExpand(): Set<String> = buildSet {
- val item = relayListRepository.selectedLocation.value.getOrNull()
- when (item) {
+ when (val item = relayListRepository.selectedLocation.value.getOrNull()) {
is GeoLocationId.City -> {
add(item.country.code)
}
@@ -131,7 +131,7 @@ class SelectLocationViewModel(
.size
}
- buildList<FilterChip> {
+ buildList {
if (ownershipFilter != null) {
add(FilterChip.Ownership(ownershipFilter))
}
@@ -157,9 +157,10 @@ class SelectLocationViewModel(
searchTerm.length >= MIN_SEARCH_LENGTH,
selectedItem.getOrNull(),
filteredCustomLists,
- relayCountries,
- { it in expandedItems }
- )
+ relayCountries
+ ) {
+ it in expandedItems
+ }
if (relayItems.isEmpty()) {
add(RelayListItem.LocationsEmptyText(searchTerm))
} else {
@@ -201,7 +202,7 @@ class SelectLocationViewModel(
): List<RelayListItem> =
customLists.flatMap { customList ->
val expanded = isExpanded(customList.id.expandKey())
- buildList<RelayListItem> {
+ buildList {
add(
RelayListItem.CustomListItem(
customList,
@@ -243,36 +244,35 @@ class SelectLocationViewModel(
item: RelayItem.Location,
depth: Int = 1,
isExpanded: (String) -> Boolean,
- ): List<RelayListItem.CustomListEntryItem> =
- buildList<RelayListItem.CustomListEntryItem> {
- val expanded = isExpanded(item.id.expandKey(parent))
- add(
- RelayListItem.CustomListEntryItem(
- parentId = parent,
- item = item,
- expanded = expanded,
- depth
- )
+ ): List<RelayListItem.CustomListEntryItem> = buildList {
+ val expanded = isExpanded(item.id.expandKey(parent))
+ add(
+ RelayListItem.CustomListEntryItem(
+ parentId = parent,
+ item = item,
+ expanded = expanded,
+ depth
)
+ )
- if (expanded) {
- when (item) {
- is RelayItem.Location.City ->
- addAll(
- item.relays.flatMap {
- createCustomListEntry(parent, it, depth + 1, isExpanded)
- }
- )
- is RelayItem.Location.Country ->
- addAll(
- item.cities.flatMap {
- createCustomListEntry(parent, it, depth + 1, isExpanded)
- }
- )
- is RelayItem.Location.Relay -> {} // No children to add
- }
+ if (expanded) {
+ when (item) {
+ is RelayItem.Location.City ->
+ addAll(
+ item.relays.flatMap {
+ createCustomListEntry(parent, it, depth + 1, isExpanded)
+ }
+ )
+ is RelayItem.Location.Country ->
+ addAll(
+ item.cities.flatMap {
+ createCustomListEntry(parent, it, depth + 1, isExpanded)
+ }
+ )
+ is RelayItem.Location.Relay -> {} // No children to add
}
}
+ }
private fun createGeoLocationEntry(
item: RelayItem.Location,
@@ -363,11 +363,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.Success.LocationAdded(
+ customListName = it.name,
+ locationName = item.name,
+ undo = it.undo
+ )
+ } else {
+ CustomListActionResultData.Success.LocationChanged(
+ customListName = it.name,
+ undo = it.undo
+ )
+ }
+ },
+ )
+ _uiSideEffect.send(SelectLocationSideEffect.CustomListActionToast(result))
}
}
@@ -382,17 +399,26 @@ class SelectLocationViewModel(
val customList =
customListsRepository.getCustomListById(customListId).bind()
val newLocations = (customList.locations - item.id)
-
- customListActionUseCase(
- CustomListAction.UpdateLocations(customList.id, newLocations)
+ val success =
+ customListActionUseCase(
+ CustomListAction.UpdateLocations(customList.id, newLocations)
+ )
+ .bind()
+ if (success.addedLocations.isEmpty()) {
+ CustomListActionResultData.Success.LocationRemoved(
+ customListName = success.name,
+ locationName = item.name,
+ undo = success.undo
+ )
+ } else {
+ CustomListActionResultData.Success.LocationChanged(
+ customListName = success.name,
+ undo = success.undo
)
- .bind()
+ }
}
- .fold(
- { SelectLocationSideEffect.GenericError },
- { SelectLocationSideEffect.LocationRemovedFromCustomList(it) }
- )
- _uiSideEffect.send(result)
+ .getOrElse { CustomListActionResultData.GenericError }
+ _uiSideEffect.send(SelectLocationSideEffect.CustomListActionToast(result))
}
}
@@ -404,9 +430,8 @@ class SelectLocationViewModel(
sealed interface SelectLocationSideEffect {
data object CloseScreen : SelectLocationSideEffect
- data class LocationAddedToCustomList(val result: LocationsChanged) : SelectLocationSideEffect
-
- class LocationRemovedFromCustomList(val result: LocationsChanged) : SelectLocationSideEffect
+ data class CustomListActionToast(val resultData: CustomListActionResultData) :
+ SelectLocationSideEffect
data object GenericError : SelectLocationSideEffect
}