Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(deck-options): add progress bar to stop flickering #17891

Merged
merged 1 commit into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion AnkiDroid/src/main/java/com/ichi2/anki/pages/DeckOptions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ package com.ichi2.anki.pages
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.webkit.JavascriptInterface
import android.webkit.WebResourceRequest
import android.webkit.WebView
import androidx.activity.OnBackPressedCallback
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity
import anki.collection.OpChanges
import anki.collection.Progress
Expand Down Expand Up @@ -136,6 +138,15 @@ class DeckOptions : PageFragment() {
}
}

/** @see onWebViewReady */
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?,
) {
pageLoadingIndicator.isVisible = true
david-allison marked this conversation as resolved.
Show resolved Hide resolved
super.onViewCreated(view, savedInstanceState)
}

override fun onWebViewCreated(webView: WebView) {
// addJavascriptInterface needs to happen before loadUrl
webView.addJavascriptInterface(ModalJavaScriptInterfaceListener(), "ankidroid")
Expand All @@ -152,6 +163,11 @@ class DeckOptions : PageFragment() {
return object : PageWebViewClient() {
private val ankiManualHostRegex = Regex("^docs\\.ankiweb\\.net\$")

/** @see onWebViewReady */
override fun onShowWebView(webView: WebView) {
// no-op: handled in onVebViewReady
}

override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?,
Expand Down Expand Up @@ -205,7 +221,8 @@ class DeckOptions : PageFragment() {

fun onWebViewReady() {
Timber.d("WebView ready to receive input")
// TODO: handle this
webView.isVisible = true
pageLoadingIndicator.isVisible = false
}

companion object {
Expand Down
10 changes: 10 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/pages/PageFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import androidx.annotation.LayoutRes
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.progressindicator.CircularProgressIndicator
import com.ichi2.anki.R
import com.ichi2.anki.SingleFragmentActivity
import com.ichi2.themes.Themes
Expand All @@ -46,6 +47,15 @@ open class PageFragment(
lateinit var webView: WebView
private val server = AnkiServer(this).also { it.start() }

/**
* A loading indicator for the page. May be shown before the WebView is loaded to
* stop flickering
*
* @exception IllegalStateException if accessed before [onViewCreated]
*/
val pageLoadingIndicator: CircularProgressIndicator
get() = requireView().findViewById(R.id.page_loading)

/**
* Override this to set a custom [WebViewClient] to the page.
* This is called in [onViewCreated].
Expand Down
61 changes: 14 additions & 47 deletions AnkiDroid/src/main/java/com/ichi2/anki/pages/PageWebViewClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ import java.io.IOException
* Base WebViewClient to be used on [PageFragment]
*/
open class PageWebViewClient : WebViewClient() {
/** Wait for the provided promise to complete before showing the WebView */
open val promiseToWaitFor: String? = null

val onPageFinishedCallbacks: MutableList<OnPageFinishedCallback> = mutableListOf()

override fun shouldInterceptRequest(
Expand Down Expand Up @@ -87,57 +84,27 @@ open class PageWebViewClient : WebViewClient() {
}
}

/**
* Shows the WebView after the page is loaded
*
* This may be overridden if additional 'screen ready' logic is provided by the backend
* @see DeckOptions
*/
open fun onShowWebView(webView: WebView) {
Timber.v("Displaying WebView")
webView.isVisible = true
}

override fun onPageFinished(
view: WebView?,
url: String?,
) {
super.onPageFinished(view, url)
if (view == null) return
onPageFinishedCallbacks.map { callback -> callback.onPageFinished(view) }
if (promiseToWaitFor == null) {
/** [PageFragment.webView] is invisible by default to avoid flashes while
* the page is loaded, and can be made visible again after it finishes loading */
Timber.v("displaying WebView")
view.isVisible = true
} else {
view.evaluateJavascript(
"""$promiseToWaitFor.then(() => { console.log("page-fully-loaded:"); window.location.href = "page-fully-loaded:" } )""",
) {
Timber.v("waiting for '$promiseToWaitFor' before displaying WebView")
}
}
}

@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION") // still needed for API 23
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
if (view == null || url == null) return super.shouldOverrideUrlLoading(view, url)
if (handleUrl(view, url)) {
return true
}
return super.shouldOverrideUrlLoading(view, url)
}

override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?,
): Boolean {
if (view == null || request == null) return super.shouldOverrideUrlLoading(view, request)
if (handleUrl(view, request.url.toString())) {
return true
}
return super.shouldOverrideUrlLoading(view, request)
}

private fun handleUrl(
view: WebView,
url: String,
): Boolean {
if (url == "page-fully-loaded:") {
Timber.v("displaying WebView after '$promiseToWaitFor' executed")
view.isVisible = true
return true
}
return false
/** [PageFragment.webView] is invisible by default to avoid flashes while
* the page is loaded, and can be made visible again after it finishes loading */
onShowWebView(view)
}
}

Expand Down
29 changes: 26 additions & 3 deletions AnkiDroid/src/main/res/layout/page_fragment.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
Expand All @@ -15,10 +16,32 @@
app:navigationIcon="?attr/homeAsUpIndicator"
/>

<WebView
android:id="@+id/webview"

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"/>
>
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/page_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />

<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

</LinearLayout>