allow user to select a theme preference over light/dark/system (#318)

This commit is contained in:
lollerfirst
2025-08-28 08:14:46 +02:00
committed by GitHub
parent 846cd976c0
commit 28abd3c593
6 changed files with 94 additions and 8 deletions
@@ -2,6 +2,7 @@ package com.bitchat.android
import android.app.Application
import com.bitchat.android.nostr.RelayDirectory
import com.bitchat.android.ui.theme.ThemePreferenceManager
/**
* Main application class for bitchat Android
@@ -23,5 +24,8 @@ class BitchatApplication : Application() {
try {
com.bitchat.android.nostr.NostrIdentityBridge.getCurrentNostrIdentity(this)
} catch (_: Exception) { }
// Initialize theme preference
ThemePreferenceManager.init(this)
}
}
@@ -10,13 +10,11 @@ import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.core.view.WindowCompat
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.lifecycleScope
@@ -42,6 +40,8 @@ import com.bitchat.android.onboarding.PermissionManager
import com.bitchat.android.ui.ChatScreen
import com.bitchat.android.ui.ChatViewModel
import com.bitchat.android.ui.theme.BitchatTheme
import com.bitchat.android.ui.theme.ThemePreference
import com.bitchat.android.ui.theme.ThemePreferenceManager
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -161,6 +161,40 @@ fun AboutSheet(
}
}
// Appearance section (theme toggle)
item {
val themePref by com.bitchat.android.ui.theme.ThemePreferenceManager.themeFlow.collectAsState()
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "appearance",
fontSize = 12.sp,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Medium,
color = colorScheme.onSurface.copy(alpha = 0.8f)
)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
FilterChip(
selected = themePref == com.bitchat.android.ui.theme.ThemePreference.System,
onClick = { com.bitchat.android.ui.theme.ThemePreferenceManager.set(context, com.bitchat.android.ui.theme.ThemePreference.System) },
label = { Text("system", fontFamily = FontFamily.Monospace) }
)
FilterChip(
selected = themePref == com.bitchat.android.ui.theme.ThemePreference.Light,
onClick = { com.bitchat.android.ui.theme.ThemePreferenceManager.set(context, com.bitchat.android.ui.theme.ThemePreference.Light) },
label = { Text("light", fontFamily = FontFamily.Monospace) }
)
FilterChip(
selected = themePref == com.bitchat.android.ui.theme.ThemePreference.Dark,
onClick = { com.bitchat.android.ui.theme.ThemePreferenceManager.set(context, com.bitchat.android.ui.theme.ThemePreference.Dark) },
label = { Text("dark", fontFamily = FontFamily.Monospace) }
)
}
}
}
// Emergency warning
item {
Surface(
@@ -35,6 +35,7 @@ import com.bitchat.android.R
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.text.withStyle
import com.bitchat.android.ui.theme.BASE_FONT_SIZE
import androidx.compose.foundation.isSystemInDarkTheme
/**
* Input components for ChatScreen
@@ -5,6 +5,8 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
// Colors that match the iOS bitchat theme
@@ -36,14 +38,23 @@ private val LightColorScheme = lightColorScheme(
@Composable
fun BitchatTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
darkTheme: Boolean? = null,
content: @Composable () -> Unit
) {
val colorScheme = when {
darkTheme -> DarkColorScheme
else -> LightColorScheme
// App-level override from ThemePreferenceManager
val themePref by ThemePreferenceManager.themeFlow.collectAsState(initial = ThemePreference.System)
val shouldUseDark = when (darkTheme) {
true -> true
false -> false
null -> when (themePref) {
ThemePreference.Dark -> true
ThemePreference.Light -> false
ThemePreference.System -> isSystemInDarkTheme()
}
}
val colorScheme = if (shouldUseDark) DarkColorScheme else LightColorScheme
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
@@ -0,0 +1,36 @@
package com.bitchat.android.ui.theme
import android.content.Context
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
/**
* App theme preference: System default, Light, or Dark.
*/
enum class ThemePreference {
System, Light, Dark
}
/**
* Simple SharedPreferences-backed manager for theme preference with a StateFlow.
* Avoids adding DataStore dependency for now.
*/
object ThemePreferenceManager {
private const val PREFS_NAME = "bitchat_settings"
private const val KEY_THEME = "theme_preference"
private val _themeFlow = MutableStateFlow(ThemePreference.System)
val themeFlow: StateFlow<ThemePreference> = _themeFlow
fun init(context: Context) {
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
val saved = prefs.getString(KEY_THEME, ThemePreference.System.name)
_themeFlow.value = runCatching { ThemePreference.valueOf(saved!!) }.getOrDefault(ThemePreference.System)
}
fun set(context: Context, preference: ThemePreference) {
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
prefs.edit().putString(KEY_THEME, preference.name).apply()
_themeFlow.value = preference
}
}