Skip to content

Commit 553ca79

Browse files
authored
[MBL-19385][All] Fix light mode button disappearing after rotation (#3285)
Fixed issue where the "Switch to Light Mode" button in calendar event details would disappear after screen rotation and the theme state would not persist. refs: MBL-19385 affects: Parent, Student, Teacher release note: Fixed light mode button disappearing after screen rotation in calendar event details test plan: Enable dark mode on device Navigate to calendar event with HTML description Verify "Switch to Light Mode" button appears Rotate device - button should remain visible Toggle to light mode, rotate device - light mode should persist 🤖 Generated with Claude Code
1 parent 1a1aab3 commit 553ca79

File tree

2 files changed

+105
-8
lines changed

2 files changed

+105
-8
lines changed

libs/pandautils/src/main/java/com/instructure/pandautils/compose/composables/ComposeCanvasWebViewWrapper.kt

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ import android.webkit.WebView
2121
import androidx.compose.foundation.layout.fillMaxSize
2222
import androidx.compose.material.Text
2323
import androidx.compose.runtime.Composable
24+
import androidx.compose.runtime.mutableStateOf
25+
import androidx.compose.runtime.saveable.Saver
2426
import androidx.compose.runtime.saveable.rememberSaveable
2527
import androidx.compose.ui.Modifier
28+
import androidx.compose.ui.platform.LocalConfiguration
2629
import androidx.compose.ui.platform.LocalInspectionMode
2730
import androidx.compose.ui.viewinterop.AndroidView
2831
import androidx.core.os.bundleOf
@@ -47,6 +50,13 @@ fun ComposeCanvasWebViewWrapper(
4750
embeddedWebViewCallbacks: ComposeEmbeddedWebViewCallbacks? = null,
4851
) {
4952
val webViewState = rememberSaveable(content) { bundleOf() }
53+
val savedHtml = rememberSaveable(content, stateSaver = Saver(
54+
save = { it },
55+
restore = { it }
56+
)) { mutableStateOf(content) }
57+
val savedThemeSwitched = rememberSaveable { bundleOf("themeSwitched" to false) }
58+
val configuration = LocalConfiguration.current
59+
val configKey = "${configuration.orientation}-${configuration.uiMode}"
5060

5161
if (LocalInspectionMode.current) {
5262
Text(text = content)
@@ -84,27 +94,34 @@ fun ComposeCanvasWebViewWrapper(
8494
applyOnWebView?.let { applyOnWebView -> webView.applyOnWebView() }
8595
}
8696
},
87-
update = {
97+
update = { view ->
98+
configKey // Read configuration to trigger update on change
99+
savedHtml.value = content // Update saved HTML on each update
88100
if (webViewState.isEmpty) {
89101
if (useInAppFormatting) {
90-
it.loadHtml(content, title)
102+
view.loadHtml(savedHtml.value, title)
91103
} else {
92-
it.loadDataWithBaseUrl(CanvasWebView.getReferrer(true), content, contentType, "UTF-8", null)
104+
view.loadDataWithBaseUrl(CanvasWebView.getReferrer(true), savedHtml.value, contentType, "UTF-8", null)
93105
}
94106

95107
if (onLtiButtonPressed != null) {
96-
it.webView.addJavascriptInterface(JsExternalToolInterface(onLtiButtonPressed), Const.LTI_TOOL)
108+
view.webView.addJavascriptInterface(JsExternalToolInterface(onLtiButtonPressed), Const.LTI_TOOL)
97109
}
98110

99-
if (HtmlContentFormatter.hasGoogleDocsUrl(content)) {
100-
it.webView.addJavascriptInterface(JsGoogleDocsInterface(it.context), Const.GOOGLE_DOCS)
111+
if (HtmlContentFormatter.hasGoogleDocsUrl(savedHtml.value)) {
112+
view.webView.addJavascriptInterface(JsGoogleDocsInterface(view.context), Const.GOOGLE_DOCS)
101113
}
114+
view.handleConfigurationChange()
102115
} else {
103-
it.webView.restoreState(webViewState)
116+
view.webView.restoreState(webViewState)
117+
view.setHtmlContent(savedHtml.value)
118+
view.setThemeSwitched(savedThemeSwitched.getBoolean("themeSwitched", false))
119+
view.handleConfigurationChange(reloadContent = false)
104120
}
105-
applyOnUpdate?.let { applyOnUpdate -> it.applyOnUpdate() }
121+
applyOnUpdate?.let { applyOnUpdate -> view.applyOnUpdate() }
106122
},
107123
onRelease = {
124+
savedThemeSwitched.putBoolean("themeSwitched", it.themeSwitched)
108125
it.webView.saveState(webViewState)
109126
},
110127
modifier = modifier.fillMaxSize()

libs/pandautils/src/main/java/com/instructure/pandautils/views/CanvasWebViewWrapper.kt

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,13 @@ package com.instructure.pandautils.views
1818

1919
import android.content.Context
2020
import android.content.res.Configuration
21+
import android.os.Parcel
22+
import android.os.Parcelable
2123
import android.util.AttributeSet
2224
import android.view.LayoutInflater
2325
import android.widget.LinearLayout
2426
import androidx.annotation.ColorRes
27+
import androidx.core.view.isVisible
2528
import com.instructure.canvasapi2.utils.ApiPrefs
2629
import com.instructure.pandautils.R
2730
import com.instructure.pandautils.databinding.ViewCanvasWebViewWrapperBinding
@@ -137,6 +140,13 @@ open class CanvasWebViewWrapper @JvmOverloads constructor(
137140
}
138141

139142
private fun initVisibility(html: String) {
143+
updateButtonVisibility(html)
144+
if (binding.themeSwitchButton.visibility == android.view.View.VISIBLE) {
145+
changeButtonTheme()
146+
}
147+
}
148+
149+
private fun updateButtonVisibility(html: String) {
140150
val nightModeFlags: Int = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
141151
if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES && html.isNotEmpty()) {
142152
binding.themeSwitchButton.setVisible()
@@ -145,6 +155,76 @@ open class CanvasWebViewWrapper @JvmOverloads constructor(
145155
}
146156
}
147157

158+
override fun onConfigurationChanged(newConfig: Configuration?) {
159+
super.onConfigurationChanged(newConfig)
160+
handleConfigurationChange()
161+
}
162+
163+
fun setHtmlContent(content: String?) {
164+
this.html = content
165+
}
166+
167+
fun setThemeSwitched(switched: Boolean) {
168+
this.themeSwitched = switched
169+
}
170+
171+
fun handleConfigurationChange(reloadContent: Boolean = true) {
172+
html?.let { htmlContent ->
173+
updateButtonVisibility(htmlContent)
174+
if (binding.themeSwitchButton.isVisible) {
175+
changeButtonTheme()
176+
if (reloadContent) {
177+
changeContentTheme(htmlContent)
178+
}
179+
}
180+
}
181+
}
182+
183+
override fun onSaveInstanceState(): Parcelable {
184+
val superState = super.onSaveInstanceState()
185+
return SavedState(superState).apply {
186+
this.themeSwitched = this@CanvasWebViewWrapper.themeSwitched
187+
this.html = this@CanvasWebViewWrapper.html
188+
}
189+
}
190+
191+
override fun onRestoreInstanceState(state: Parcelable?) {
192+
when (state) {
193+
is SavedState -> {
194+
super.onRestoreInstanceState(state.superState)
195+
themeSwitched = state.themeSwitched
196+
html = state.html
197+
}
198+
else -> super.onRestoreInstanceState(state)
199+
}
200+
}
201+
202+
private class SavedState : BaseSavedState {
203+
var themeSwitched: Boolean = false
204+
var html: String? = null
205+
206+
constructor(superState: Parcelable?) : super(superState)
207+
208+
private constructor(parcel: Parcel) : super(parcel) {
209+
themeSwitched = parcel.readInt() == 1
210+
html = parcel.readString()
211+
}
212+
213+
override fun writeToParcel(out: Parcel, flags: Int) {
214+
super.writeToParcel(out, flags)
215+
out.writeInt(if (themeSwitched) 1 else 0)
216+
out.writeString(html)
217+
}
218+
219+
companion object {
220+
@JvmField
221+
val CREATOR = object : Parcelable.Creator<SavedState> {
222+
override fun createFromParcel(parcel: Parcel) = SavedState(parcel)
223+
override fun newArray(size: Int): Array<SavedState?> = arrayOfNulls(size)
224+
}
225+
}
226+
}
227+
148228
private fun formatHtml(data: String): String {
149229
val textDarkest = colorResToHexString(if (themeSwitched) R.color.licorice else R.color.textDarkest)
150230
val textDark = colorResToHexString(if (themeSwitched) R.color.ash else R.color.textDark)

0 commit comments

Comments
 (0)