Loading packages/SystemUI/src/com/android/systemui/media/remedia/data/repository/MediaRepository.kt +31 −0 Original line number Diff line number Diff line Loading @@ -24,7 +24,11 @@ import android.media.MediaMetadata import android.media.session.MediaController import android.media.session.PlaybackState import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.graphics.Color import com.android.internal.logging.InstanceId Loading Loading @@ -59,11 +63,22 @@ interface MediaRepository { /** Current sorted media sessions. */ val currentMedia: List<MediaDataModel> /** Index of the current visible media session */ val currentCarouselIndex: Int /** Whether media carousel should show first media session. */ val shouldScrollToFirst: Boolean /** Seek to [to], in milliseconds on the media session with the given [sessionKey]. */ fun seek(sessionKey: InstanceId, to: Long) /** Reorders media list when media is not visible to user */ fun reorderMedia() fun storeCarouselIndex(index: Int) /** Resets [shouldScrollToFirst] flag. */ fun resetScrollToFirst() } @SysUISingleton Loading @@ -78,6 +93,10 @@ constructor( override val currentMedia: SnapshotStateList<MediaDataModel> = mutableStateListOf() override var currentCarouselIndex by mutableIntStateOf(0) override var shouldScrollToFirst by mutableStateOf(false) private var sortedMedia = TreeMap<MediaSortKeyModel, MediaDataModel>(comparator) // To store active controllers and their callbacks Loading Loading @@ -124,6 +143,15 @@ constructor( override fun reorderMedia() { currentMedia.clear() currentMedia.addAll(sortedMedia.values.toList()) currentCarouselIndex = 0 } override fun storeCarouselIndex(index: Int) { currentCarouselIndex = index } override fun resetScrollToFirst() { shouldScrollToFirst = false } private fun addToSortedMedia(data: MediaData, updateModel: UpdateArtInfoModel?) { Loading Loading @@ -175,6 +203,9 @@ constructor( } currentMedia.clear() if (isNewToCurrentMedia && active) { // New media added is at the top of the current media given its priority. // Media carousel should show the first card in the current media list. shouldScrollToFirst = true currentMedia.addAll(sortedMap.values.toList()) } else { currentMedia.addAll(currentList) Loading packages/SystemUI/src/com/android/systemui/media/remedia/domain/interactor/MediaInteractor.kt +24 −0 Original line number Diff line number Diff line Loading @@ -64,6 +64,12 @@ interface MediaInteractor { /** The list of sessions. Needs to be backed by a compose snapshot state. */ val sessions: List<MediaSessionModel> /** Index of the current visible media session */ val currentCarouselIndex: Int /** Whether media carousel should show first media session. */ val shouldScrollToFirst: Boolean /** Seek to [to], in milliseconds on the media session with the given [sessionKey]. */ fun seek(sessionKey: Any, to: Long) Loading @@ -74,6 +80,10 @@ interface MediaInteractor { fun openMediaSettings() fun reorderMedia() fun storeCurrentCarouselIndex(index: Int) fun resetScrollToFirst() } @SysUISingleton Loading @@ -93,6 +103,12 @@ constructor( override val sessions: List<MediaSessionModel> get() = repository.currentMedia.map { toMediaSessionModel(it) } override val currentCarouselIndex: Int get() = repository.currentCarouselIndex override val shouldScrollToFirst: Boolean get() = repository.shouldScrollToFirst override fun seek(sessionKey: Any, to: Long) { repository.seek(sessionKey as InstanceId, to) } Loading @@ -109,6 +125,14 @@ constructor( repository.reorderMedia() } override fun storeCurrentCarouselIndex(index: Int) { repository.storeCarouselIndex(index) } override fun resetScrollToFirst() { repository.resetScrollToFirst() } private fun toMediaSessionModel(dataModel: MediaDataModel): MediaSessionModel { return object : MediaSessionModel { override val key Loading packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt +12 −1 Original line number Diff line number Diff line Loading @@ -229,8 +229,12 @@ private fun CardCarouselContent( modifier: Modifier = Modifier, ) { val pagerState = rememberPagerState { viewModel.cards.size } LaunchedEffect(viewModel.currentIndex) { if (viewModel.currentIndex != pagerState.currentPage) { pagerState.scrollToPage(viewModel.currentIndex) } } LaunchedEffect(pagerState.currentPage) { viewModel.onCardSelected(pagerState.currentPage) } var isFalseTouchDetected: Boolean by remember(behavior.isCarouselScrollFalseTouch) { mutableStateOf(false) } val isSwipingEnabled = behavior.isCarouselScrollingEnabled && !isFalseTouchDetected Loading Loading @@ -308,6 +312,13 @@ private fun CardCarouselContent( ) } } LaunchedEffect(viewModel.scrollToFirst) { if (viewModel.scrollToFirst && viewModel.cards.isNotEmpty()) { pagerState.animateScrollToPage(0) viewModel.onScrollToFirstCard() } } } /** Renders the UI of a single media card. */ Loading packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt +12 −0 Original line number Diff line number Diff line Loading @@ -69,6 +69,12 @@ constructor( private var selectedCardIndex: Int by mutableIntStateOf(0) private set /** The index of the currently visible card across different locations of media carousel */ val currentIndex: Int by derivedStateOf { interactor.currentCarouselIndex } /** Whether media carousel should scroll to the first card in the list after composition */ val scrollToFirst: Boolean by derivedStateOf { interactor.shouldScrollToFirst } /** The current list of cards to show in the UI. */ val cards: List<MediaCardViewModel> by derivedStateOf { interactor.sessions.mapIndexed { sessionIndex, session -> Loading Loading @@ -307,6 +313,12 @@ constructor( fun onCardSelected(cardIndex: Int) { check(cardIndex >= 0 && cardIndex < cards.size) selectedCardIndex = cardIndex interactor.storeCurrentCarouselIndex(selectedCardIndex) } /** Notifies that the carousel is reordered and first card is now visible on screen. */ fun onScrollToFirstCard() { interactor.resetScrollToFirst() } override suspend fun onActivated(): Nothing { Loading Loading
packages/SystemUI/src/com/android/systemui/media/remedia/data/repository/MediaRepository.kt +31 −0 Original line number Diff line number Diff line Loading @@ -24,7 +24,11 @@ import android.media.MediaMetadata import android.media.session.MediaController import android.media.session.PlaybackState import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.graphics.Color import com.android.internal.logging.InstanceId Loading Loading @@ -59,11 +63,22 @@ interface MediaRepository { /** Current sorted media sessions. */ val currentMedia: List<MediaDataModel> /** Index of the current visible media session */ val currentCarouselIndex: Int /** Whether media carousel should show first media session. */ val shouldScrollToFirst: Boolean /** Seek to [to], in milliseconds on the media session with the given [sessionKey]. */ fun seek(sessionKey: InstanceId, to: Long) /** Reorders media list when media is not visible to user */ fun reorderMedia() fun storeCarouselIndex(index: Int) /** Resets [shouldScrollToFirst] flag. */ fun resetScrollToFirst() } @SysUISingleton Loading @@ -78,6 +93,10 @@ constructor( override val currentMedia: SnapshotStateList<MediaDataModel> = mutableStateListOf() override var currentCarouselIndex by mutableIntStateOf(0) override var shouldScrollToFirst by mutableStateOf(false) private var sortedMedia = TreeMap<MediaSortKeyModel, MediaDataModel>(comparator) // To store active controllers and their callbacks Loading Loading @@ -124,6 +143,15 @@ constructor( override fun reorderMedia() { currentMedia.clear() currentMedia.addAll(sortedMedia.values.toList()) currentCarouselIndex = 0 } override fun storeCarouselIndex(index: Int) { currentCarouselIndex = index } override fun resetScrollToFirst() { shouldScrollToFirst = false } private fun addToSortedMedia(data: MediaData, updateModel: UpdateArtInfoModel?) { Loading Loading @@ -175,6 +203,9 @@ constructor( } currentMedia.clear() if (isNewToCurrentMedia && active) { // New media added is at the top of the current media given its priority. // Media carousel should show the first card in the current media list. shouldScrollToFirst = true currentMedia.addAll(sortedMap.values.toList()) } else { currentMedia.addAll(currentList) Loading
packages/SystemUI/src/com/android/systemui/media/remedia/domain/interactor/MediaInteractor.kt +24 −0 Original line number Diff line number Diff line Loading @@ -64,6 +64,12 @@ interface MediaInteractor { /** The list of sessions. Needs to be backed by a compose snapshot state. */ val sessions: List<MediaSessionModel> /** Index of the current visible media session */ val currentCarouselIndex: Int /** Whether media carousel should show first media session. */ val shouldScrollToFirst: Boolean /** Seek to [to], in milliseconds on the media session with the given [sessionKey]. */ fun seek(sessionKey: Any, to: Long) Loading @@ -74,6 +80,10 @@ interface MediaInteractor { fun openMediaSettings() fun reorderMedia() fun storeCurrentCarouselIndex(index: Int) fun resetScrollToFirst() } @SysUISingleton Loading @@ -93,6 +103,12 @@ constructor( override val sessions: List<MediaSessionModel> get() = repository.currentMedia.map { toMediaSessionModel(it) } override val currentCarouselIndex: Int get() = repository.currentCarouselIndex override val shouldScrollToFirst: Boolean get() = repository.shouldScrollToFirst override fun seek(sessionKey: Any, to: Long) { repository.seek(sessionKey as InstanceId, to) } Loading @@ -109,6 +125,14 @@ constructor( repository.reorderMedia() } override fun storeCurrentCarouselIndex(index: Int) { repository.storeCarouselIndex(index) } override fun resetScrollToFirst() { repository.resetScrollToFirst() } private fun toMediaSessionModel(dataModel: MediaDataModel): MediaSessionModel { return object : MediaSessionModel { override val key Loading
packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt +12 −1 Original line number Diff line number Diff line Loading @@ -229,8 +229,12 @@ private fun CardCarouselContent( modifier: Modifier = Modifier, ) { val pagerState = rememberPagerState { viewModel.cards.size } LaunchedEffect(viewModel.currentIndex) { if (viewModel.currentIndex != pagerState.currentPage) { pagerState.scrollToPage(viewModel.currentIndex) } } LaunchedEffect(pagerState.currentPage) { viewModel.onCardSelected(pagerState.currentPage) } var isFalseTouchDetected: Boolean by remember(behavior.isCarouselScrollFalseTouch) { mutableStateOf(false) } val isSwipingEnabled = behavior.isCarouselScrollingEnabled && !isFalseTouchDetected Loading Loading @@ -308,6 +312,13 @@ private fun CardCarouselContent( ) } } LaunchedEffect(viewModel.scrollToFirst) { if (viewModel.scrollToFirst && viewModel.cards.isNotEmpty()) { pagerState.animateScrollToPage(0) viewModel.onScrollToFirstCard() } } } /** Renders the UI of a single media card. */ Loading
packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt +12 −0 Original line number Diff line number Diff line Loading @@ -69,6 +69,12 @@ constructor( private var selectedCardIndex: Int by mutableIntStateOf(0) private set /** The index of the currently visible card across different locations of media carousel */ val currentIndex: Int by derivedStateOf { interactor.currentCarouselIndex } /** Whether media carousel should scroll to the first card in the list after composition */ val scrollToFirst: Boolean by derivedStateOf { interactor.shouldScrollToFirst } /** The current list of cards to show in the UI. */ val cards: List<MediaCardViewModel> by derivedStateOf { interactor.sessions.mapIndexed { sessionIndex, session -> Loading Loading @@ -307,6 +313,12 @@ constructor( fun onCardSelected(cardIndex: Int) { check(cardIndex >= 0 && cardIndex < cards.size) selectedCardIndex = cardIndex interactor.storeCurrentCarouselIndex(selectedCardIndex) } /** Notifies that the carousel is reordered and first card is now visible on screen. */ fun onScrollToFirstCard() { interactor.resetScrollToFirst() } override suspend fun onActivated(): Nothing { Loading