@@ -71,6 +71,20 @@ import com.dot.gallery.R
7171import com.dot.gallery.feature_node.domain.model.PlaybackSpeed
7272import com.dot.gallery.feature_node.presentation.util.formatMinSec
7373import kotlinx.coroutines.launch
74+ import androidx.compose.animation.animateColorAsState
75+ import androidx.compose.animation.core.animateIntAsState
76+ import androidx.compose.animation.core.Spring
77+ import androidx.compose.animation.core.spring
78+ import androidx.compose.foundation.interaction.MutableInteractionSource
79+ import androidx.compose.foundation.interaction.collectIsPressedAsState
80+ import androidx.compose.ui.draw.scale
81+ import androidx.compose.ui.draw.clip
82+ import androidx.compose.foundation.shape.RoundedCornerShape
83+ import androidx.compose.foundation.clickable
84+ import android.view.HapticFeedbackConstants
85+ import androidx.compose.animation.core.animateDpAsState
86+ import androidx.compose.ui.platform.LocalView
87+ import kotlinx.coroutines.delay
7488
7589@OptIn(ExperimentalMaterial3Api ::class )
7690@Composable
@@ -166,13 +180,13 @@ fun VideoPlayerController(
166180 )
167181 }
168182 }
169- IconButton (onClick = { showMenu = ! showMenu }) {
170- Icon (
171- imageVector = Icons .Outlined .Speed ,
172- tint = Color .White ,
173- contentDescription = stringResource(R .string.change_playback_speed_cd)
174- )
175- }
183+ // IconButton(onClick = { showMenu = !showMenu }) {
184+ // Icon(
185+ // imageVector = Icons.Outlined.Speed,
186+ // tint = Color.White,
187+ // contentDescription = stringResource(R.string.change_playback_speed_cd)
188+ // )
189+ // }
176190 }
177191
178192 IconButton (
@@ -186,21 +200,22 @@ fun VideoPlayerController(
186200 isMuted = true
187201 }
188202 }
189- ) {
190- Icon (
191- imageVector = if (isMuted) Icons .AutoMirrored .Outlined .VolumeMute else Icons .AutoMirrored .Outlined .VolumeUp ,
192- tint = Color .White ,
193- contentDescription = stringResource(R .string.toggle_audio_cd)
194- )
203+ )
204+ {
205+ // Icon(
206+ // imageVector = if (isMuted) Icons.AutoMirrored.Outlined.VolumeMute else Icons.AutoMirrored.Outlined.VolumeUp,
207+ // tint = Color.White,
208+ // contentDescription = stringResource(R.string.toggle_audio_cd)
209+ // )
195210 }
196211
197- IconButton (onClick = { toggleRotate() }) {
198- Icon (
199- imageVector = Icons .Outlined .ScreenRotation ,
200- tint = Color .White ,
201- contentDescription = stringResource(R .string.rotate_screen_cd)
202- )
203- }
212+ // IconButton(onClick = { toggleRotate() }) {
213+ // Icon(
214+ // imageVector = Icons.Outlined.ScreenRotation,
215+ // tint = Color.White,
216+ // contentDescription = stringResource(R.string.rotate_screen_cd)
217+ // )
218+ // }
204219
205220 // --- Timeline Row (Wavy Scrubber) ---
206221 Row (
@@ -266,36 +281,23 @@ fun VideoPlayerController(
266281 }
267282
268283 // Center Play/Pause button
269- IconButton (
270- onClick = {
271- val newState = ! isPlaying.value
272- isPlaying.value = newState
273- if (newState) {
274- player.playWhenReady = true
275- player.play()
276- } else {
277- player.pause()
278- }
279- },
284+ Box (
280285 modifier = Modifier
281286 .align(Alignment .Center )
282- .size(64 .dp)
283287 ) {
284- if (isPlaying.value && player.isPlaying) {
285- Image (
286- modifier = Modifier .fillMaxSize(),
287- imageVector = Icons .Filled .PauseCircleFilled ,
288- contentDescription = stringResource(R .string.pause_video),
289- colorFilter = ColorFilter .tint(Color .White )
290- )
291- } else {
292- Image (
293- modifier = Modifier .fillMaxSize(),
294- imageVector = Icons .Filled .PlayCircleFilled ,
295- contentDescription = stringResource(R .string.play_video),
296- colorFilter = ColorFilter .tint(Color .White )
297- )
298- }
288+ ExpressivePlayButton (
289+ isPlaying = isPlaying.value && player.isPlaying,
290+ onClick = {
291+ val newState = ! isPlaying.value
292+ isPlaying.value = newState
293+ if (newState) {
294+ player.playWhenReady = true
295+ player.play()
296+ } else {
297+ player.pause()
298+ }
299+ }
300+ )
299301 }
300302 }
301303}
@@ -372,4 +374,91 @@ fun WavyVideoScrubber(
372374 trackColor = Color .White .copy(alpha = 0.3f )
373375 )
374376 }
375- }
377+ }
378+
379+ @Composable
380+ fun ExpressivePlayButton (
381+ isPlaying : Boolean ,
382+ onClick : () -> Unit
383+ ) {
384+ val view = LocalView .current
385+ val scope = rememberCoroutineScope() // Для таймера анімації
386+
387+ val interactionSource = remember { MutableInteractionSource () }
388+ val isPressed by interactionSource.collectIsPressedAsState()
389+
390+ // Додатковий стан для "удару" при швидкому кліку
391+ var isAnimatingClick by remember { mutableStateOf(false ) }
392+
393+ // Анімація ширини
394+ // Логіка: Якщо тиснеш АБО тільки що клікнув -> розширюємось
395+ val width by animateDpAsState(
396+ targetValue = if (isPressed || isAnimatingClick) 115 .dp else 90 .dp,
397+ animationSpec = spring(
398+ dampingRatio = 0.4f , // Пружний відскок
399+ stiffness = Spring .StiffnessMedium
400+ ),
401+ label = " buttonWidth"
402+ )
403+
404+ // Анімація форми (без змін)
405+ val cornerPercent by animateIntAsState(
406+ targetValue = if (isPlaying) 50 else 30 ,
407+ animationSpec = spring(
408+ dampingRatio = Spring .DampingRatioNoBouncy ,
409+ stiffness = Spring .StiffnessLow
410+ ),
411+ label = " buttonShape"
412+ )
413+
414+ // Анімація кольору (без змін)
415+ val containerColor by animateColorAsState(
416+ targetValue = if (isPlaying)
417+ MaterialTheme .colorScheme.primaryContainer.copy(alpha = 0.8f )
418+ else
419+ MaterialTheme .colorScheme.tertiaryContainer.copy(alpha = 0.9f ),
420+ label = " buttonColor"
421+ )
422+
423+ val contentColor by animateColorAsState(
424+ targetValue = if (isPlaying)
425+ MaterialTheme .colorScheme.onPrimaryContainer
426+ else
427+ MaterialTheme .colorScheme.onTertiaryContainer,
428+ label = " iconColor"
429+ )
430+
431+ Box (
432+ modifier = Modifier
433+ .size(width = width, height = 64 .dp)
434+ .clip(RoundedCornerShape (cornerPercent))
435+ .background(containerColor)
436+ .clickable(
437+ interactionSource = interactionSource,
438+ indication = null
439+ ) {
440+ // 1. Хаптик (CONFIRM, як ти хотів)
441+ view.performHapticFeedback(HapticFeedbackConstants .CONFIRM )
442+
443+ // 2. Виконуємо дію
444+ onClick()
445+
446+ // 3. Запускаємо візуальний "поштовх"
447+ scope.launch {
448+ isAnimatingClick = true
449+ // Чекаємо 100мс, щоб пружина встигла візуально розширити кнопку
450+ delay(100 )
451+ view.performHapticFeedback(HapticFeedbackConstants .CONFIRM )
452+ isAnimatingClick = false
453+ }
454+ },
455+ contentAlignment = Alignment .Center
456+ ) {
457+ Icon (
458+ imageVector = if (isPlaying) Icons .Filled .PauseCircleFilled else Icons .Filled .PlayCircleFilled ,
459+ contentDescription = null ,
460+ tint = contentColor,
461+ modifier = Modifier .size(32 .dp)
462+ )
463+ }
464+ }
0 commit comments