Android Kotlin. Мне необходимо в buildList, если в additionalInfoList.map срабатывает ветка else то в начало списка buildList добавляется элемент MaxiPaySubscriptionsListViewItem.AdditionalInfoType.FAQ, КАК ЭТО СДЕЛАТЬ? package ru.maxi.mobile.feature.maxiPay.ui.subscriptionsList

import android.app.Application
import android.net.Uri
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import ru.maxi.mobile.R
import ru.maxi.mobile.core.data.exception.BusinessLogicException
import ru.maxi.mobile.core.data.exception.BusinessLogicValidException
import ru.maxi.mobile.feature.analytics.model.AnalyticType
import ru.maxi.mobile.feature.analytics.model.actions.AnalyticMaxiPayActions
import ru.maxi.mobile.feature.analytics.model.events.AnalyticsGeneralEvent
import ru.maxi.mobile.feature.analytics.usecase.AnalyticsUseCase
import ru.maxi.mobile.feature.appDialogs.usecase.NotificationsPermissionUseCase
import ru.maxi.mobile.feature.maxiPay.domain.CreateMaxiPaySubscriptionUseCase
import ru.maxi.mobile.feature.maxiPay.domain.GetMaxiPayAdditionalInfoListUseCase
import ru.maxi.mobile.feature.maxiPay.domain.GetMaxiPayAvailableMerchantsListUseCase
import ru.maxi.mobile.feature.maxiPay.domain.GetMaxiPayClientSettingsUseCase
import ru.maxi.mobile.feature.maxiPay.domain.GetMaxiPayMerchantsListDataUseCase
import ru.maxi.mobile.feature.maxiPay.domain.MaxiPaySseUseCase
import ru.maxi.mobile.feature.maxiPay.domain.SendMaxiPaySavePaymentSettingsRequestUseCase
import ru.maxi.mobile.feature.maxiPay.domain.UpdateMaxiPayClientSettingsUseCase
import ru.maxi.mobile.feature.maxiPay.domain.UpdateMaxiPayPrimarySubscriptionUseCase
import ru.maxi.mobile.feature.maxiPay.domain.UpdateMaxiPaySubscriptionBankDataUseCase
import ru.maxi.mobile.feature.maxiPay.model.MaxiPayAdditionalInfo
import ru.maxi.mobile.feature.maxiPay.model.MaxiPayClientSettings
import ru.maxi.mobile.feature.maxiPay.model.MaxiPayMerchantWithSubscriptions
import ru.maxi.mobile.feature.maxiPay.model.MaxiPayMerchantsListData
import ru.maxi.mobile.feature.maxiPay.model.MaxiPaySseEvent
import ru.maxi.mobile.feature.maxiPay.model.MaxiPaySubscription
import ru.maxi.mobile.feature.maxiPay.ui.subscriptionsList.model.MaxiPaySubscriptionListNavigationEvent
import ru.maxi.mobile.feature.maxiPay.ui.subscriptionsList.model.MaxiPaySubscriptionsListViewItem
import ru.maxi.mobile.feature.sbp.model.BankAppInfo
import ru.maxi.mobile.feature.sbp.model.SbpBankChooserResult
import java.util.UUID
import javax.inject.Inject

