diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt index 5a20438c2359..fd4a97a1d251 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt @@ -1,6 +1,7 @@ package net.mullvad.mullvadvpn.compose.data import net.mullvad.mullvadvpn.model.Constraint +import net.mullvad.mullvadvpn.model.CustomListName import net.mullvad.mullvadvpn.model.PortRange import net.mullvad.mullvadvpn.model.RelayEndpointData import net.mullvad.mullvadvpn.model.RelayList @@ -46,6 +47,16 @@ val DUMMY_RELAY_COUNTRIES = val DUMMY_CUSTOM_LISTS = listOf( - RelayItem.CustomList("First list", false, "1", locations = DUMMY_RELAY_COUNTRIES), - RelayItem.CustomList("Empty list", expanded = false, "2", locations = emptyList()) + RelayItem.CustomList( + CustomListName.fromString("First list"), + false, + "1", + locations = DUMMY_RELAY_COUNTRIES + ), + RelayItem.CustomList( + CustomListName.fromString("Empty list"), + expanded = false, + "2", + locations = emptyList() + ) ) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/communication/CustomListAction.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/communication/CustomListAction.kt index 0b478f5272f4..9ddee73e22d0 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/communication/CustomListAction.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/communication/CustomListAction.kt @@ -2,22 +2,26 @@ package net.mullvad.mullvadvpn.compose.communication import android.os.Parcelable import kotlinx.parcelize.Parcelize +import net.mullvad.mullvadvpn.model.CustomListName sealed interface CustomListAction : Parcelable { @Parcelize - data class Rename(val customListId: String, val name: String, val newName: String) : - CustomListAction { + data class Rename( + val customListId: String, + val name: CustomListName, + val newName: CustomListName + ) : CustomListAction { fun not() = this.copy(name = newName, newName = name) } @Parcelize data class Delete(val customListId: String) : CustomListAction { - fun not(name: String, locations: List) = Create(name, locations) + fun not(name: CustomListName, locations: List) = Create(name, locations) } @Parcelize - data class Create(val name: String = "", val locations: List = emptyList()) : + data class Create(val name: CustomListName, val locations: List = emptyList()) : CustomListAction, Parcelable { fun not(customListId: String) = Delete(customListId) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/communication/CustomListResult.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/communication/CustomListResult.kt index 32fa077a7f18..14cba09b44d0 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/communication/CustomListResult.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/communication/CustomListResult.kt @@ -2,6 +2,7 @@ package net.mullvad.mullvadvpn.compose.communication import android.os.Parcelable import kotlinx.parcelize.Parcelize +import net.mullvad.mullvadvpn.model.CustomListName sealed interface CustomListResult : Parcelable { val undo: CustomListAction @@ -9,26 +10,26 @@ sealed interface CustomListResult : Parcelable { @Parcelize data class Created( val id: String, - val name: String, + val name: CustomListName, val locationName: String?, override val undo: CustomListAction.Delete ) : CustomListResult @Parcelize data class Deleted(override val undo: CustomListAction.Create) : CustomListResult { - val name + val name: CustomListName get() = undo.name } @Parcelize data class Renamed(override val undo: CustomListAction.Rename) : CustomListResult { - val name: String + val name: CustomListName get() = undo.name } @Parcelize data class LocationsChanged( - val name: String, + val name: CustomListName, override val undo: CustomListAction.UpdateLocations ) : CustomListResult } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/CustomListNameTextField.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/CustomListNameTextField.kt index 675f6f8f14dc..b3a0ece5773e 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/CustomListNameTextField.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/CustomListNameTextField.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.compose.textfield.CustomTextField +import net.mullvad.mullvadvpn.model.CustomListName import net.mullvad.mullvadvpn.model.CustomListsError @Composable @@ -41,6 +42,7 @@ fun CustomListNameTextField( placeholderText = null, isValidValue = error == null, isDigitsOnlyAllowed = false, + maxCharLength = CustomListName.MAX_LENGTH, supportingText = error?.let { { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/CustomListExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/CustomListExtensions.kt index 6fb87a6af523..ad668ed9e88c 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/CustomListExtensions.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/CustomListExtensions.kt @@ -1,13 +1,14 @@ package net.mullvad.mullvadvpn.relaylist import net.mullvad.mullvadvpn.model.CustomList +import net.mullvad.mullvadvpn.model.CustomListName private fun CustomList.toRelayItemCustomList( relayCountries: List ): RelayItem.CustomList = RelayItem.CustomList( id = this.id, - name = this.name, + customListName = CustomListName.fromString(name), expanded = false, locations = this.locations.mapNotNull { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItem.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItem.kt index 54c4a9bef4e5..ce4be395b651 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItem.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItem.kt @@ -1,5 +1,6 @@ package net.mullvad.mullvadvpn.relaylist +import net.mullvad.mullvadvpn.model.CustomListName import net.mullvad.mullvadvpn.model.GeoIpLocation import net.mullvad.mullvadvpn.model.GeographicLocationConstraint @@ -15,11 +16,12 @@ sealed interface RelayItem { val expanded: Boolean data class CustomList( - override val name: String, + val customListName: CustomListName, override val expanded: Boolean, val id: String, val locations: List, ) : RelayItem { + override val name: String = customListName.value override val active get() = locations.any { location -> location.active } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/CustomListsRepository.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/CustomListsRepository.kt index f1a38871bd9f..0832f434a56a 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/CustomListsRepository.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/CustomListsRepository.kt @@ -8,6 +8,7 @@ import net.mullvad.mullvadvpn.lib.ipc.Request import net.mullvad.mullvadvpn.lib.ipc.events import net.mullvad.mullvadvpn.model.CreateCustomListResult import net.mullvad.mullvadvpn.model.CustomList +import net.mullvad.mullvadvpn.model.CustomListName import net.mullvad.mullvadvpn.model.CustomListsError import net.mullvad.mullvadvpn.model.GeographicLocationConstraint import net.mullvad.mullvadvpn.model.UpdateCustomListResult @@ -20,8 +21,8 @@ class CustomListsRepository( private val settingsRepository: SettingsRepository, private val relayListListener: RelayListListener ) { - suspend fun createCustomList(name: String): CreateCustomListResult { - val result = messageHandler.trySendRequest(Request.CreateCustomList(name)) + suspend fun createCustomList(name: CustomListName): CreateCustomListResult { + val result = messageHandler.trySendRequest(Request.CreateCustomList(name.value)) return if (result) { messageHandler.events().first().result @@ -52,8 +53,8 @@ class CustomListsRepository( ArrayList(locationCodes.mapNotNull { getGeographicLocationConstraintByCode(it) }) ) - suspend fun updateCustomListName(id: String, name: String): UpdateCustomListResult = - getCustomListById(id)?.let { updateCustomList(it.copy(name = name)) } + suspend fun updateCustomListName(id: String, name: CustomListName): UpdateCustomListResult = + getCustomListById(id)?.let { updateCustomList(it.copy(name = name.value)) } ?: UpdateCustomListResult.Error(CustomListsError.OtherError) private suspend fun updateCustomListLocations( 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 7b2e5a43aa00..8d722325d650 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 @@ -5,6 +5,7 @@ import net.mullvad.mullvadvpn.compose.communication.CustomListAction import net.mullvad.mullvadvpn.compose.communication.CustomListResult import net.mullvad.mullvadvpn.model.CreateCustomListResult import net.mullvad.mullvadvpn.model.CustomList +import net.mullvad.mullvadvpn.model.CustomListName import net.mullvad.mullvadvpn.model.GeographicLocationConstraint import net.mullvad.mullvadvpn.model.UpdateCustomListResult import net.mullvad.mullvadvpn.relaylist.getRelayItemsByCodes @@ -79,9 +80,9 @@ class CustomListActionUseCase( } fun performAction(action: CustomListAction.Delete): Result { - val customList: CustomList? = customListsRepository.getCustomListById(action.customListId) + val customList: CustomList = customListsRepository.getCustomListById(action.customListId)!! val oldLocations = customList.locations() - val name = customList?.name ?: "" + val name = CustomListName.fromString(customList.name) customListsRepository.deleteCustomList(action.customListId) return Result.success( CustomListResult.Deleted(undo = action.not(locations = oldLocations, name = name)) @@ -91,9 +92,9 @@ class CustomListActionUseCase( suspend fun performAction( action: CustomListAction.UpdateLocations ): Result { - val customList: CustomList? = customListsRepository.getCustomListById(action.customListId) + val customList = customListsRepository.getCustomListById(action.customListId)!! val oldLocations = customList.locations() - val name = customList?.name ?: "" + val name = CustomListName.fromString(customList.name) customListsRepository.updateCustomListLocationsFromCodes( action.customListId, action.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 9ae5bb7a648b..f58916cd66c6 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 @@ -13,6 +13,7 @@ import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.compose.communication.CustomListAction import net.mullvad.mullvadvpn.compose.communication.CustomListResult import net.mullvad.mullvadvpn.compose.state.CreateCustomListUiState +import net.mullvad.mullvadvpn.model.CustomListName import net.mullvad.mullvadvpn.model.CustomListsError import net.mullvad.mullvadvpn.usecase.customlists.CustomListActionUseCase import net.mullvad.mullvadvpn.usecase.customlists.CustomListsException @@ -38,7 +39,7 @@ class CreateCustomListDialogViewModel( customListActionUseCase .performAction( CustomListAction.Create( - name, + CustomListName.fromString(name), if (locationCode.isNotEmpty()) { listOf(locationCode) } else { 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 5fa99306a35f..5efba5321ee3 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 @@ -28,8 +28,6 @@ class CustomListLocationsViewModel( private val relayListUseCase: RelayListUseCase, private val customListActionUseCase: CustomListActionUseCase ) : ViewModel() { - private var customListName: String = "" - private val _uiSideEffect = MutableSharedFlow(replay = 1, extraBufferCapacity = 1) val uiSideEffect: SharedFlow = _uiSideEffect @@ -195,11 +193,9 @@ class CustomListLocationsViewModel( private suspend fun fetchInitialSelectedLocations() { _selectedLocations.value = - awaitCustomListById(customListId) - ?.apply { customListName = name } - ?.locations - ?.selectChildren() - .apply { _initialLocations.value = this ?: emptySet() } + awaitCustomListById(customListId)?.locations?.selectChildren().apply { + _initialLocations.value = this ?: emptySet() + } } companion object { 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 c2625e6d567d..9a8d3d2f6236 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,6 +13,7 @@ import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.compose.communication.CustomListAction import net.mullvad.mullvadvpn.compose.communication.CustomListResult import net.mullvad.mullvadvpn.compose.state.UpdateCustomListUiState +import net.mullvad.mullvadvpn.model.CustomListName import net.mullvad.mullvadvpn.model.CustomListsError import net.mullvad.mullvadvpn.usecase.customlists.CustomListActionUseCase import net.mullvad.mullvadvpn.usecase.customlists.CustomListsException @@ -44,8 +45,8 @@ class EditCustomListNameDialogViewModel( .performAction( CustomListAction.Rename( customListId = customListId, - name = initialName, - newName = name + name = CustomListName.fromString(initialName), + newName = CustomListName.fromString(name) ) ) .fold( diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/repository/CustomListsRepositoryTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/repository/CustomListsRepositoryTest.kt index 129d921c36ea..9c2ac615c31e 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/repository/CustomListsRepositoryTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/repository/CustomListsRepositoryTest.kt @@ -13,6 +13,7 @@ import net.mullvad.mullvadvpn.lib.ipc.Request import net.mullvad.mullvadvpn.lib.ipc.events import net.mullvad.mullvadvpn.model.CreateCustomListResult import net.mullvad.mullvadvpn.model.CustomList +import net.mullvad.mullvadvpn.model.CustomListName import net.mullvad.mullvadvpn.model.CustomListsError import net.mullvad.mullvadvpn.model.GeographicLocationConstraint import net.mullvad.mullvadvpn.model.RelayList @@ -94,7 +95,8 @@ class CustomListsRepositoryTest { flowOf(Event.CreateCustomListResultEvent(expectedResult)) // Act - val result = customListsRepository.createCustomList(customListName) + val result = + customListsRepository.createCustomList(CustomListName.fromString(customListName)) // Assert assertEquals(expectedResult, result) @@ -113,7 +115,8 @@ class CustomListsRepositoryTest { flowOf(Event.CreateCustomListResultEvent(expectedResult)) // Act - val result = customListsRepository.createCustomList(customListName) + val result = + customListsRepository.createCustomList(CustomListName.fromString(customListName)) // Assert assertEquals(expectedResult, result) @@ -139,7 +142,11 @@ class CustomListsRepositoryTest { every { mockSettings.customLists.customLists } returns arrayListOf(mockCustomList) // Act - val result = customListsRepository.updateCustomListName(customListId, customListName) + val result = + customListsRepository.updateCustomListName( + customListId, + CustomListName.fromString(customListName) + ) // Assert assertEquals(expectedResult, result) @@ -167,7 +174,11 @@ class CustomListsRepositoryTest { every { mockSettings.customLists.customLists } returns arrayListOf(mockCustomList) // Act - val result = customListsRepository.updateCustomListName(customListId, customListName) + val result = + customListsRepository.updateCustomListName( + customListId, + CustomListName.fromString(customListName) + ) // Assert assertEquals(expectedResult, result) diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/CustomListActionUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/CustomListActionUseCaseTest.kt index 0370f23ffb41..fdcb4170f00f 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/CustomListActionUseCaseTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/CustomListActionUseCaseTest.kt @@ -11,6 +11,7 @@ import net.mullvad.mullvadvpn.compose.communication.CustomListAction import net.mullvad.mullvadvpn.compose.communication.CustomListResult import net.mullvad.mullvadvpn.model.CreateCustomListResult import net.mullvad.mullvadvpn.model.CustomList +import net.mullvad.mullvadvpn.model.CustomListName import net.mullvad.mullvadvpn.model.CustomListsError import net.mullvad.mullvadvpn.model.GeographicLocationConstraint import net.mullvad.mullvadvpn.model.UpdateCustomListResult @@ -40,7 +41,7 @@ class CustomListActionUseCaseTest { @Test fun `create action should return success when ok`() = runTest { // Arrange - val name = "test" + val name = CustomListName.fromString("test") val locationCode = "AB" val locationName = "Acklaba" val createdId = "1" @@ -83,7 +84,7 @@ class CustomListActionUseCaseTest { @Test fun `create action should return error when name already exists`() = runTest { // Arrange - val name = "test" + val name = CustomListName.fromString("test") val locationCode = "AB" val action = CustomListAction.Create(name = name, locations = listOf(locationCode)) val expectedError = CustomListsError.CustomListExists @@ -103,8 +104,8 @@ class CustomListActionUseCaseTest { @Test fun `rename action should return success when ok`() = runTest { // Arrange - val name = "test" - val newName = "test2" + val name = CustomListName.fromString("test") + val newName = CustomListName.fromString("test2") val customListId = "1" val action = CustomListAction.Rename(customListId = customListId, name = name, newName = newName) @@ -123,8 +124,8 @@ class CustomListActionUseCaseTest { @Test fun `rename action should return error when name already exists`() = runTest { // Arrange - val name = "test" - val newName = "test2" + val name = CustomListName.fromString("test") + val newName = CustomListName.fromString("test2") val customListId = "1" val action = CustomListAction.Rename(customListId = customListId, name = name, newName = newName) @@ -149,7 +150,7 @@ class CustomListActionUseCaseTest { val mockCustomList: CustomList = mockk() val mockLocation: GeographicLocationConstraint.Country = mockk() val mockLocations: ArrayList = arrayListOf(mockLocation) - val name = "test" + val name = CustomListName.fromString("test") val customListId = "1" val locationCode = "AB" val action = CustomListAction.Delete(customListId = customListId) @@ -160,7 +161,7 @@ class CustomListActionUseCaseTest { ) ) every { mockCustomList.locations } returns mockLocations - every { mockCustomList.name } returns name + every { mockCustomList.name } returns name.value every { mockLocation.countryCode } returns locationCode coEvery { mockCustomListsRepository.deleteCustomList(id = customListId) } returns true every { mockCustomListsRepository.getCustomListById(customListId) } returns mockCustomList @@ -175,7 +176,7 @@ class CustomListActionUseCaseTest { @Test fun `update locations action should return success with changed locations`() = runTest { // Arrange - val name = "test" + val name = CustomListName.fromString("test") val oldLocationCodes = listOf("AB", "CD") val newLocationCodes = listOf("EF", "GH") val oldLocations: ArrayList = @@ -184,7 +185,7 @@ class CustomListActionUseCaseTest { GeographicLocationConstraint.Country("CD") ) val customListId = "1" - val customList = CustomList(id = customListId, name = name, locations = oldLocations) + val customList = CustomList(id = customListId, name = name.value, locations = oldLocations) val action = CustomListAction.UpdateLocations( customListId = customListId, diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListViewModelTest.kt index 33986961b3a1..cbc5ff1c50dc 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListViewModelTest.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import net.mullvad.mullvadvpn.compose.state.EditCustomListState import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule +import net.mullvad.mullvadvpn.model.CustomListName import net.mullvad.mullvadvpn.relaylist.RelayItem import net.mullvad.mullvadvpn.usecase.RelayListUseCase import org.junit.jupiter.api.Assertions.assertEquals @@ -23,7 +24,12 @@ class EditCustomListViewModelTest { // Arrange val customListId = "2" val customList = - RelayItem.CustomList(id = "1", name = "test", expanded = false, locations = emptyList()) + RelayItem.CustomList( + id = "1", + customListName = CustomListName.fromString("test"), + expanded = false, + locations = emptyList() + ) every { mockRelayListUseCase.customLists() } returns flowOf(listOf(customList)) val viewModel = createViewModel(customListId) @@ -41,7 +47,7 @@ class EditCustomListViewModelTest { val customList = RelayItem.CustomList( id = customListId, - name = "test", + customListName = CustomListName.fromString("test"), expanded = false, locations = emptyList() ) diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomListName.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomListName.kt new file mode 100644 index 000000000000..5822eec2b3a1 --- /dev/null +++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/CustomListName.kt @@ -0,0 +1,20 @@ +package net.mullvad.mullvadvpn.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +@JvmInline +value class CustomListName private constructor(val value: String) : Parcelable { + + override fun toString() = value + + companion object { + const val MAX_LENGTH = 30 + + fun fromString(name: String): CustomListName { + val trimmedName = name.trim().take(MAX_LENGTH) + return CustomListName(trimmedName) + } + } +}