1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.compose.animation.scene.demo
18
19 import androidx.compose.foundation.background
20 import androidx.compose.foundation.layout.Box
21 import androidx.compose.foundation.layout.fillMaxWidth
22 import androidx.compose.foundation.layout.height
23 import androidx.compose.foundation.layout.padding
24 import androidx.compose.foundation.shape.RoundedCornerShape
25 import androidx.compose.material.icons.Icons
26 import androidx.compose.material.icons.filled.Pause
27 import androidx.compose.material.icons.filled.PlayArrow
28 import androidx.compose.material3.FilledIconButton
29 import androidx.compose.material3.Icon
30 import androidx.compose.material3.IconButtonDefaults
31 import androidx.compose.material3.MaterialTheme
32 import androidx.compose.runtime.Composable
33 import androidx.compose.ui.Alignment
34 import androidx.compose.ui.Modifier
35 import androidx.compose.ui.unit.dp
36 import com.android.compose.animation.scene.ContentKey
37 import com.android.compose.animation.scene.ContentScope
38 import com.android.compose.animation.scene.ElementKey
39 import com.android.compose.animation.scene.MovableElementContentPicker
40 import com.android.compose.animation.scene.MovableElementKey
41 import com.android.compose.animation.scene.StaticElementContentPicker
42 import com.android.compose.animation.scene.content.state.TransitionState
43
44 object MediaPlayer {
45 object Elements {
46 val MediaPlayer = MovableElementKey("MediaPlayer", contentPicker = ContentPicker)
47 val SmallMediaPlayer =
48 MovableElementKey(
49 "SmallMediaPlayer",
50 contentPicker = MovableElementContentPicker(setOf(Overlays.QuickSettings)),
51 )
52 }
53
54 object Dimensions {
55 val HeightLarge = 150.dp
56 val HeightSmall = 70.dp
57 }
58
59 object Shapes {
60 val Background = RoundedCornerShape(24.dp)
61 }
62
63 object ContentPicker : StaticElementContentPicker {
64 override val contents =
65 setOf(
66 Scenes.Lockscreen,
67 Scenes.SplitLockscreen,
68 Scenes.Shade,
69 Scenes.SplitShade,
70 Scenes.QuickSettings,
71 Overlays.Notifications,
72 )
73
contentDuringTransitionnull74 override fun contentDuringTransition(
75 element: ElementKey,
76 transition: TransitionState.Transition,
77 fromContentZIndex: Long,
78 toContentZIndex: Long,
79 ): ContentKey {
80 return when {
81 // During the Lockscreen => Shade transition, the media player is visible in the
82 // Lockscreen when progress is in [0; 0.5]. It is then visible in the Shade scene
83 // when progress is in [0.8; 1]. We move it half-way through, when progress = 0.65.
84 transition.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Shade) -> {
85 if (transition.progress < 0.65f) {
86 Scenes.Lockscreen
87 } else {
88 Scenes.Shade
89 }
90 }
91
92 // Same as Lockscreen => Shade, but with reversed progress.
93 transition.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen) -> {
94 if (transition.progress < 1f - 0.65f) {
95 Scenes.Shade
96 } else {
97 Scenes.Lockscreen
98 }
99 }
100
101 // When going from QS <=> Shade we always want to compose the media player in the QS
102 // scene, otherwise it will be drawn above the QS footer actions.
103 transition.isTransitioningBetween(Scenes.QuickSettings, Scenes.Shade) -> {
104 Scenes.QuickSettings
105 }
106
107 // When going from SplitLockscreen to SplitShade, we always compose the media player
108 // in the SplitShade, otherwise the shade will fade above it.
109 transition.isTransitioningBetween(Scenes.SplitLockscreen, Scenes.SplitShade) ->
110 Scenes.SplitShade
111 transition.isTransitioningBetween(Scenes.Lockscreen, Scenes.QuickSettings) ->
112 Scenes.QuickSettings
113 transition.isTransitioningFromOrTo(Overlays.Notifications) -> Overlays.Notifications
114 else -> pickSingleContentIn(contents, transition, element)
115 }
116 }
117 }
118 }
119
120 @Composable
ContentScopenull121 fun ContentScope.MediaPlayer(
122 presentationStyle: DemoMediaPresentationStyle,
123 isPlaying: Boolean,
124 onIsPlayingChange: (Boolean) -> Unit,
125 onVisibilityChange: (Boolean) -> Unit,
126 modifier: Modifier = Modifier,
127 ) {
128 val injectedContentOrNull = LocalDependencies.current.mediaPlayer
129 if (injectedContentOrNull != null) {
130 Element(
131 key =
132 if (presentationStyle != DemoMediaPresentationStyle.Compact) {
133 MediaPlayer.Elements.MediaPlayer
134 } else {
135 MediaPlayer.Elements.SmallMediaPlayer
136 },
137 modifier = modifier.fillMaxWidth(),
138 ) {
139 injectedContentOrNull(presentationStyle, onVisibilityChange)
140 }
141 } else {
142 val isSmall = presentationStyle == DemoMediaPresentationStyle.Compact
143 val key =
144 if (isSmall) {
145 MediaPlayer.Elements.SmallMediaPlayer
146 } else {
147 MediaPlayer.Elements.MediaPlayer
148 }
149
150 MovableElement(
151 key,
152 modifier
153 .fillMaxWidth()
154 .height(
155 if (isSmall) MediaPlayer.Dimensions.HeightSmall
156 else MediaPlayer.Dimensions.HeightLarge
157 ),
158 ) {
159 content {
160 Box(
161 Modifier.background(
162 MaterialTheme.colorScheme.tertiary,
163 MediaPlayer.Shapes.Background,
164 )
165 .padding(8.dp)
166 ) {
167 FilledIconButton(
168 onClick = { onIsPlayingChange(!isPlaying) },
169 Modifier.align(Alignment.CenterEnd),
170 colors =
171 IconButtonDefaults.filledIconButtonColors(
172 MaterialTheme.colorScheme.onTertiary
173 ),
174 ) {
175 val color = MaterialTheme.colorScheme.tertiary
176 if (isPlaying) {
177 Icon(Icons.Default.Pause, null, tint = color)
178 } else {
179 Icon(Icons.Default.PlayArrow, null, tint = color)
180 }
181 }
182 }
183 }
184 }
185 }
186 }
187
188 enum class DemoMediaPresentationStyle {
189 Default,
190 Compressed,
191 Compact,
192 }
193