Coverage Summary for Class: BpmWheelKt (com.vsevolodganin.clicktrack.ui.piece)

Class Method, % Branch, % Line, % Instruction, %
BpmWheelKt 0% (0/13) 0% (0/24) 0% (0/71) 0% (0/972)
BpmWheelKt$Wheel$1$1$1 0% (0/2) 0% (0/4) 0% (0/41)
Total 0% (0/15) 0% (0/24) 0% (0/75) 0% (0/1013)


 package com.vsevolodganin.clicktrack.ui.piece
 
 import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.layout.BoxWithConstraints
 import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.material.MaterialTheme
 import androidx.compose.material.Surface
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.SolidColor
 import androidx.compose.ui.graphics.drawscope.Stroke
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.dp
 import com.vsevolodganin.clicktrack.model.BeatsPerMinute
 import com.vsevolodganin.clicktrack.model.BeatsPerMinuteOffset
 import com.vsevolodganin.clicktrack.model.bpm
 import com.vsevolodganin.clicktrack.utils.compose.Preview
 import com.vsevolodganin.clicktrack.utils.compose.detectRadialDragGesture
 import com.vsevolodganin.clicktrack.utils.compose.toRadians
 import kotlin.math.cos
 import kotlin.math.roundToInt
 import kotlin.math.sin
 
 @Composable
 fun BpmWheel(state: MutableState<BeatsPerMinute>, modifier: Modifier = Modifier, sensitivity: Float = 0.08f) {
     BpmWheel(
         value = state.value,
         onValueChange = { state.value = state.value + it },
         modifier = modifier,
         sensitivity = sensitivity,
     )
 }
 
 @Composable
 fun BpmWheel(
     value: BeatsPerMinute,
     onValueChange: (BeatsPerMinuteOffset) -> Unit,
     modifier: Modifier = Modifier,
     sensitivity: Float = 0.08f,
 ) {
     var floatState by remember { mutableStateOf(value.value.toFloat()) }
 
     Wheel(
         onAngleChange = {
             val floatChange = it * sensitivity
             val newFloatState = floatState + floatChange
             val intChange = newFloatState.roundToInt() - floatState.roundToInt()
             floatState = newFloatState
             if (intChange != 0) {
                 onValueChange(BeatsPerMinuteOffset(intChange))
             }
         },
         modifier = modifier.aspectRatio(1f),
     )
 }
 
 @Composable
 private fun Wheel(onAngleChange: (diff: Float) -> Unit, modifier: Modifier = Modifier) {
     var buttonAngle by remember { mutableStateOf(90f) }
 
     BoxWithConstraints(modifier) {
         val density = LocalDensity.current
         val width = minWidth
         val height = minHeight
         val widthPx = with(density) { width.toPx() }
         val heightPx = with(density) { height.toPx() }
         val localOnAngleChange by rememberUpdatedState(onAngleChange)
 
         Surface(
             modifier = Modifier
                 .fillMaxSize()
                 .pointerInput(widthPx, heightPx) {
                     val center = Offset(x = widthPx / 2, y = heightPx / 2)
                     detectRadialDragGesture(center) { angleDiff ->
                         buttonAngle -= angleDiff
                         localOnAngleChange(angleDiff)
                     }
                 },
             shape = CircleShape,
             color = Color.Transparent,
         ) {
             val wheelWidth = width * WHEEL_WIDTH_MULTIPLIER
             val wheelWidthPx = with(density) { wheelWidth.toPx() }
 
             val wheelColor = MaterialTheme.colors.secondary
             val controllerButtonColor = MaterialTheme.colors.onSecondary
 
             Canvas(
                 modifier = Modifier
                     .fillMaxSize()
                     .padding(wheelWidth / 2),
             ) {
                 if (size.maxDimension <= 0) return@Canvas
 
                 drawArc(
                     brush = SolidColor(wheelColor),
                     startAngle = 0f,
                     sweepAngle = 360f,
                     useCenter = false,
                     style = Stroke(
                         width = wheelWidthPx,
                     ),
                 )
 
                 val buttonSize = Size(wheelWidthPx, wheelWidthPx) * 0.3f
                 val buttonOffset = Offset(
                     x = (cos(buttonAngle.toRadians()) + 1) * center.x - buttonSize.width / 2,
                     y = (-sin(buttonAngle.toRadians()) + 1) * center.y - buttonSize.height / 2,
                 )
 
                 drawArc(
                     color = controllerButtonColor.copy(alpha = 0.8f),
                     topLeft = buttonOffset,
                     size = buttonSize,
                     startAngle = 0f,
                     sweepAngle = 360f,
                     useCenter = false,
                 )
             }
         }
     }
 }
 
 private const val WHEEL_WIDTH_MULTIPLIER = 0.125f
 
 @Preview
 @Composable
 private fun Preview() {
     BpmWheel(
         state = remember { mutableStateOf(60.bpm) },
         modifier = Modifier.size(200.dp),
     )
 }