'How to create timed Instagram story loading bar using Jetpack Compose animation?

I would like to create a composable component very similar to Instagram story loading bar with 10 seconds of duration.

enter image description here

I had an idea how to do it but I'm not how to executed. I was thinking about adding a static bar (grey color) using a BOX and then add another bar (white color) which animates from 0 to final of composable width in 10 seconds.

Do you have any idea how I can implement this component?



Solution 1:[1]

You can animate the progress of a LinearProgressIndicator. Something like:

var enabled by remember { mutableStateOf(false) }
val progress: Float by animateFloatAsState(
    if (enabled) 1f else 0.0f,
    animationSpec = tween(
        durationMillis = 10000,
        delayMillis = 40,
        easing = LinearOutSlowInEasing
    )
)

LinearProgressIndicator(
    color = White,
    backgroundColor = LightGray,
    progress = progress,
    modifier = Modifier.width(100.dp)
)

Just set enabled=true to start the animation (it is just an example).

enter image description here

Solution 2:[2]

try this solution, it should work:

 var progress by remember {mutableStateOf(0.0f)}
var enabled by remember { mutableStateOf(true) }
LaunchedEffect(key1 = progress, key2 = enabled) {
    if(progress<1 && enabled) {
        delay(100L)
        progress += 0.01F
    }
}
LinearProgressIndicator(
    color = White,
    backgroundColor = LightGray,
    progress = progress,
    modifier = Modifier.width(100.dp)
)

Solution 3:[3]

This is can be a full sample solution you can see and try to extend as well as refactor. The code is self-explanatory and you will see how it goes.

A story composable:

@OptIn(ExperimentalAnimationApi::class)
@Composable
fun Story(story: Stories) {
    AnimatedContent(story) {
        when (story) {
            Stories.ONE -> {
                StoryContent("1")
            }
            Stories.TWO -> {
                StoryContent("2")
            }
            Stories.THREE -> {
                StoryContent("3")
            }
        }
    }
}

@Composable
private fun StoryContent(content: String) {
    Text("Story $content")
}

Story progress indicator:

@Composable
fun StoryProgressIndicator(running: Boolean, modifier: Modifier = Modifier, onTenSecondsOnThisStory: () -> Unit) {
    val progress: Float by animateFloatAsState(
        if (running) 1f else 0f,
        animationSpec = tween(
            durationMillis = if (running) 10_000 else 0,
            easing = LinearEasing
        )
    )
    if (progress == 1f) {
        onTenSecondsOnThisStory()
    }
    LinearProgressIndicator(
        progress, modifier
    )
}

And a screen that contains the navigation among stories and playing the animation.

@OptIn(ExperimentalAnimationApi::class)
@Composable
fun InstagramLikeStories() {
    var screenWidth by remember { mutableStateOf(1) }
    var currentStory by remember { mutableStateOf(Stories.ONE) }
    var currentStoryPointer by remember { mutableStateOf(0) }
    var runningStoryOne by remember { mutableStateOf(false) }
    var runningStoryTwo by remember { mutableStateOf(false) }
    var runningStoryThree by remember { mutableStateOf(false) }

    val runStoryOne = { runningStoryOne = true; runningStoryTwo = false; runningStoryThree = false }
    val runStoryTwo = { runningStoryOne = false; runningStoryTwo = true; runningStoryThree = false }
    val runStoryThree = { runningStoryOne = false; runningStoryTwo = false; runningStoryThree = true }

    val stories = Stories.values()

    LaunchedEffect(Unit) { runStoryOne() }

    Column(
        Modifier.fillMaxSize().onGloballyPositioned {
            screenWidth = it.size.width
        }.pointerInput(Unit) {
            detectTapGestures(onTap = {
                if ((it.x / screenWidth) * 100 > 50) {
                    if (currentStoryPointer == stories.size - 1) {
                        currentStoryPointer = 0
                    } else {
                        currentStoryPointer++
                    }
                    currentStory = stories[currentStoryPointer]
                } else {
                    if (currentStoryPointer != 0) {
                        currentStoryPointer--
                        currentStory = stories[currentStoryPointer]
                    }
                }
                runStoryIndicator(currentStory, runStoryOne, runStoryTwo, runStoryThree)
            })
        }
    ) {

        Row(Modifier.fillMaxWidth().padding(horizontal = 16.dp)) {
            StoryProgressIndicator(runningStoryOne, Modifier.weight(1f), onTenSecondsOnThisStory = {
                runStoryIndicator(currentStory, runStoryOne, runStoryTwo, runStoryThree)
                currentStoryPointer = 1
                currentStory = stories[currentStoryPointer]
            })
            Spacer(Modifier.weight(0.1f))
            StoryProgressIndicator(runningStoryTwo, Modifier.weight(1f), onTenSecondsOnThisStory = {
                runStoryIndicator(currentStory, runStoryOne, runStoryTwo, runStoryThree)
                currentStoryPointer = 2
                currentStory = stories[currentStoryPointer]
            })
            Spacer(Modifier.weight(0.1f))
            StoryProgressIndicator(runningStoryThree, Modifier.weight(1f), onTenSecondsOnThisStory = {
                runStoryIndicator(currentStory, runStoryOne, runStoryTwo, runStoryThree)
                // go to first one
                currentStoryPointer = 0
                currentStory = stories[currentStoryPointer]
            })
        }
    }
    Story(currentStory)
    Row {
        Button(onClick = {
        }) {
           Text("button")
        }
    }
}

private fun runStoryIndicator(
    currentStory: Stories,
    runStoryOne: () -> Unit,
    runStoryTwo: () -> Unit,
    runStoryThree: () -> Unit
) {
    when (currentStory) {
        Stories.ONE -> runStoryOne()
        Stories.TWO -> runStoryTwo()
        Stories.THREE -> runStoryThree()
    }
}

enum class Stories {
    ONE, TWO, THREE
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Mahaman Bachir Ibrahim
Solution 3 Dharman