Skip to content

Commit a08be69

Browse files
david-allisonmikehardy
authored andcommitted
fix(deck-options): add progress bar to stop flickering
`deckOptionsReady` allows us to know exactly when the backend is ready, instead of showing the WebView in PageWebViewClient.onPageFinished Stop showing the WebView in `onPageFinished`, and wait for `deckOptionsReady` A `CircularProgressIndicator` has been added as the wait is noticeable (~300ms) Once the WebView is shown, hide the Progress Bar promiseToWaitFor and `:page-fully-loaded` are also removed as unused, and these were primarily for DeckOptions Fixes 14194 (better): deck options no longer flicker
1 parent 988402a commit a08be69

File tree

4 files changed

+68
-51
lines changed

4 files changed

+68
-51
lines changed

AnkiDroid/src/main/java/com/ichi2/anki/pages/DeckOptions.kt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ package com.ichi2.anki.pages
1818
import android.content.Context
1919
import android.content.Intent
2020
import android.os.Bundle
21+
import android.view.View
2122
import android.webkit.JavascriptInterface
2223
import android.webkit.WebResourceRequest
2324
import android.webkit.WebView
2425
import androidx.activity.OnBackPressedCallback
26+
import androidx.core.view.isVisible
2527
import androidx.fragment.app.FragmentActivity
2628
import anki.collection.OpChanges
2729
import anki.collection.Progress
@@ -136,6 +138,15 @@ class DeckOptions : PageFragment() {
136138
}
137139
}
138140

