All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.com.github.panpf.sketch.ability.ProgressIndicatorModifier.kt Maven / Gradle / Ivy

Go to download

Sketch is an image loading library specially designed for Compose Multiplatform and Android View

There is a newer version: 4.0.0
Show newest version
/*
 * Copyright (C) 2024 panpf 
 *
 * 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 com.github.panpf.sketch.ability

import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.isSpecified
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.invalidateDraw
import androidx.compose.ui.platform.InspectorInfo
import com.github.panpf.sketch.AsyncImageState
import com.github.panpf.sketch.painter.ProgressPainter
import com.github.panpf.sketch.request.LoadState
import com.github.panpf.sketch.request.Progress
import com.github.panpf.sketch.request.name
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch

/**
 * Display a progress indicator on the surface of the component,
 * which shows the user the progress of the download.
 * The style of the indicator is provided by [progressPainter]
 *
 * @see com.github.panpf.sketch.extensions.compose.common.test.ability.ProgressIndicatorModifierTest.testModifier
 */
fun Modifier.progressIndicator(
    state: AsyncImageState,
    progressPainter: ProgressPainter
): Modifier {
    return this.then(ProgressIndicatorElement(state, progressPainter))
}

/**
 * ProgressIndicator Modifier Element
 *
 * @see com.github.panpf.sketch.extensions.compose.common.test.ability.ProgressIndicatorModifierTest.testElement
 */
internal data class ProgressIndicatorElement(
    val state: AsyncImageState,
    val progressPainter: ProgressPainter,
) : ModifierNodeElement() {

    override fun create(): ProgressIndicatorNode {
        return ProgressIndicatorNode(state, progressPainter)
    }

    override fun update(node: ProgressIndicatorNode) {
        node.update(state, progressPainter)
    }

    override fun InspectorInfo.inspectableProperties() {
        name = "ProgressIndicator"
        properties["progress"] = progressPainter.progress
        properties["loadState"] = state.loadState?.name ?: "null"
    }
}

/**
 * ProgressIndicator Modifier Node
 *
 * @see com.github.panpf.sketch.extensions.compose.common.test.ability.ProgressIndicatorModifierTest.testNode
 */
internal class ProgressIndicatorNode(
    private var state: AsyncImageState,
    private var progressPainter: ProgressPainter,
) : Modifier.Node(), DrawModifierNode, CompositionLocalConsumerModifierNode {

    private var lastProgressCollectJob: Job? = null

    override fun onAttach() {
        super.onAttach()
        recollectProgress()
    }

    override fun onDetach() {
        super.onDetach()
        lastProgressCollectJob?.cancel()
    }

    override fun ContentDrawScope.draw() {
        drawContent()

        val progressPainter = progressPainter
        val progressPainterSize = progressPainter.intrinsicSize
            .takeIf { it.isSpecified && !it.isEmpty() }
            ?: size
        translate(
            left = (size.width - progressPainterSize.width) / 2,
            top = (size.height - progressPainterSize.height) / 2,
        ) {
            with(progressPainter) {
                draw(progressPainterSize)
            }
        }
    }

    fun update(state: AsyncImageState, progressPainter: ProgressPainter) {
        this.state = state
        this.progressPainter = progressPainter
        if (isAttached) {
            recollectProgress()
        }
        invalidateDraw()
    }

    private fun recollectProgress() {
        lastProgressCollectJob?.cancel()
        lastProgressCollectJob = coroutineScope.launch {
            combine(
                flows = listOf(
                    snapshotFlow { state.loadState },
                    snapshotFlow { state.progress },
                ),
                transform = { it[0] as? LoadState to it[1] as Progress? }
            ).collect { (loadState, progress) ->
                when (loadState) {
                    is LoadState.Started -> {
                        progressPainter.progress = progress?.decimalProgress ?: 0f
                    }

                    is LoadState.Success -> progressPainter.progress = 1f
                    is LoadState.Error -> progressPainter.progress = -1f
                    is LoadState.Canceled -> progressPainter.progress = -1f
                    else -> progressPainter.progress = -1f
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy