Twelve: Add a fullscreen loading progress bar view

Change-Id: I2568255d62592cd1e246c6d4187e9592591963d4
diff --git a/app/src/main/java/org/lineageos/twelve/ui/views/FullscreenLoadingProgressBar.kt b/app/src/main/java/org/lineageos/twelve/ui/views/FullscreenLoadingProgressBar.kt
new file mode 100644
index 0000000..10ba150
--- /dev/null
+++ b/app/src/main/java/org/lineageos/twelve/ui/views/FullscreenLoadingProgressBar.kt
@@ -0,0 +1,134 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.twelve.ui.views
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.Gravity
+import android.widget.FrameLayout
+import androidx.annotation.AttrRes
+import androidx.annotation.StyleRes
+import androidx.annotation.UiThread
+import androidx.core.view.isVisible
+import androidx.core.widget.ContentLoadingProgressBar
+import com.google.android.material.progressindicator.CircularProgressIndicator
+
+/**
+ * Inspired by [ContentLoadingProgressBar].
+ */
+class FullscreenLoadingProgressBar : FrameLayout {
+    constructor(context: Context) : super(context)
+
+    @JvmOverloads
+    constructor(
+        context: Context,
+        attrs: AttributeSet?,
+        @AttrRes defStyleAttr: Int = 0,
+        @StyleRes defStyleRes: Int = 0,
+    ) : super(context, attrs, defStyleAttr, defStyleRes)
+
+    private val circularProgressIndicator = CircularProgressIndicator(context).apply {
+        isIndeterminate = true
+    }
+
+    private var startTime = -1L
+    private var postedHide = false
+    private var postedShow = false
+    private var dismissed = false
+
+    private val delayedHide = Runnable {
+        postedHide = false
+        startTime = -1
+        isVisible = false
+    }
+
+    private val delayedShow = Runnable {
+        postedShow = false
+        if (!dismissed) {
+            startTime = System.currentTimeMillis()
+            isVisible = true
+        }
+    }
+
+    init {
+        setBackgroundColor(0x55000000)
+
+        addView(
+            circularProgressIndicator,
+            LayoutParams(
+                LayoutParams.WRAP_CONTENT,
+                LayoutParams.WRAP_CONTENT,
+            ).apply {
+                gravity = Gravity.CENTER
+            }
+        )
+
+        setOnClickListener {
+            // Do nothing
+        }
+    }
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        removeCallbacks()
+    }
+
+    override fun onDetachedFromWindow() {
+        super.onDetachedFromWindow()
+        removeCallbacks()
+    }
+
+    @UiThread
+    fun show() {
+        // Reset the start time.
+        startTime = -1
+        dismissed = false
+        removeCallbacks(delayedHide)
+        postedHide = false
+        if (!postedShow) {
+            postDelayed(delayedShow, DELAY_MS)
+            postedShow = true
+        }
+    }
+
+    @UiThread
+    fun hide() {
+        dismissed = true
+        removeCallbacks(delayedShow)
+        postedShow = false
+        val diff: Long = System.currentTimeMillis() - startTime
+        if (diff >= SHOW_TIME_MS || startTime == -1L) {
+            // The progress spinner has been shown long enough
+            // OR was not shown yet. If it wasn't shown yet,
+            // it will just never be shown.
+            visibility = GONE
+        } else {
+            // The progress spinner is shown, but not long enough,
+            // so put a delayed message in to hide it when its been
+            // shown long enough.
+            if (!postedHide) {
+                postDelayed(delayedHide, SHOW_TIME_MS - diff)
+                postedHide = true
+            }
+        }
+    }
+
+    suspend fun withProgress(block: suspend () -> Unit) {
+        show()
+        block()
+        hide()
+    }
+
+    private fun removeCallbacks() {
+        removeCallbacks(delayedHide)
+        removeCallbacks(delayedShow)
+    }
+
+    companion object {
+        private const val SHOW_TIME_MS = 500L
+        private const val DELAY_MS = 500L
+    }
+}