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.runtime.remember
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
|
import androidx.core.graphics.drawable.toDrawable
|
||||||
import com.ganjoor.android.data.Bookmarks
|
import com.ganjoor.android.data.Bookmarks
|
||||||
import com.ganjoor.android.data.Ganjoor
|
import com.ganjoor.android.data.Ganjoor
|
||||||
import com.ganjoor.android.data.LocalBookmarks
|
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.LocalSettings
|
||||||
import com.ganjoor.android.ui.Settings
|
import com.ganjoor.android.ui.Settings
|
||||||
import com.ganjoor.android.ui.theme.GanjoorTheme
|
import com.ganjoor.android.ui.theme.GanjoorTheme
|
||||||
|
import com.ganjoor.android.ui.theme.windowBackground
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
@ -35,9 +37,19 @@ class MainActivity : ComponentActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Ganjoor.init(applicationContext)
|
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()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
val settings = remember { Settings(applicationContext) }
|
val settings = remember { settings }
|
||||||
val bookmarks = remember { Bookmarks(applicationContext) }
|
val bookmarks = remember { Bookmarks(applicationContext) }
|
||||||
// The client reads this flag on every request, so keep it in step with the setting.
|
// The client reads this flag on every request, so keep it in step with the setting.
|
||||||
Ganjoor.offline = settings.value.offline
|
Ganjoor.offline = settings.value.offline
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.ganjoor.android.ui
|
package com.ganjoor.android.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
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 ->
|
result.let { outcome ->
|
||||||
when {
|
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()
|
CircularProgressIndicator()
|
||||||
}
|
}
|
||||||
|
|
||||||
outcome.isFailure -> Column(
|
outcome.isFailure -> Column(
|
||||||
modifier = Modifier.fillMaxSize().padding(24.dp),
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colorScheme.background)
|
||||||
|
.padding(24.dp),
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -8,7 +8,9 @@ import androidx.compose.material3.lightColorScheme
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
|
import androidx.core.graphics.drawable.toDrawable
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import com.ganjoor.android.ui.Language
|
import com.ganjoor.android.ui.Language
|
||||||
import com.ganjoor.android.ui.ThemeMode
|
import com.ganjoor.android.ui.ThemeMode
|
||||||
@ -146,6 +148,20 @@ private val SepiaDarkScheme = darkColorScheme(
|
|||||||
outline = Color(0xFF968A72),
|
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
|
@Composable
|
||||||
fun GanjoorTheme(mode: ThemeMode, language: Language, content: @Composable () -> Unit) {
|
fun GanjoorTheme(mode: ThemeMode, language: Language, content: @Composable () -> Unit) {
|
||||||
val dark = when (mode) {
|
val dark = when (mode) {
|
||||||
@ -164,6 +180,9 @@ fun GanjoorTheme(mode: ThemeMode, language: Language, content: @Composable () ->
|
|||||||
if (!view.isInEditMode) {
|
if (!view.isInEditMode) {
|
||||||
SideEffect {
|
SideEffect {
|
||||||
val window = (view.context as Activity).window
|
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 {
|
WindowCompat.getInsetsController(window, view).apply {
|
||||||
isAppearanceLightStatusBars = !dark
|
isAppearanceLightStatusBars = !dark
|
||||||
isAppearanceLightNavigationBars = !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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="purple_200">#FFBB86FC</color>
|
<!-- Matches LightScheme.background in Theme.kt. -->
|
||||||
<color name="purple_500">#FF6200EE</color>
|
<color name="window_background">#FAFDFC</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>
|
|
||||||
</resources>
|
</resources>
|
||||||
@ -1,5 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<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>
|
</resources>
|
||||||
Loading…
Reference in New Issue
Block a user