Adding pull to refresh on Bet Screen
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
6104245af7
commit
7dfe35caeb
@ -0,0 +1,38 @@
|
||||
package fr.iut.alldev.allin.ui.bet
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@HiltViewModel
|
||||
class BetViewModel @Inject constructor(
|
||||
) : ViewModel() {
|
||||
|
||||
private val _isRefreshing = MutableStateFlow(false)
|
||||
val isRefreshing: StateFlow<Boolean>
|
||||
get() = _isRefreshing.asStateFlow()
|
||||
|
||||
|
||||
private fun refreshData(){
|
||||
Thread.sleep(1000)
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
viewModelScope.launch {
|
||||
_isRefreshing.emit(true)
|
||||
withContext(Dispatchers.IO) {
|
||||
refreshData()
|
||||
}
|
||||
_isRefreshing.emit(false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
package fr.iut.alldev.allin.ui.core
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.progressSemantics
|
||||
import androidx.compose.material3.ProgressIndicatorDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import fr.iut.alldev.allin.ui.theme.AllInTheme
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
|
||||
@Composable
|
||||
fun AllInLoading(
|
||||
modifier: Modifier = Modifier,
|
||||
brush: Brush = AllInTheme.colors.allIn_MainGradient
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.clickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = null,
|
||||
onClick = {}
|
||||
)
|
||||
.background(AllInTheme.themeColors.main_surface.copy(alpha = .4f))
|
||||
) {
|
||||
AllInCircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(50.dp),
|
||||
brush = brush,
|
||||
strokeWidth = 7.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AllInCircularProgressIndicator(
|
||||
modifier: Modifier = Modifier,
|
||||
brush: Brush = AllInTheme.colors.allIn_MainGradient,
|
||||
strokeWidth: Dp = ProgressIndicatorDefaults.CircularStrokeWidth,
|
||||
strokeCap: StrokeCap = ProgressIndicatorDefaults.CircularIndeterminateStrokeCap,
|
||||
) {
|
||||
val stroke = with(LocalDensity.current) {
|
||||
Stroke(width = strokeWidth.toPx(), cap = strokeCap)
|
||||
}
|
||||
|
||||
val transition = rememberInfiniteTransition()
|
||||
val currentRotation = transition.animateValue(
|
||||
0,
|
||||
RotationsPerCycle,
|
||||
Int.VectorConverter,
|
||||
infiniteRepeatable(
|
||||
animation = tween(
|
||||
durationMillis = RotationDuration * RotationsPerCycle,
|
||||
easing = LinearEasing
|
||||
)
|
||||
)
|
||||
)
|
||||
val baseRotation = transition.animateFloat(
|
||||
0f,
|
||||
BaseRotationAngle,
|
||||
infiniteRepeatable(
|
||||
animation = tween(
|
||||
durationMillis = RotationDuration,
|
||||
easing = LinearEasing
|
||||
)
|
||||
)
|
||||
)
|
||||
val endAngle = transition.animateFloat(
|
||||
0f,
|
||||
JumpRotationAngle,
|
||||
infiniteRepeatable(
|
||||
animation = keyframes {
|
||||
durationMillis = HeadAndTailAnimationDuration + HeadAndTailDelayDuration
|
||||
0f at 0 with CircularEasing
|
||||
JumpRotationAngle at HeadAndTailAnimationDuration
|
||||
}
|
||||
)
|
||||
)
|
||||
val startAngle = transition.animateFloat(
|
||||
0f,
|
||||
JumpRotationAngle,
|
||||
infiniteRepeatable(
|
||||
animation = keyframes {
|
||||
durationMillis = HeadAndTailAnimationDuration + HeadAndTailDelayDuration
|
||||
0f at HeadAndTailDelayDuration with CircularEasing
|
||||
JumpRotationAngle at durationMillis
|
||||
}
|
||||
)
|
||||
)
|
||||
Canvas(
|
||||
modifier
|
||||
.progressSemantics()
|
||||
.size(CircularIndicatorDiameter)
|
||||
) {
|
||||
drawCircularIndicator(0f, 360f, Color.Transparent, stroke)
|
||||
val currentRotationAngleOffset = (currentRotation.value * RotationAngleOffset) % 360f
|
||||
|
||||
val sweep = abs(endAngle.value - startAngle.value)
|
||||
|
||||
val offset = StartAngleOffset + currentRotationAngleOffset + baseRotation.value
|
||||
drawIndeterminateCircularIndicator(
|
||||
startAngle.value + offset,
|
||||
strokeWidth,
|
||||
sweep,
|
||||
brush,
|
||||
stroke
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun DrawScope.drawCircularIndicator(
|
||||
startAngle: Float,
|
||||
sweep: Float,
|
||||
color: Color,
|
||||
stroke: Stroke
|
||||
) {
|
||||
val diameterOffset = stroke.width / 2
|
||||
val arcDimen = size.width - 2 * diameterOffset
|
||||
drawArc(
|
||||
color = color,
|
||||
startAngle = startAngle,
|
||||
sweepAngle = sweep,
|
||||
useCenter = false,
|
||||
topLeft = Offset(diameterOffset, diameterOffset),
|
||||
size = Size(arcDimen, arcDimen),
|
||||
style = stroke
|
||||
)
|
||||
}
|
||||
|
||||
private fun DrawScope.drawCircularIndicator(
|
||||
startAngle: Float,
|
||||
sweep: Float,
|
||||
brush: Brush,
|
||||
stroke: Stroke
|
||||
) {
|
||||
val diameterOffset = stroke.width / 2
|
||||
val arcDimen = size.width - 2 * diameterOffset
|
||||
drawArc(
|
||||
brush = brush,
|
||||
startAngle = startAngle,
|
||||
sweepAngle = sweep,
|
||||
useCenter = false,
|
||||
topLeft = Offset(diameterOffset, diameterOffset),
|
||||
size = Size(arcDimen, arcDimen),
|
||||
style = stroke
|
||||
)
|
||||
}
|
||||
|
||||
private fun DrawScope.drawIndeterminateCircularIndicator(
|
||||
startAngle: Float,
|
||||
strokeWidth: Dp,
|
||||
sweep: Float,
|
||||
brush: Brush,
|
||||
stroke: Stroke
|
||||
) {
|
||||
val strokeCapOffset = if (stroke.cap == StrokeCap.Butt) {
|
||||
0f
|
||||
} else {
|
||||
(180.0 / PI).toFloat() * (strokeWidth / (CircularIndicatorDiameter / 2)) / 2f
|
||||
}
|
||||
val adjustedStartAngle = startAngle + strokeCapOffset
|
||||
val adjustedSweep = max(sweep, 0.1f)
|
||||
drawCircularIndicator(adjustedStartAngle, adjustedSweep, brush, stroke)
|
||||
}
|
||||
|
||||
private const val RotationsPerCycle = 5
|
||||
private const val RotationDuration = 1332
|
||||
private const val StartAngleOffset = -90f
|
||||
private const val BaseRotationAngle = 286f
|
||||
private const val JumpRotationAngle = 290f
|
||||
private val CircularIndicatorDiameter = 38.dp
|
||||
private const val RotationAngleOffset = (BaseRotationAngle + JumpRotationAngle) % 360f
|
||||
private const val HeadAndTailAnimationDuration = (RotationDuration * 0.5).toInt()
|
||||
private const val HeadAndTailDelayDuration = HeadAndTailAnimationDuration
|
||||
private val CircularEasing = CubicBezierEasing(0.4f, 0f, 0.2f, 1f)
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun AllInLoadingPreview() {
|
||||
AllInTheme {
|
||||
AllInLoading()
|
||||
}
|
||||
}
|
@ -1,11 +1,30 @@
|
||||
package fr.iut.alldev.allin.ui.login
|
||||
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class LoginViewModel @Inject constructor(
|
||||
) : ViewModel() {
|
||||
|
||||
var loading = mutableStateOf(false)
|
||||
fun onLogin(
|
||||
navigateToDashboard: ()->Unit
|
||||
){
|
||||
viewModelScope.launch {
|
||||
loading.value = true
|
||||
withContext(Dispatchers.IO) {
|
||||
Thread.sleep(3000)
|
||||
}
|
||||
navigateToDashboard()
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in new issue