141+
/** @see onWebViewReady */
142+
override fun onViewCreated(
143+
view: View,
144+
savedInstanceState: Bundle?,
145+
) {
146+
pageLoadingIndicator.isVisible = true
147+
super.onViewCreated(view, savedInstanceState)
148+
}
149+
139150
override fun onWebViewCreated(webView: WebView) {
140151
// addJavascriptInterface needs to happen before loadUrl
141152
webView.addJavascriptInterface(ModalJavaScriptInterfaceListener(), "ankidroid")
@@ -152,6 +163,11 @@ class DeckOptions : PageFragment() {
152163
return object : PageWebViewClient() {
153164
private val ankiManualHostRegex = Regex("^docs\\.ankiweb\\.net\$")
154165

166+
/** @see onWebViewReady */
167+
override fun onShowWebView(webView: WebView) {
168+
// no-op: handled in onVebViewReady
169+
}
170+
155171
override fun shouldOverrideUrlLoading(
156172
view: WebView?,
157173
request: WebResourceRequest?,
@@ -205,7 +221,8 @@ class DeckOptions : PageFragment() {
205221

206222
fun onWebViewReady() {
207223
Timber.d("WebView ready to receive input")
208-
// TODO: handle this
224+
webView.isVisible = true
225+
pageLoadingIndicator.isVisible = false
209226
}
210227

211228
companion object {

AnkiDroid/src/main/java/com/ichi2/anki/pages/PageFragment.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import androidx.annotation.LayoutRes
2828
import androidx.core.os.bundleOf
2929
import androidx.fragment.app.Fragment
3030
import com.google.android.material.appbar.MaterialToolbar
31+
import com.google.android.material.progressindicator.CircularProgressIndicator
3132
import com.ichi2.anki.R
3233
import com.ichi2.anki.SingleFragmentActivity
3334
import com.ichi2.themes.Themes
@@ -46,6 +47,15 @@ open class PageFragment(
4647
lateinit var webView: WebView
4748
private val server = AnkiServer(this).also { it.start() }
4849

50+
/**
51+
* A loading indicator for the page. May be shown before the WebView is loaded to
52+
* stop flickering
53+
*
54+
* @exception IllegalStateException if accessed before [onViewCreated]
55+
*/
56+
val pageLoadingIndicator: CircularProgressIndicator
57+
get() = requireView().findViewById(R.id.page_loading)
58+
4959
/**
5060
* Override this to set a custom [WebViewClient] to the page.
5161
* This is called in [onViewCreated].

AnkiDroid/src/main/java/com/ichi2/anki/pages/PageWebViewClient.kt

Lines changed: 14 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,6 @@ import java.io.IOException
3434
* Base WebViewClient to be used on [PageFragment]
3535
*/
3636
open class PageWebViewClient : WebViewClient() {
37-
/** Wait for the provided promise to complete before showing the WebView */
38-
open val promiseToWaitFor: String? = null
39-
4037
val onPageFinishedCallbacks: MutableList<OnPageFinishedCallback> = mutableListOf()
4138

4239
override fun shouldInterceptRequest(
@@ -87,57 +84,27 @@ open class PageWebViewClient : WebViewClient() {
8784
}
8885
}
8986

87+
/**
88+
* Shows the WebView after the page is loaded
89+
*
90+
* This may be overridden if additional 'screen ready' logic is provided by the backend
91+
* @see DeckOptions
92+
*/
93+
open fun onShowWebView(webView: WebView) {
94+
Timber.v("Displaying WebView")
95+
webView.isVisible = true
96+
}
97+
9098
override fun onPageFinished(
9199
view: WebView?,
92100
url: String?,
93101
) {
94102
super.onPageFinished(view, url)
95103
if (view == null) return
96104
onPageFinishedCallbacks.map { callback -> callback.onPageFinished(view) }
97-
if (promiseToWaitFor == null) {
98-
/** [PageFragment.webView] is invisible by default to avoid flashes while
99-
* the page is loaded, and can be made visible again after it finishes loading */
100-
Timber.v("displaying WebView")
101-
view.isVisible = true
102-
} else {
103-
view.evaluateJavascript(
104-
"""$promiseToWaitFor.then(() => { console.log("page-fully-loaded:"); window.location.href = "page-fully-loaded:" } )""",
105-
) {
106-
Timber.v("waiting for '$promiseToWaitFor' before displaying WebView")
107-
}
108-
}
109-
}
110-
111-
@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION") // still needed for API 23
112-
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
113-
if (view == null || url == null) return super.shouldOverrideUrlLoading(view, url)
114-
if (handleUrl(view, url)) {
115-
return true
116-
}
117-
return super.shouldOverrideUrlLoading(view, url)
118-
}
119-
120-
override fun shouldOverrideUrlLoading(
121-
view: WebView?,
122-
request: WebResourceRequest?,
123-
): Boolean {
124-
if (view == null || request == null) return super.shouldOverrideUrlLoading(view, request)
125-
if (handleUrl(view, request.url.toString())) {
126-
return true
127-
}
128-
return super.shouldOverrideUrlLoading(view, request)
129-
}
130-
131-
private fun handleUrl(
132-
view: WebView,
133-
url: String,
134-
): Boolean {
135-
if (url == "page-fully-loaded:") {
136-
Timber.v("displaying WebView after '$promiseToWaitFor' executed")
137-
view.isVisible = true
138-
return true
139-
}
140-
return false
105+
/** [PageFragment.webView] is invisible by default to avoid flashes while
106+
* the page is loaded, and can be made visible again after it finishes loading */
107+
onShowWebView(view)
141108
}
142109
}
143110

AnkiDroid/src/main/res/layout/page_fragment.xml

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<LinearLayout
33
xmlns:android="http://schemas.android.com/apk/res/android"
44
xmlns:app="http://schemas.android.com/apk/res-auto"
5+
xmlns:tools="http://schemas.android.com/tools"
56
android:layout_width="match_parent"
67
android:layout_height="match_parent"
78
android:orientation="vertical">
@@ -15,10 +16,32 @@
1516
app:navigationIcon="?attr/homeAsUpIndicator"
1617
/>
1718

18-
<WebView
19-
android:id="@+id/webview"
19+
20+
<androidx.constraintlayout.widget.ConstraintLayout
2021
android:layout_width="match_parent"
2122
android:layout_height="match_parent"
22-
android:visibility="invisible"/>
23+
>
24+
<com.google.android.material.progressindicator.CircularProgressIndicator
25+
android:id="@+id/page_loading"
26+
android:layout_width="wrap_content"
27+
android:layout_height="wrap_content"
28+
android:indeterminate="true"
29+
android:visibility="gone"
30+
app:layout_constraintTop_toTopOf="parent"
31+
app:layout_constraintBottom_toBottomOf="parent"
32+
app:layout_constraintEnd_toEndOf="parent"
33+
app:layout_constraintStart_toStartOf="parent"
34+
tools:visibility="visible" />
35+
36+
<WebView
37+
android:id="@+id/webview"
38+
android:layout_width="match_parent"
39+
android:layout_height="match_parent"
40+
android:visibility="invisible"
41+
app:layout_constraintTop_toTopOf="parent"
42+
app:layout_constraintBottom_toBottomOf="parent"
43+
app:layout_constraintEnd_toEndOf="parent"
44+
app:layout_constraintStart_toStartOf="parent" />
45+
</androidx.constraintlayout.widget.ConstraintLayout>
2346

2447
</LinearLayout>

0 commit comments

Comments
 (0)