Loading packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/DismissibleHorizontalPager.kt +2 −1 Original line number Original line Diff line number Diff line Loading @@ -82,6 +82,7 @@ fun DismissibleHorizontalPager( modifier: Modifier = Modifier, modifier: Modifier = Modifier, key: ((Int) -> Any)? = null, key: ((Int) -> Any)? = null, pageSpacing: Dp = 0.dp, pageSpacing: Dp = 0.dp, isFalseTouchDetected: Boolean, indicator: @Composable BoxScope.() -> Unit, indicator: @Composable BoxScope.() -> Unit, pageContent: @Composable PagerScope.(page: Int) -> Unit, pageContent: @Composable PagerScope.(page: Int) -> Unit, ) { ) { Loading Loading @@ -142,7 +143,7 @@ fun DismissibleHorizontalPager( Box(modifier = modifier) { Box(modifier = modifier) { HorizontalPager( HorizontalPager( state = state.pagerState, state = state.pagerState, userScrollEnabled = state.isScrollingEnabled, userScrollEnabled = state.isScrollingEnabled && !isFalseTouchDetected, key = key, key = key, pageSpacing = pageSpacing, pageSpacing = pageSpacing, pageContent = pageContent, pageContent = pageContent, Loading packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt +23 −2 Original line number Original line Diff line number Diff line Loading @@ -36,6 +36,8 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.hoverable import androidx.compose.foundation.hoverable import androidx.compose.foundation.indication import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.DragInteraction import androidx.compose.foundation.interaction.DragInteraction Loading Loading @@ -72,7 +74,9 @@ import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip Loading @@ -89,6 +93,7 @@ import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.clipRect import androidx.compose.ui.graphics.drawscope.clipRect import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Layout Loading Loading @@ -202,6 +207,8 @@ private fun CardCarouselContent( ) { ) { viewModel.cards.size viewModel.cards.size } } var isFalseTouchDetected: Boolean by remember(behavior.isCarouselScrollFalseTouch) { mutableStateOf(false) } val roundedCornerShape = RoundedCornerShape(32.dp) val roundedCornerShape = RoundedCornerShape(32.dp) Loading Loading @@ -229,7 +236,16 @@ private fun CardCarouselContent( ) ) } } }, }, modifier = modifier.padding(8.dp).clip(roundedCornerShape), isFalseTouchDetected = isFalseTouchDetected, modifier = modifier.padding(8.dp).clip(roundedCornerShape).pointerInput(behavior) { if (behavior.isCarouselScrollFalseTouch != null) { awaitEachGesture { awaitFirstDown(false, PointerEventPass.Initial) isFalseTouchDetected = behavior.isCarouselScrollFalseTouch.invoke() } } }, ) { index -> ) { index -> Card( Card( viewModel = viewModel.cards[index], viewModel = viewModel.cards[index], Loading Loading @@ -1084,7 +1100,12 @@ data class MediaUiBehavior( val isCarouselDismissible: Boolean = true, val isCarouselDismissible: Boolean = true, val isCarouselScrollingEnabled: Boolean = true, val isCarouselScrollingEnabled: Boolean = true, val carouselVisibility: MediaCarouselVisibility = MediaCarouselVisibility.WhenNotEmpty, val carouselVisibility: MediaCarouselVisibility = MediaCarouselVisibility.WhenNotEmpty, val isFalsingProtectionNeeded: Boolean = false, /** * If provided, this callback will be consulted at the beginning of each carousel scroll gesture * to see if the falsing system thinks that it's a false touch. If it then returns `true`, the * scroll will be canceled. */ val isCarouselScrollFalseTouch: (() -> Boolean)? = null, ) ) @Stable @Stable Loading packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt +72 −14 Original line number Original line Diff line number Diff line Loading @@ -24,6 +24,8 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap import com.android.systemui.classifier.Classifier import com.android.systemui.classifier.domain.interactor.runIfNotFalseTap import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Icon import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.ExclusiveActivatable Loading @@ -31,6 +33,7 @@ import com.android.systemui.media.remedia.domain.interactor.MediaInteractor import com.android.systemui.media.remedia.domain.model.MediaActionModel import com.android.systemui.media.remedia.domain.model.MediaActionModel import com.android.systemui.media.remedia.shared.model.MediaColorScheme import com.android.systemui.media.remedia.shared.model.MediaColorScheme import com.android.systemui.media.remedia.shared.model.MediaSessionState import com.android.systemui.media.remedia.shared.model.MediaSessionState import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.res.R import dagger.assisted.Assisted import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory Loading @@ -43,6 +46,7 @@ class MediaViewModel @AssistedInject @AssistedInject constructor( constructor( private val interactor: MediaInteractor, private val interactor: MediaInteractor, private val falsingSystem: FalsingSystem, @Assisted private val context: Context, @Assisted private val context: Context, @Assisted private val carouselVisibility: MediaCarouselVisibility, @Assisted private val carouselVisibility: MediaCarouselVisibility, ) : ExclusiveActivatable() { ) : ExclusiveActivatable() { Loading Loading @@ -106,10 +110,12 @@ constructor( seekProgress = progress seekProgress = progress }, }, onScrubFinished = { onScrubFinished = { if (!falsingSystem.isFalseTouch(Classifier.MEDIA_SEEKBAR)) { interactor.seek( interactor.seek( sessionKey = session.key, sessionKey = session.key, to = (seekProgress * session.durationMs).roundToLong(), to = (seekProgress * session.durationMs).roundToLong(), ) ) } isScrubbing = false isScrubbing = false }, }, ) ) Loading Loading @@ -139,20 +145,36 @@ constructor( R.string.controls_media_dismiss_button R.string.controls_media_dismiss_button ), ), onClick = { onClick = { falsingSystem.runIfNotFalseTap( FalsingManager.LOW_PENALTY ) { interactor.hide(session.key) interactor.hide(session.key) isGutsVisible = false isGutsVisible = false } }, }, ) ) } else { } else { MediaGutsButtonViewModel( MediaGutsButtonViewModel( text = context.getString(R.string.cancel), text = context.getString(R.string.cancel), onClick = { isGutsVisible = false }, onClick = { falsingSystem.runIfNotFalseTap( FalsingManager.LOW_PENALTY ) { isGutsVisible = false } }, ) ) }, }, secondaryAction = secondaryAction = MediaGutsButtonViewModel( MediaGutsButtonViewModel( text = context.getString(R.string.cancel), text = context.getString(R.string.cancel), onClick = { isGutsVisible = false }, onClick = { falsingSystem.runIfNotFalseTap( FalsingManager.LOW_PENALTY ) { isGutsVisible = false } }, ) ) .takeIf { session.canBeHidden }, .takeIf { session.canBeHidden }, settingsButton = settingsButton = Loading @@ -165,7 +187,11 @@ constructor( res = R.string.controls_media_settings_button res = R.string.controls_media_settings_button ), ), ), ), onClick = { interactor.openMediaSettings() }, onClick = { falsingSystem.runIfNotFalseTap(FalsingManager.LOW_PENALTY) { interactor.openMediaSettings() } }, ), ), onLongClick = { isGutsVisible = false }, onLongClick = { isGutsVisible = false }, ) ) Loading @@ -178,7 +204,12 @@ constructor( icon = session.outputDevice.icon, icon = session.outputDevice.icon, text = session.outputDevice.name, text = session.outputDevice.name, onClick = { onClick = { // TODO(b/397989775): tell the UI to show the output switcher. falsingSystem.runIfNotFalseTap( FalsingManager.MODERATE_PENALTY ) { // TODO(b/397989775): tell the UI to show the output // switcher. } }, }, ) ) ) ) Loading @@ -189,12 +220,16 @@ constructor( return MediaSecondaryActionViewModel.Action( return MediaSecondaryActionViewModel.Action( icon = session.outputDevice.icon, icon = session.outputDevice.icon, onClick = { onClick = { falsingSystem.runIfNotFalseTap(FalsingManager.MODERATE_PENALTY) { // TODO(b/397989775): tell the UI to show the output switcher. // TODO(b/397989775): tell the UI to show the output switcher. } }, }, ) ) } } override val onClick = session.onClick override val onClick = { falsingSystem.runIfNotFalseTap(FalsingManager.LOW_PENALTY) { session.onClick() } } override val onClickLabel = override val onClickLabel = context.getString(R.string.controls_media_playing_item_description) context.getString(R.string.controls_media_playing_item_description) override val onLongClick = { isGutsVisible = true } override val onLongClick = { isGutsVisible = true } Loading Loading @@ -230,7 +265,14 @@ constructor( MediaPlayPauseActionViewModel( MediaPlayPauseActionViewModel( state = mediaSessionState, state = mediaSessionState, icon = icon, icon = icon, onClick = onClick ?: {}, onClick = onClick?.let { { falsingSystem.runIfNotFalseTap(FalsingManager.MODERATE_PENALTY) { it() } } }, ) ) is MediaActionModel.None, is MediaActionModel.None, is MediaActionModel.ReserveSpace -> null is MediaActionModel.ReserveSpace -> null Loading @@ -240,12 +282,28 @@ constructor( private fun MediaActionModel.toSecondaryActionViewModel(): MediaSecondaryActionViewModel { private fun MediaActionModel.toSecondaryActionViewModel(): MediaSecondaryActionViewModel { return when (this) { return when (this) { is MediaActionModel.Action -> is MediaActionModel.Action -> MediaSecondaryActionViewModel.Action(icon = icon, onClick = onClick) MediaSecondaryActionViewModel.Action( icon = icon, onClick = onClick?.let { { falsingSystem.runIfNotFalseTap(FalsingManager.MODERATE_PENALTY) { it() } } }, ) is MediaActionModel.ReserveSpace -> MediaSecondaryActionViewModel.ReserveSpace is MediaActionModel.ReserveSpace -> MediaSecondaryActionViewModel.ReserveSpace is MediaActionModel.None -> MediaSecondaryActionViewModel.None is MediaActionModel.None -> MediaSecondaryActionViewModel.None } } } } interface FalsingSystem { fun runIfNotFalseTap(@FalsingManager.Penalty penalty: Int, block: () -> Unit) fun isFalseTouch(@Classifier.InteractionType interactionType: Int): Boolean } @AssistedFactory @AssistedFactory interface Factory { interface Factory { fun create(context: Context, carouselVisibility: MediaCarouselVisibility): MediaViewModel fun create(context: Context, carouselVisibility: MediaCarouselVisibility): MediaViewModel Loading Loading
packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/DismissibleHorizontalPager.kt +2 −1 Original line number Original line Diff line number Diff line Loading @@ -82,6 +82,7 @@ fun DismissibleHorizontalPager( modifier: Modifier = Modifier, modifier: Modifier = Modifier, key: ((Int) -> Any)? = null, key: ((Int) -> Any)? = null, pageSpacing: Dp = 0.dp, pageSpacing: Dp = 0.dp, isFalseTouchDetected: Boolean, indicator: @Composable BoxScope.() -> Unit, indicator: @Composable BoxScope.() -> Unit, pageContent: @Composable PagerScope.(page: Int) -> Unit, pageContent: @Composable PagerScope.(page: Int) -> Unit, ) { ) { Loading Loading @@ -142,7 +143,7 @@ fun DismissibleHorizontalPager( Box(modifier = modifier) { Box(modifier = modifier) { HorizontalPager( HorizontalPager( state = state.pagerState, state = state.pagerState, userScrollEnabled = state.isScrollingEnabled, userScrollEnabled = state.isScrollingEnabled && !isFalseTouchDetected, key = key, key = key, pageSpacing = pageSpacing, pageSpacing = pageSpacing, pageContent = pageContent, pageContent = pageContent, Loading
packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt +23 −2 Original line number Original line Diff line number Diff line Loading @@ -36,6 +36,8 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.hoverable import androidx.compose.foundation.hoverable import androidx.compose.foundation.indication import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.DragInteraction import androidx.compose.foundation.interaction.DragInteraction Loading Loading @@ -72,7 +74,9 @@ import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip Loading @@ -89,6 +93,7 @@ import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.clipRect import androidx.compose.ui.graphics.drawscope.clipRect import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Layout Loading Loading @@ -202,6 +207,8 @@ private fun CardCarouselContent( ) { ) { viewModel.cards.size viewModel.cards.size } } var isFalseTouchDetected: Boolean by remember(behavior.isCarouselScrollFalseTouch) { mutableStateOf(false) } val roundedCornerShape = RoundedCornerShape(32.dp) val roundedCornerShape = RoundedCornerShape(32.dp) Loading Loading @@ -229,7 +236,16 @@ private fun CardCarouselContent( ) ) } } }, }, modifier = modifier.padding(8.dp).clip(roundedCornerShape), isFalseTouchDetected = isFalseTouchDetected, modifier = modifier.padding(8.dp).clip(roundedCornerShape).pointerInput(behavior) { if (behavior.isCarouselScrollFalseTouch != null) { awaitEachGesture { awaitFirstDown(false, PointerEventPass.Initial) isFalseTouchDetected = behavior.isCarouselScrollFalseTouch.invoke() } } }, ) { index -> ) { index -> Card( Card( viewModel = viewModel.cards[index], viewModel = viewModel.cards[index], Loading Loading @@ -1084,7 +1100,12 @@ data class MediaUiBehavior( val isCarouselDismissible: Boolean = true, val isCarouselDismissible: Boolean = true, val isCarouselScrollingEnabled: Boolean = true, val isCarouselScrollingEnabled: Boolean = true, val carouselVisibility: MediaCarouselVisibility = MediaCarouselVisibility.WhenNotEmpty, val carouselVisibility: MediaCarouselVisibility = MediaCarouselVisibility.WhenNotEmpty, val isFalsingProtectionNeeded: Boolean = false, /** * If provided, this callback will be consulted at the beginning of each carousel scroll gesture * to see if the falsing system thinks that it's a false touch. If it then returns `true`, the * scroll will be canceled. */ val isCarouselScrollFalseTouch: (() -> Boolean)? = null, ) ) @Stable @Stable Loading
packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt +72 −14 Original line number Original line Diff line number Diff line Loading @@ -24,6 +24,8 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap import com.android.systemui.classifier.Classifier import com.android.systemui.classifier.domain.interactor.runIfNotFalseTap import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Icon import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.ExclusiveActivatable Loading @@ -31,6 +33,7 @@ import com.android.systemui.media.remedia.domain.interactor.MediaInteractor import com.android.systemui.media.remedia.domain.model.MediaActionModel import com.android.systemui.media.remedia.domain.model.MediaActionModel import com.android.systemui.media.remedia.shared.model.MediaColorScheme import com.android.systemui.media.remedia.shared.model.MediaColorScheme import com.android.systemui.media.remedia.shared.model.MediaSessionState import com.android.systemui.media.remedia.shared.model.MediaSessionState import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.res.R import dagger.assisted.Assisted import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory Loading @@ -43,6 +46,7 @@ class MediaViewModel @AssistedInject @AssistedInject constructor( constructor( private val interactor: MediaInteractor, private val interactor: MediaInteractor, private val falsingSystem: FalsingSystem, @Assisted private val context: Context, @Assisted private val context: Context, @Assisted private val carouselVisibility: MediaCarouselVisibility, @Assisted private val carouselVisibility: MediaCarouselVisibility, ) : ExclusiveActivatable() { ) : ExclusiveActivatable() { Loading Loading @@ -106,10 +110,12 @@ constructor( seekProgress = progress seekProgress = progress }, }, onScrubFinished = { onScrubFinished = { if (!falsingSystem.isFalseTouch(Classifier.MEDIA_SEEKBAR)) { interactor.seek( interactor.seek( sessionKey = session.key, sessionKey = session.key, to = (seekProgress * session.durationMs).roundToLong(), to = (seekProgress * session.durationMs).roundToLong(), ) ) } isScrubbing = false isScrubbing = false }, }, ) ) Loading Loading @@ -139,20 +145,36 @@ constructor( R.string.controls_media_dismiss_button R.string.controls_media_dismiss_button ), ), onClick = { onClick = { falsingSystem.runIfNotFalseTap( FalsingManager.LOW_PENALTY ) { interactor.hide(session.key) interactor.hide(session.key) isGutsVisible = false isGutsVisible = false } }, }, ) ) } else { } else { MediaGutsButtonViewModel( MediaGutsButtonViewModel( text = context.getString(R.string.cancel), text = context.getString(R.string.cancel), onClick = { isGutsVisible = false }, onClick = { falsingSystem.runIfNotFalseTap( FalsingManager.LOW_PENALTY ) { isGutsVisible = false } }, ) ) }, }, secondaryAction = secondaryAction = MediaGutsButtonViewModel( MediaGutsButtonViewModel( text = context.getString(R.string.cancel), text = context.getString(R.string.cancel), onClick = { isGutsVisible = false }, onClick = { falsingSystem.runIfNotFalseTap( FalsingManager.LOW_PENALTY ) { isGutsVisible = false } }, ) ) .takeIf { session.canBeHidden }, .takeIf { session.canBeHidden }, settingsButton = settingsButton = Loading @@ -165,7 +187,11 @@ constructor( res = R.string.controls_media_settings_button res = R.string.controls_media_settings_button ), ), ), ), onClick = { interactor.openMediaSettings() }, onClick = { falsingSystem.runIfNotFalseTap(FalsingManager.LOW_PENALTY) { interactor.openMediaSettings() } }, ), ), onLongClick = { isGutsVisible = false }, onLongClick = { isGutsVisible = false }, ) ) Loading @@ -178,7 +204,12 @@ constructor( icon = session.outputDevice.icon, icon = session.outputDevice.icon, text = session.outputDevice.name, text = session.outputDevice.name, onClick = { onClick = { // TODO(b/397989775): tell the UI to show the output switcher. falsingSystem.runIfNotFalseTap( FalsingManager.MODERATE_PENALTY ) { // TODO(b/397989775): tell the UI to show the output // switcher. } }, }, ) ) ) ) Loading @@ -189,12 +220,16 @@ constructor( return MediaSecondaryActionViewModel.Action( return MediaSecondaryActionViewModel.Action( icon = session.outputDevice.icon, icon = session.outputDevice.icon, onClick = { onClick = { falsingSystem.runIfNotFalseTap(FalsingManager.MODERATE_PENALTY) { // TODO(b/397989775): tell the UI to show the output switcher. // TODO(b/397989775): tell the UI to show the output switcher. } }, }, ) ) } } override val onClick = session.onClick override val onClick = { falsingSystem.runIfNotFalseTap(FalsingManager.LOW_PENALTY) { session.onClick() } } override val onClickLabel = override val onClickLabel = context.getString(R.string.controls_media_playing_item_description) context.getString(R.string.controls_media_playing_item_description) override val onLongClick = { isGutsVisible = true } override val onLongClick = { isGutsVisible = true } Loading Loading @@ -230,7 +265,14 @@ constructor( MediaPlayPauseActionViewModel( MediaPlayPauseActionViewModel( state = mediaSessionState, state = mediaSessionState, icon = icon, icon = icon, onClick = onClick ?: {}, onClick = onClick?.let { { falsingSystem.runIfNotFalseTap(FalsingManager.MODERATE_PENALTY) { it() } } }, ) ) is MediaActionModel.None, is MediaActionModel.None, is MediaActionModel.ReserveSpace -> null is MediaActionModel.ReserveSpace -> null Loading @@ -240,12 +282,28 @@ constructor( private fun MediaActionModel.toSecondaryActionViewModel(): MediaSecondaryActionViewModel { private fun MediaActionModel.toSecondaryActionViewModel(): MediaSecondaryActionViewModel { return when (this) { return when (this) { is MediaActionModel.Action -> is MediaActionModel.Action -> MediaSecondaryActionViewModel.Action(icon = icon, onClick = onClick) MediaSecondaryActionViewModel.Action( icon = icon, onClick = onClick?.let { { falsingSystem.runIfNotFalseTap(FalsingManager.MODERATE_PENALTY) { it() } } }, ) is MediaActionModel.ReserveSpace -> MediaSecondaryActionViewModel.ReserveSpace is MediaActionModel.ReserveSpace -> MediaSecondaryActionViewModel.ReserveSpace is MediaActionModel.None -> MediaSecondaryActionViewModel.None is MediaActionModel.None -> MediaSecondaryActionViewModel.None } } } } interface FalsingSystem { fun runIfNotFalseTap(@FalsingManager.Penalty penalty: Int, block: () -> Unit) fun isFalseTouch(@Classifier.InteractionType interactionType: Int): Boolean } @AssistedFactory @AssistedFactory interface Factory { interface Factory { fun create(context: Context, carouselVisibility: MediaCarouselVisibility): MediaViewModel fun create(context: Context, carouselVisibility: MediaCarouselVisibility): MediaViewModel Loading