Stop loading screens flashing white against the chosen theme
Two causes. The activity's window was a Light platform theme with no values-night, so the bare window was white whatever theme was picked. And on the category and poem screens Load sits outside the Scaffold, so while a page was loading nothing painted a background and that white window showed through. The window is now painted from the chosen theme before the first frame, and kept in step when the theme changes, which a static XML theme could not do for sepia or OLED. Load paints its own loading and error states too. Android 12+ draws its splash before any app code runs, so that one can only follow the system light/dark setting; picking sepia or OLED on a light-mode phone still shows a light splash for a frame. Verified on an API 36 emulator under the OLED theme: the loading screen is now true black with the themed spinner. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a5fc608bca
commit
b545220bbc
@ -10,6 +10,7 @@ import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import com.ganjoor.android.data.Bookmarks
|
||||
import com.ganjoor.android.data.Ganjoor
|
||||
import com.ganjoor.android.data.LocalBookmarks
|
||||
@ -17,6 +18,7 @@ import com.ganjoor.android.ui.GanjoorApp
|
||||
import com.ganjoor.android.ui.LocalSettings
|
||||
import com.ganjoor.android.ui.Settings
|
||||
import com.ganjoor.android.ui.theme.GanjoorTheme
|
||||
import com.ganjoor.android.ui.theme.windowBackground
|
||||
import java.util.Locale
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
@ -35,9 +37,19 @@ class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Ganjoor.init(applicationContext)
|
||||
|
||||
// Before the first frame: otherwise the window keeps the platform's white through
|
||||
// startup and every screen transition, whatever theme is chosen.
|
||||
val settings = Settings(applicationContext)
|
||||
val systemInDark = resources.configuration.uiMode and
|
||||
Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
||||
window.setBackgroundDrawable(
|
||||
windowBackground(settings.value.theme, systemInDark).toDrawable()
|
||||
)
|
||||
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
val settings = remember { Settings(applicationContext) }
|
||||
val settings = remember { settings }
|
||||
val bookmarks = remember { Bookmarks(applicationContext) }
|
||||
// The client reads this flag on every request, so keep it in step with the setting.
|
||||
Ganjoor.offline = settings.value.offline
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.ganjoor.android.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@ -36,12 +37,23 @@ fun <T> Load(key: Any?, block: suspend () -> T, content: @Composable (T) -> Unit
|
||||
|
||||
result.let { outcome ->
|
||||
when {
|
||||
outcome == null -> Box(Modifier.fillMaxSize(), Alignment.Center) {
|
||||
// Painted explicitly: on the category and poem screens Load sits outside the
|
||||
// Scaffold, so while loading nothing else fills the window and the bare window
|
||||
// background would show through.
|
||||
outcome == null -> Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
|
||||
outcome.isFailure -> Column(
|
||||
modifier = Modifier.fillMaxSize().padding(24.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.padding(24.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
|
||||
@ -8,7 +8,9 @@ import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import androidx.core.view.WindowCompat
|
||||
import com.ganjoor.android.ui.Language
|
||||
import com.ganjoor.android.ui.ThemeMode
|
||||
@ -146,6 +148,20 @@ private val SepiaDarkScheme = darkColorScheme(
|
||||
outline = Color(0xFF968A72),
|
||||
)
|
||||
|
||||
/**
|
||||
* The window's own background colour for a theme. The activity paints this before Compose runs,
|
||||
* so the gap between the window appearing and the first frame matches the theme instead of
|
||||
* flashing the platform's white. A static XML theme can't express sepia or OLED, hence this.
|
||||
*/
|
||||
fun windowBackground(mode: ThemeMode, systemInDark: Boolean): Int = when (mode) {
|
||||
ThemeMode.Light -> LightScheme
|
||||
ThemeMode.Dark -> DarkScheme
|
||||
ThemeMode.Sepia -> SepiaScheme
|
||||
ThemeMode.SepiaDark -> SepiaDarkScheme
|
||||
ThemeMode.Black -> BlackScheme
|
||||
ThemeMode.System -> if (systemInDark) DarkScheme else LightScheme
|
||||
}.background.toArgb()
|
||||
|
||||
@Composable
|
||||
fun GanjoorTheme(mode: ThemeMode, language: Language, content: @Composable () -> Unit) {
|
||||
val dark = when (mode) {
|
||||
@ -164,6 +180,9 @@ fun GanjoorTheme(mode: ThemeMode, language: Language, content: @Composable () ->
|
||||
if (!view.isInEditMode) {
|
||||
SideEffect {
|
||||
val window = (view.context as Activity).window
|
||||
// Keep the window in step when the theme changes, so switching themes doesn't leave
|
||||
// a stale colour behind transitions.
|
||||
window.setBackgroundDrawable(scheme.background.toArgb().toDrawable())
|
||||
WindowCompat.getInsetsController(window, view).apply {
|
||||
isAppearanceLightStatusBars = !dark
|
||||
isAppearanceLightNavigationBars = !dark
|
||||
|
||||
13
app/src/main/res/values-night-v31/themes.xml
Normal file
13
app/src/main/res/values-night-v31/themes.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!--
|
||||
Android 12+ draws its own splash from these attributes, before any of our code runs, so it
|
||||
can only follow the system's light/dark setting — not the theme chosen inside the app.
|
||||
Picking sepia or OLED on a light-mode phone still shows a light splash for one frame.
|
||||
-->
|
||||
<style name="Theme.Ganjoorappandroid" parent="android:Theme.Material.NoActionBar">
|
||||
<item name="android:windowBackground">@color/window_background</item>
|
||||
<item name="android:windowSplashScreenBackground">@color/window_background</item>
|
||||
</style>
|
||||
</resources>
|
||||
5
app/src/main/res/values-night/colors.xml
Normal file
5
app/src/main/res/values-night/colors.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Matches DarkScheme.background in Theme.kt. -->
|
||||
<color name="window_background">#191C1C</color>
|
||||
</resources>
|
||||
7
app/src/main/res/values-night/themes.xml
Normal file
7
app/src/main/res/values-night/themes.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Theme.Ganjoorappandroid" parent="android:Theme.Material.NoActionBar">
|
||||
<item name="android:windowBackground">@color/window_background</item>
|
||||
</style>
|
||||
</resources>
|
||||
13
app/src/main/res/values-v31/themes.xml
Normal file
13
app/src/main/res/values-v31/themes.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!--
|
||||
Android 12+ draws its own splash from these attributes, before any of our code runs, so it
|
||||
can only follow the system's light/dark setting — not the theme chosen inside the app.
|
||||
Picking sepia or OLED on a light-mode phone still shows a light splash for one frame.
|
||||
-->
|
||||
<style name="Theme.Ganjoorappandroid" parent="android:Theme.Material.Light.NoActionBar">
|
||||
<item name="android:windowBackground">@color/window_background</item>
|
||||
<item name="android:windowSplashScreenBackground">@color/window_background</item>
|
||||
</style>
|
||||
</resources>
|
||||
@ -1,10 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
<!-- Matches LightScheme.background in Theme.kt. -->
|
||||
<color name="window_background">#FAFDFC</color>
|
||||
</resources>
|
||||
@ -1,5 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Theme.Ganjoorappandroid" parent="android:Theme.Material.Light.NoActionBar" />
|
||||
<!--
|
||||
The starting window Android shows before onCreate runs. MainActivity immediately repaints
|
||||
it from the chosen theme, so this only has to cover light vs dark; values-night supplies
|
||||
the other half.
|
||||
-->
|
||||
<style name="Theme.Ganjoorappandroid" parent="android:Theme.Material.Light.NoActionBar">
|
||||
<item name="android:windowBackground">@color/window_background</item>
|
||||
</style>
|
||||
</resources>
|
||||
Loading…
Reference in New Issue
Block a user