/**

[ViewModel] экрана экрана списка счетов Макси Pay
*/
@HiltViewModel
internal class MaxiPaySubscriptionsListViewModel @Inject constructor(
application: Application,
getMerchantsListData: GetMaxiPayMerchantsListDataUseCase,
getMerchantsList: GetMaxiPayAvailableMerchantsListUseCase,
getClientSettings: GetMaxiPayClientSettingsUseCase,
getAdditionalInfoList: GetMaxiPayAdditionalInfoListUseCase,
private val createSubscription: CreateMaxiPaySubscriptionUseCase,
private val updatePrimarySubscription: UpdateMaxiPayPrimarySubscriptionUseCase,
private val updateSubscriptionBankData: UpdateMaxiPaySubscriptionBankDataUseCase,
private val sendSavePaymentSettingsRequest: SendMaxiPaySavePaymentSettingsRequestUseCase,
private val updateMaxiPayClientSettings: UpdateMaxiPayClientSettingsUseCase,
private val notificationsPermissionUseCase: NotificationsPermissionUseCase,
private val sseUseCase: MaxiPaySseUseCase,
private val analyticsUseCase: AnalyticsUseCase,
) : AndroidViewModel(application), MaxiPayPaymentConfirmationActions { private enum class NotificationPermissionRequestSource {
FROM_TOGGLE,
FROM_RESUME,
} private var hasActiveSseConnection = false private var notificationPermissionRequestState: NotificationPermissionRequestSource? = null private val _blockingLoaderState = MutableStateFlow(false) private val _navigationEvent = Channel(Channel.BUFFERED) private val _messageEvent = Channel(Channel.BUFFERED) private val paymentConfirmationSaveButtonState =
MutableStateFlow(MaxiPaySubscriptionsListViewItem.PaymentConfirmationBlock.SaveButtonState.IDLE)
private val merchantsGroupsState = MutableStateFlow<List>(emptyList()) private val clientSettingsState: StateFlow<MaxiPayClientSettings?> = getClientSettings()
.stateIn(viewModelScope, SharingStarted.Lazily, null)
/** События навигации */
val navigationEvent = _navigationEvent.receiveAsFlow() /** События сообщений */
val messageEvent = _messageEvent.receiveAsFlow() /** События SSE-соединения */
val sseEvents: Flow = sseUseCase.sseEvents /** Состояние отображения блокирующего лоадера /
val blockingLoaderState: Flow = _blockingLoaderState
.stateIn(viewModelScope, SharingStarted.Lazily, _blockingLoaderState.value)
/* Состояние экрана */
val viewState: Flow<List> = combine(
combine(getMerchantsListData(), getMerchantsList(), ::prepareMerchantsGroups),
clientSettingsState.filterNotNull(),
paymentConfirmationSaveButtonState,
getAdditionalInfoList(),
) { merchantsGroups, clientSettings, paymentConfirmationSaveButtonState, additionalInfoList ->
val subscriptions = merchantsGroups.flatMap { group ->
group.merchants.flatMap(MaxiPayMerchantWithSubscriptions::subscriptions)
}
if (subscriptions.isEmpty()) {
_navigationEvent.send(MaxiPaySubscriptionListNavigationEvent.OpenAboutScreen)
}

showAddOtherSubscriptionDialogIfNeeded(
oldMerchants = merchantsGroupsState.value.flatMap(MerchantsGroup::merchants),
newMerchants = merchantsGroups.flatMap(MerchantsGroup::merchants),
)

merchantsGroupsState.update { merchantsGroups }

if (subscriptions.all { it.status == MaxiPaySubscription.Status.CONFIRMED }) {
disconnectFromSseConnectionIfAllowed()
} else {
connectToSseConnectionIfAllowed()
}

val atLeastOneSubscriptionWithCreatedStatus = subscriptions
.any { it.status == MaxiPaySubscription.Status.CREATED }
return@combine buildList {

// add(MaxiPaySubscriptionsListViewItem.AdditionalInfoType.FAQ)
add(
MaxiPaySubscriptionsListViewItem.Title(
getApplication().getString(R.string.maxi_pay_payment_confirmation_block_title),
),
)
add(
MaxiPaySubscriptionsListViewItem.PaymentConfirmationBlock(
clientSettings,
paymentConfirmationSaveButtonState,
),
)
addAll(
merchantsGroups.flatMap {
it.toViewItems(forceCreateButtonHidden = atLeastOneSubscriptionWithCreatedStatus)
},
)
addAll(
additionalInfoList.map {
if (subscriptions.any {it.status == MaxiPaySubscription.Status.CREATED}) {
remove(MaxiPaySubscriptionsListViewItem.AdditionalInfoType.FAQ)
MaxiPaySubscriptionsListViewItem.AdditionalInfoType.FaqAndAboutService
} else {
MaxiPaySubscriptionsListViewItem.AdditionalInfoType.AboutService
}
// when (it) {
// is MaxiPayAdditionalInfo.Faq -> MaxiPaySubscriptionsListViewItem.AdditionalInfoType.FAQ
// is MaxiPayAdditionalInfo.AboutService -> MaxiPaySubscriptionsListViewItem.AdditionalInfoType.AboutService
// is MaxiPayAdditionalInfo.FaqAndAboutService -> MaxiPaySubscriptionsListViewItem.AdditionalInfoType.FaqAndAboutService
// }
},
)

// if (subscriptions.any {it.status == MaxiPaySubscription.Status.CONFIRMED}) {
// remove(MaxiPaySubscriptionsListViewItem.AdditionalInfoType.FAQ)
// add(
// MaxiPaySubscriptionsListViewItem.AdditionalInfoType.FaqAndAboutService,
// )
// } else {
// add(MaxiPaySubscriptionsListViewItem.AdditionalInfoType.AboutService)
// }

}
}.stateIn(viewModelScope, SharingStarted.Lazily, emptyList()) private fun showAddOtherSubscriptionDialogIfNeeded(
oldMerchants: List,
newMerchants: List,
) {
if (oldMerchants.isEmpty()) return

val atLeastOneOldSubscriptionWithConfirmedStatus = oldMerchants.any { merchant ->
merchant.subscriptions.any { it.status == MaxiPaySubscription.Status.CONFIRMED }
}
if (atLeastOneOldSubscriptionWithConfirmedStatus) return

val merchantsWithConfirmedSubscriptions = newMerchants.mapNotNull { merchant ->
merchant.merchant.takeIf {
merchant.subscriptions.any { it.status == MaxiPaySubscription.Status.CONFIRMED }
}
}

if (merchantsWithConfirmedSubscriptions.isEmpty() ||
merchantsWithConfirmedSubscriptions.any { !it.isMain } ||
newMerchants.none { it.merchant.isMain }
) {
return
}

_navigationEvent.trySend(MaxiPaySubscriptionListNavigationEvent.ShowAddOtherSubscriptionDialog)
} private fun prepareMerchantsGroups(
merchantsListData: MaxiPayMerchantsListData,
merchants: List,
): List = buildList {
val (mainMerchants, otherMerchants) = merchants.partition { it.merchant.isMain }

if (mainMerchants.isNotEmpty()) {
add(MerchantsGroup(merchantsListData.mainMerchantsListTitle, mainMerchants))
}

if (otherMerchants.isNotEmpty()) {
add(MerchantsGroup(merchantsListData.otherMerchantsListTitle, otherMerchants))
}
} private fun List.getMerchantNameBySubscriptionUuid(subscriptionUuid: UUID): String? {
return firstNotNullOfOrNull { merchantsGroup ->
merchantsGroup.merchants.firstNotNullOfOrNull { merchant ->
merchant.merchant.name
.takeIf { merchant.subscriptions.any { it.uuid == subscriptionUuid } }
}
}
} /**
Обработать клик по счёту с идентификатором [uuid]
*/
fun onSubscriptionClick(uuid: UUID) {
val subscription = merchantsGroupsState.value.firstNotNullOfOrNull { merchantsGroup ->
merchantsGroup.merchants.firstNotNullOfOrNull { merchant ->
merchant.subscriptions.firstOrNull { it.uuid == uuid }
}
} ?: return when (subscription.status) {
MaxiPaySubscription.Status.CREATED -> subscription.subscriptionUrl?.let { subscriptionUrl ->
launchBankChooser(
uuid,
subscriptionUrl,
requireNotNull(merchantsGroupsState.value.getMerchantNameBySubscriptionUuid(uuid)),
)
}

MaxiPaySubscription.Status.IN_PROCESS -> {}

MaxiPaySubscription.Status.CONFIRMED -> viewModelScope.launch {
try {
updatePrimarySubscription(uuid)
} catch (exception: Exception) {
val errorMessage = (exception as? BusinessLogicException)?.message
?: exception.localizedMessage
_messageEvent.send(errorMessage)
}
}
}
} /**
Обработать клик по кнопке добавления нового счёта для мерчанта с кодом [merchantCode]
*/
fun onCreateSubscriptionClick(merchantCode: String) {
val merchant = merchantsGroupsState.value
.flatMap { it.merchants }
.find { it.merchant.code == merchantCode }
?.merchant
?: return
val analyticAction = if (merchant.isMain) {
AnalyticMaxiPayActions.PRODUCT_PAY_BUTTON_ADD
} else {
AnalyticMaxiPayActions.ALKO_PAY_BUTTON_ADD
} analyticsUseCase log AnalyticsGeneralEvent.SelectContent(
analyticType = AnalyticType.MAXI_PAY,
analyticAction = analyticAction,
) connectToSseConnectionIfAllowed()
viewModelScope.launch {
try {
_blockingLoaderState.update { true }
createSubscription(merchantCode)
} catch (exception: Exception) {
val errorMessage = (exception as? BusinessLogicValidException)?.message
?: (exception as? BusinessLogicException)?.message
?: exception.localizedMessage
_messageEvent.send(errorMessage)
} finally {
_blockingLoaderState.update { false }
}
}
} /**
Обработать разрешение отображения пуш-уведомлений
*/
fun onNotificationsPermissionGranted() {
val currentSettings = clientSettingsState.value ?: return
val notificationsPermissionState = notificationPermissionRequestState ?: return
if (notificationsPermissionState == NotificationPermissionRequestSource.FROM_RESUME) {
notificationPermissionRequestState = null
return
}
viewModelScope.launch {
try {
sendSavePaymentSettingsRequest()
} catch (exception: Exception) {
val errorMessage = (exception as? BusinessLogicException)?.message
?: exception.localizedMessage
_messageEvent.send(errorMessage)
updateMaxiPayClientSettings(currentSettings.copy(usePaymentConfirmation = false))
} finally {
notificationPermissionRequestState = null
}
}
}
/**
Обработать запрет отображения пуш-уведомлений
*/
fun onNotificationsPermissionDenied() {
val currentSettings = clientSettingsState.value ?: return
val notificationsPermissionState = notificationPermissionRequestState ?: return
viewModelScope.launch {
updateMaxiPayClientSettings(currentSettings.copy(usePaymentConfirmation = false))
if (notificationsPermissionState == NotificationPermissionRequestSource.FROM_RESUME) {
try {
sendSavePaymentSettingsRequest()
} catch (exception: Exception) {
val errorMessage = (exception as? BusinessLogicException)?.message
?: exception.localizedMessage
_messageEvent.send(errorMessage)
updateMaxiPayClientSettings(currentSettings)
}
}
notificationPermissionRequestState = null
}
}
/**
Проверить разрешение на отправку пуш-уведомлений
*/
fun checkNotificationPermission() {
val currentSettings = clientSettingsState.value ?: return
if (!currentSettings.usePaymentConfirmation) return when (notificationPermissionRequestState) {
NotificationPermissionRequestSource.FROM_TOGGLE ->
if (notificationsPermissionUseCase.isNotificationPermissionGranted) {
onNotificationsPermissionGranted()
} else {
onNotificationsPermissionDenied()
}

NotificationPermissionRequestSource.FROM_RESUME -> {
notificationPermissionRequestState = null
}

null -> if (!notificationsPermissionUseCase.isNotificationPermissionGranted) {
notificationPermissionRequestState = NotificationPermissionRequestSource.FROM_RESUME
viewModelScope.launch {
_navigationEvent.send(MaxiPaySubscriptionListNavigationEvent.LaunchNotificationsPermissionRequest)
}
}
}
} override fun onPaymentConfirmationMinSumChange(minSum: Long) {
val currentSettings = clientSettingsState.value ?: return

 analyticsUseCase log AnalyticsGeneralEvent.SelectContent(
     analyticType = AnalyticType.MAXI_PAY,
     analyticAction = AnalyticMaxiPayActions.PROOF_PAY_BUTTON_SAVE,
     index = AnalyticsGeneralEvent.Index.IntIndex(minSum.toInt()),
 )

 viewModelScope.launch {
     try {
         paymentConfirmationSaveButtonState.update {
             MaxiPaySubscriptionsListViewItem.PaymentConfirmationBlock.SaveButtonState.LOADING
         }
         updateMaxiPayClientSettings(currentSettings.copy(paymentConfirmationMinSum = minSum))
         sendSavePaymentSettingsRequest()
         _messageEvent.send(SAVE_BUTTON_MESSAGE_TEXT)
     } catch (exception: Exception) {
         val errorMessage = (exception as? BusinessLogicException)?.message
             ?: exception.localizedMessage
         _messageEvent.send(errorMessage)
         updateMaxiPayClientSettings(currentSettings)
     } finally {
         paymentConfirmationSaveButtonState.update {
             MaxiPaySubscriptionsListViewItem.PaymentConfirmationBlock.SaveButtonState.IDLE
         }
     }
 }
} override fun onPaymentConfirmationStateChange(isEnabled: Boolean) {
val currentSettings = clientSettingsState.value ?: return

 val analyticAction =
     if (isEnabled) AnalyticMaxiPayActions.PROOF_PAY_CONFIRM else AnalyticMaxiPayActions.PROOF_PAY_CANCEL
 analyticsUseCase log AnalyticsGeneralEvent.SelectContent(
     analyticType = AnalyticType.MAXI_PAY,
     analyticAction = analyticAction,
 )

 viewModelScope.launch {
     try {
         updateMaxiPayClientSettings(currentSettings.copy(usePaymentConfirmation = isEnabled))
         if (isEnabled && !notificationsPermissionUseCase.isNotificationPermissionGranted) {
             notificationPermissionRequestState = NotificationPermissionRequestSource.FROM_TOGGLE
             _navigationEvent.send(MaxiPaySubscriptionListNavigationEvent.LaunchNotificationsPermissionRequest)
             return@launch
         }
         sendSavePaymentSettingsRequest()
     } catch (exception: Exception) {
         val errorMessage = (exception as? BusinessLogicException)?.message
             ?: exception.localizedMessage
         _messageEvent.send(errorMessage)
         updateMaxiPayClientSettings(currentSettings)
     }
 }
} /**
Обработать событие [event] SSE-соединения
*/
fun onSseEvent(event: MaxiPaySseEvent) {
when (event) {
is MaxiPaySseEvent.SubscriptionStateChange -> handleSubscriptionStateChangeEvent(event)
}
}
private fun handleSubscriptionStateChangeEvent(event: MaxiPaySseEvent.SubscriptionStateChange) {
viewModelScope.launch {
try {
sseUseCase.handleSubscriptionStateChangeEvent(event)
if (event is MaxiPaySseEvent.SubscriptionStateChange.InProgress) {
launchBankChooser(
event.uuid,
event.subscriptionUrl,
requireNotNull(merchantsGroupsState.value.getMerchantNameBySubscriptionUuid(event.uuid)),
)
}
} catch (exception: Exception) {
val errorMessage = (exception as? BusinessLogicException)?.message
?: exception.localizedMessage
_messageEvent.trySend(errorMessage)
}
}
} private fun launchBankChooser(subscriptionUuid: UUID, subscriptionUrl: String, merchantName: String) {
try {
val uri = Uri.parse(subscriptionUrl)
check(uri.isHierarchical && uri.host == "sub.nspk.ru")
_navigationEvent.trySend(
MaxiPaySubscriptionListNavigationEvent.OpenBankChooserScreen(subscriptionUuid, uri, merchantName),
)
} catch (ignored: Exception) {
}
} /**
Обработать результат [result] выбора банка для привязки счёта с идентификатором [subscriptionUuid]
*/
fun onBankChooserResult(subscriptionUuid: UUID, result: SbpBankChooserResult) {
when (result) {
SbpBankChooserResult.ChooserDismissed -> {}
is SbpBankChooserResult.OpenedInBankApp -> onBankChooserSelectedBankApp(subscriptionUuid, result.appInfo)
}
}
/**
Обработать клик по редактированию мерчанта с кодом [merchantCode]
*/
fun onEditMerchantClick(merchantCode: String) {
val merchant = merchantsGroupsState.value
.flatMap { it.merchants }
.find { it.merchant.code == merchantCode }
?.merchant
?: return
val analyticAction = if (merchant.isMain) {
AnalyticMaxiPayActions.PRODUCT_PAY_BUTTON_CHANGE
} else {
AnalyticMaxiPayActions.ALKO_PAY_BUTTON_CHANGE
} analyticsUseCase log AnalyticsGeneralEvent.SelectContent(
analyticType = AnalyticType.MAXI_PAY,
analyticAction = analyticAction,
) viewModelScope.launch {
_navigationEvent.trySend(
MaxiPaySubscriptionListNavigationEvent.OpenEditMerchantSubscriptionsScreen(merchantCode),
)
}
} /**
Обработать клик по элементу [item] дополнительной информации
*/
fun onAdditionalInfoItemClick(item: MaxiPayAdditionalInfo) {
when (item) {
is MaxiPayAdditionalInfo.Faq -> _navigationEvent.trySend(
MaxiPaySubscriptionListNavigationEvent.OpenFaqScreen,
)

is MaxiPayAdditionalInfo.AboutService -> {
Log.d("TESTING", "MaxiPaySubscriptionsListViewModel onAddititionalInfoItemClick AboutService")
analyticsUseCase log AnalyticsGeneralEvent.SelectContent(
analyticType = AnalyticType.MAXI_PAY,
analyticAction = AnalyticMaxiPayActions.ABOUT_SERVICE_BUTTON_CLICK
)
_navigationEvent.trySend(
MaxiPaySubscriptionListNavigationEvent.OpenAboutServiceScreen
)
}

is MaxiPayAdditionalInfo.FaqAndAboutService -> {}
}
} private fun onBankChooserSelectedBankApp(subscriptionUuid: UUID, bankData: BankAppInfo) {
viewModelScope.launch {
try {
updateSubscriptionBankData(subscriptionUuid, bankData)
} catch (ignored: Exception) {
}
}
} override fun onCleared() {
super.onCleared()
disconnectFromSseConnectionIfAllowed()
} private fun connectToSseConnectionIfAllowed() {
if (hasActiveSseConnection) return
hasActiveSseConnection = true
sseUseCase.connectToSse(MaxiPaySseUseCase.SUBSCRIPTIONS_LIST_SCREEN_SSE_CLIENT_TAG)
} private fun disconnectFromSseConnectionIfAllowed() {
if (!hasActiveSseConnection) return
hasActiveSseConnection = false
sseUseCase.disconnectFromSse(MaxiPaySseUseCase.SUBSCRIPTIONS_LIST_SCREEN_SSE_CLIENT_TAG)
} private companion object {
const val SAVE_BUTTON_MESSAGE_TEXT = "Данные сохранены"
}
}

