Skip to content

Commit

Permalink
refactor(doubleTapSeek): convert view to compose
Browse files Browse the repository at this point in the history
  • Loading branch information
quickdesh committed Dec 13, 2024
1 parent f294ad2 commit 4c91238
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 307 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ class PlayerActivity : BaseActivity() {
val audioManager by lazy { getSystemService(Context.AUDIO_SERVICE) as AudioManager }

private var mediaSession: MediaSession? = null
private val gesturePreferences: GesturePreferences = viewModel.gesturePreferences
private val playerPreferences: PlayerPreferences = viewModel.playerPreferences
private val gesturePreferences: GesturePreferences get() = viewModel.gesturePreferences
private val playerPreferences: PlayerPreferences get() = viewModel.playerPreferences
private val subtitlePreferences: SubtitlePreferences = Injekt.get()
private val audioPreferences: AudioPreferences = Injekt.get()
private val advancedPlayerPreferences: AdvancedPlayerPreferences = Injekt.get()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

package eu.kanade.tachiyomi.ui.player.controls

import android.view.View
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
Expand Down Expand Up @@ -50,7 +49,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.viewinterop.AndroidView
import eu.kanade.presentation.player.components.LeftSideOvalShape
import eu.kanade.presentation.player.components.RightSideOvalShape
import eu.kanade.presentation.theme.playerRippleConfiguration
Expand Down Expand Up @@ -306,22 +304,8 @@ fun DoubleTapToSeekOvals(
.background(Color.White.copy(alpha))
.indication(interactionSource, ripple()),
)
AndroidView(
factory = { DoubleTapSeekSecondsView(it, null) },
update = {
if (text != null) {
it.text = text
it.visibility = View.VISIBLE
it.start()
} else if (amount != 0) {
it.isForward = amount > 0
it.seconds = amount
it.visibility = View.VISIBLE
it.start()
} else {
it.visibility = View.GONE
}
},
DoubleTapSeekSecondsView(
seconds = amount,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,212 +1,83 @@
/*
* Copyright 2024 Abdallah Mehiz
* https://github.com/abdallahmehiz/mpvKt
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package eu.kanade.tachiyomi.ui.player.controls.components

import android.animation.Animator
import android.animation.ValueAnimator
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import androidx.annotation.DrawableRes
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.PlayerDoubleTapSeekViewBinding
import tachiyomi.core.common.i18n.pluralStringResource
import tachiyomi.i18n.MR

/**
* View that shows the arrows animation when double tapping to seek
*/
class DoubleTapSeekSecondsView(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {

var binding: PlayerDoubleTapSeekViewBinding

companion object {
const val ICON_ANIMATION_DURATION = 750L
}

var cycleDuration: Long = ICON_ANIMATION_DURATION
set(value) {
firstAnimator.duration = value / 5
secondAnimator.duration = value / 5
thirdAnimator.duration = value / 5
fourthAnimator.duration = value / 5
fifthAnimator.duration = value / 5
field = value
}

var text: String? = null
set(value) {
binding.doubleTapSeconds.text = value
field = value
}

var seconds: Int = 0
set(value) {
binding.doubleTapSeconds.text = context.pluralStringResource(
MR.plurals.seconds,
value,
value,
)
field = value
import tachiyomi.presentation.core.i18n.pluralStringResource

@Composable
fun DoubleTapSeekSecondsView(
modifier: Modifier = Modifier,
seconds: Int,
) {
val animationDuration = 750L

val alpha1 = remember { Animatable(0f) }
val alpha2 = remember { Animatable(0f) }
val alpha3 = remember { Animatable(0f) }

LaunchedEffect(animationDuration) {
while (true) {
alpha1.animateTo(1f, animationSpec = tween((animationDuration / 5).toInt()))
alpha2.animateTo(1f, animationSpec = tween((animationDuration / 5).toInt()))
alpha3.animateTo(1f, animationSpec = tween((animationDuration / 5).toInt()))
alpha1.animateTo(0f, animationSpec = tween((animationDuration / 5).toInt()))
alpha2.animateTo(0f, animationSpec = tween((animationDuration / 5).toInt()))
alpha3.animateTo(0f, animationSpec = tween((animationDuration / 5).toInt()))
}

var isForward: Boolean = true
set(value) {
binding.triangleContainer.rotation = if (value) 0f else 180f
field = value
}

@DrawableRes
var icon: Int = R.drawable.ic_play_seek_triangle
set(value) {
if (value > 0) {
binding.tri1.setImageResource(value)
binding.tri2.setImageResource(value)
binding.tri3.setImageResource(value)
}
field = value
}

init {
binding = PlayerDoubleTapSeekViewBinding.inflate(LayoutInflater.from(context), this)
orientation = VERTICAL
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
}

fun start() {
stop()
firstAnimator.start()
}

fun stop() {
firstAnimator.cancel()
secondAnimator.cancel()
thirdAnimator.cancel()
fourthAnimator.cancel()
fifthAnimator.cancel()

reset()
}
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
val rotation = if (seconds > 0) 0f else 180f
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.rotate(rotation)
) {
DoubleTapArrow(alpha1.value)
DoubleTapArrow(alpha2.value)
DoubleTapArrow(alpha3.value)
}

private fun reset() {
binding.tri1.alpha = 0f
binding.tri2.alpha = 0f
binding.tri3.alpha = 0f
Text(
text = pluralStringResource(MR.plurals.seconds, seconds, seconds),
fontSize = 12.sp,
textAlign = TextAlign.Center,
color = Color.White
)
}
}

private val firstAnimator: ValueAnimator = CustomValueAnimator(
{
binding.tri1.alpha = 0f
binding.tri2.alpha = 0f
binding.tri3.alpha = 0f
},
{
binding.tri1.alpha = it
},
{
secondAnimator.start()
},
)

private val secondAnimator: ValueAnimator = CustomValueAnimator(
{
binding.tri1.alpha = 1f
binding.tri2.alpha = 0f
binding.tri3.alpha = 0f
},
{
binding.tri2.alpha = it
},
{
thirdAnimator.start()
},
)

private val thirdAnimator: ValueAnimator = CustomValueAnimator(
{
binding.tri1.alpha = 1f
binding.tri2.alpha = 1f
binding.tri3.alpha = 0f
},
{
binding.tri1.alpha = 1f - binding.tri3.alpha
binding.tri3.alpha = it
},
{
fourthAnimator.start()
},
)

private val fourthAnimator: ValueAnimator = CustomValueAnimator(
{
binding.tri1.alpha = 0f
binding.tri2.alpha = 1f
binding.tri3.alpha = 1f
},
{
binding.tri2.alpha = 1f - it
},
{
fifthAnimator.start()
},
)

private val fifthAnimator: ValueAnimator = CustomValueAnimator(
{
binding.tri1.alpha = 0f
binding.tri2.alpha = 0f
binding.tri3.alpha = 1f
},
{
binding.tri3.alpha = 1f - it
},
{
firstAnimator.start()
},
@Composable
private fun DoubleTapArrow(
alpha: Float,
) {
Icon(
painter = painterResource(R.drawable.ic_play_seek_triangle),
contentDescription = null,
modifier = Modifier
.size(width = 16.dp, height = 20.dp)
.alpha(alpha = alpha),
tint = Color.White
)

private inner class CustomValueAnimator(
start: () -> Unit,
update: (value: Float) -> Unit,
end: () -> Unit,
) : ValueAnimator() {

init {
duration = cycleDuration / 5
setFloatValues(0f, 1f)

addUpdateListener { update(it.animatedValue as Float) }
addListener(
object : AnimatorListener {
override fun onAnimationStart(animation: Animator) {
start()
}

override fun onAnimationEnd(animation: Animator) {
end()
}

override fun onAnimationCancel(animation: Animator) = Unit

override fun onAnimationRepeat(animation: Animator) = Unit
},
)
}
}
}
10 changes: 0 additions & 10 deletions app/src/main/res/drawable/ic_pause_64dp.xml

This file was deleted.

8 changes: 0 additions & 8 deletions app/src/main/res/drawable/ic_play_pause_bg.xml

This file was deleted.

Loading

0 comments on commit 4c91238

Please sign in to comment.