diff --git a/app/src/main/java/com/bitchat/android/BitchatApplication.kt b/app/src/main/java/com/bitchat/android/BitchatApplication.kt index 5564399b..f0d15923 100644 --- a/app/src/main/java/com/bitchat/android/BitchatApplication.kt +++ b/app/src/main/java/com/bitchat/android/BitchatApplication.kt @@ -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) } } diff --git a/app/src/main/java/com/bitchat/android/MainActivity.kt b/app/src/main/java/com/bitchat/android/MainActivity.kt index 83be2978..30ff742e 100644 --- a/app/src/main/java/com/bitchat/android/MainActivity.kt +++ b/app/src/main/java/com/bitchat/android/MainActivity.kt @@ -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 diff --git a/app/src/main/java/com/bitchat/android/ui/AboutSheet.kt b/app/src/main/java/com/bitchat/android/ui/AboutSheet.kt index 00102727..a22bc114 100644 --- a/app/src/main/java/com/bitchat/android/ui/AboutSheet.kt +++ b/app/src/main/java/com/bitchat/android/ui/AboutSheet.kt @@ -160,6 +160,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 { diff --git a/app/src/main/java/com/bitchat/android/ui/InputComponents.kt b/app/src/main/java/com/bitchat/android/ui/InputComponents.kt index ba45f9f8..6ebeb21d 100644 --- a/app/src/main/java/com/bitchat/android/ui/InputComponents.kt +++ b/app/src/main/java/com/bitchat/android/ui/InputComponents.kt @@ -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 diff --git a/app/src/main/java/com/bitchat/android/ui/theme/Theme.kt b/app/src/main/java/com/bitchat/android/ui/theme/Theme.kt index bd6fc793..31dbc80a 100644 --- a/app/src/main/java/com/bitchat/android/ui/theme/Theme.kt +++ b/app/src/main/java/com/bitchat/android/ui/theme/Theme.kt @@ -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, diff --git a/app/src/main/java/com/bitchat/android/ui/theme/ThemePreference.kt b/app/src/main/java/com/bitchat/android/ui/theme/ThemePreference.kt new file mode 100644 index 00000000..9e00bde9 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/ui/theme/ThemePreference.kt @@ -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 = _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 + } +}