private data class MerchantsGroup(
val title: String,
val merchants: List,
)

private fun MerchantsGroup.toViewItems(forceCreateButtonHidden: Boolean): List =
buildList {
add(titleViewItem)
addAll(merchants.flatMap { it.toViewItems(forceCreateButtonHidden) })
}

private val MerchantsGroup.titleViewItem: MaxiPaySubscriptionsListViewItem.Title
get() = MaxiPaySubscriptionsListViewItem.Title(
text = title,
)

private fun MaxiPayMerchantWithSubscriptions.toViewItems(
forceCreateButtonHidden: Boolean,
): List = buildList {
add(headerViewItem)
addAll(subscriptions.map { it.toViewItem(isSelected = it.uuid == selectedSubscriptionUuid) })
add(getFooterViewItem(forceCreateButtonHidden))
}

private val MaxiPayMerchantWithSubscriptions.headerViewItem: MaxiPaySubscriptionsListViewItem.MerchantHeader
get() = MaxiPaySubscriptionsListViewItem.MerchantHeader(
merchantCode = merchant.code,
merchantName = merchant.name,
editButtonVisible = subscriptions.isNotEmpty(),
)

private fun MaxiPayMerchantWithSubscriptions.getFooterViewItem(
forceCreateButtonHidden: Boolean,
) = MaxiPaySubscriptionsListViewItem.MerchantFooter(
merchantCode = merchant.code,
createButtonVisible = !forceCreateButtonHidden &&
subscriptions.all { it.status == MaxiPaySubscription.Status.CONFIRMED },
)

private fun MaxiPaySubscription.toViewItem(isSelected: Boolean) = MaxiPaySubscriptionsListViewItem.Subscription(
uuid = uuid,
name = name,
status = status,
iconUrl = iconUrl,
hasSubscriptionUrl = subscriptionUrl != null,
selected = isSelected,
)