-
Notifications
You must be signed in to change notification settings - Fork 0
Android: Fix status bar visibility and add theme selector #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d12ccb8
cb0e500
9357049
f930525
58516f1
013e474
c21ac77
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| package llc.lookatwhataicando.codeoba.android | ||
|
|
||
| import android.content.Context | ||
| import android.content.SharedPreferences | ||
| import androidx.compose.runtime.Composable | ||
| import androidx.compose.runtime.collectAsState | ||
| import androidx.compose.runtime.getValue | ||
| import kotlinx.coroutines.flow.MutableStateFlow | ||
| import kotlinx.coroutines.flow.StateFlow | ||
| import kotlinx.coroutines.flow.asStateFlow | ||
|
|
||
| /** | ||
| * Theme mode options for the app. | ||
| */ | ||
| enum class ThemeMode { | ||
| LIGHT, | ||
| DARK, | ||
| SYSTEM | ||
| } | ||
|
|
||
| /** | ||
| * Manages theme preferences for the application. | ||
| * Stores user's theme selection and provides reactive state updates. | ||
| */ | ||
| class ThemePreferenceManager(context: Context) { | ||
| private val prefs: SharedPreferences = context.getSharedPreferences( | ||
| "theme_prefs", | ||
| Context.MODE_PRIVATE | ||
| ) | ||
|
|
||
| private val _themeMode = MutableStateFlow(loadThemeMode()) | ||
| val themeMode: StateFlow<ThemeMode> = _themeMode.asStateFlow() | ||
|
|
||
| companion object { | ||
| private const val KEY_THEME_MODE = "theme_mode" | ||
| } | ||
|
|
||
| private fun loadThemeMode(): ThemeMode { | ||
| val savedMode = prefs.getString(KEY_THEME_MODE, ThemeMode.SYSTEM.name) | ||
| return try { | ||
| ThemeMode.valueOf(savedMode ?: ThemeMode.SYSTEM.name) | ||
| } catch (e: IllegalArgumentException) { | ||
| ThemeMode.SYSTEM | ||
| } | ||
| } | ||
|
|
||
| fun setThemeMode(mode: ThemeMode) { | ||
| prefs.edit().putString(KEY_THEME_MODE, mode.name).apply() | ||
| _themeMode.value = mode | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Composable helper to get the current theme mode. | ||
| */ | ||
| @Composable | ||
| fun ThemePreferenceManager.currentThemeMode(): ThemeMode { | ||
| val mode by themeMode.collectAsState() | ||
| return mode | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <resources> | ||
| <!-- Dark theme using Material3 with dynamic colors --> | ||
| <style name="Theme.Codeoba" parent="android:Theme.Material.NoActionBar"> | ||
| <!-- Enable edge-to-edge display --> | ||
| <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> | ||
| <!-- Status bar configuration for dark mode --> | ||
| <item name="android:statusBarColor">@android:color/transparent</item> | ||
| <!-- Use light icons (white) on dark background --> | ||
| <item name="android:windowLightStatusBar">false</item> | ||
| <!-- Navigation bar configuration --> | ||
| <item name="android:navigationBarColor">@android:color/transparent</item> | ||
| <item name="android:windowLightNavigationBar">false</item> | ||
| <!-- Add status bar scrim for better contrast --> | ||
| <item name="android:enforceStatusBarContrast">true</item> | ||
| </style> | ||
| </resources> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,16 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <resources> | ||
| <!-- Base application theme using Material3 --> | ||
| <!-- Base application theme using Material3 with dynamic colors --> | ||
| <style name="Theme.Codeoba" parent="android:Theme.Material.Light.NoActionBar"> | ||
| <item name="android:statusBarColor">@color/ic_launcher_background</item> | ||
| <!-- Enable edge-to-edge display --> | ||
| <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> | ||
| <!-- Status bar configuration for light mode --> | ||
| <item name="android:statusBarColor">@android:color/transparent</item> | ||
| <item name="android:windowLightStatusBar">true</item> | ||
| <!-- Navigation bar configuration --> | ||
| <item name="android:navigationBarColor">@android:color/transparent</item> | ||
| <item name="android:windowLightNavigationBar">true</item> | ||
| <!-- Add status bar scrim for better contrast --> | ||
| <item name="android:enforceStatusBarContrast">true</item> | ||
|
Comment on lines
+5
to
+14
|
||
| </style> | ||
| </resources> | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,10 +7,12 @@ import androidx.compose.foundation.layout.Arrangement | |||||||
| import androidx.compose.foundation.layout.Box | ||||||||
| import androidx.compose.foundation.layout.Column | ||||||||
| import androidx.compose.foundation.layout.Row | ||||||||
| import androidx.compose.foundation.layout.WindowInsets | ||||||||
| import androidx.compose.foundation.layout.fillMaxSize | ||||||||
| import androidx.compose.foundation.layout.fillMaxWidth | ||||||||
| import androidx.compose.foundation.layout.height | ||||||||
| import androidx.compose.foundation.layout.padding | ||||||||
| import androidx.compose.foundation.layout.size | ||||||||
| import androidx.compose.foundation.lazy.LazyColumn | ||||||||
| import androidx.compose.foundation.lazy.items | ||||||||
| import androidx.compose.foundation.shape.RoundedCornerShape | ||||||||
|
|
@@ -34,13 +36,17 @@ import androidx.compose.material3.ModalNavigationDrawer | |||||||
| import androidx.compose.material3.OutlinedTextField | ||||||||
| import androidx.compose.material3.RadioButton | ||||||||
| import androidx.compose.material3.Scaffold | ||||||||
| import androidx.compose.material3.SegmentedButton | ||||||||
| import androidx.compose.material3.SegmentedButtonDefaults | ||||||||
| import androidx.compose.material3.SingleChoiceSegmentedButtonRow | ||||||||
| import androidx.compose.material3.Surface | ||||||||
| import androidx.compose.material3.Switch | ||||||||
| import androidx.compose.material3.Tab | ||||||||
| import androidx.compose.material3.PrimaryTabRow | ||||||||
| import androidx.compose.material3.Text | ||||||||
| import androidx.compose.material3.TextButton | ||||||||
| import androidx.compose.material3.TopAppBar | ||||||||
| import androidx.compose.material3.TopAppBarDefaults | ||||||||
|
||||||||
| import androidx.compose.material3.TopAppBarDefaults |
Copilot
AI
Dec 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The icon parameter is set to an empty lambda {}, which creates an empty composable slot. According to Material 3 SegmentedButton best practices, if you don't want to display an icon, you should omit the parameter entirely rather than passing an empty lambda. This can cause unnecessary recomposition overhead and doesn't follow the intended API design.
| ), | |
| icon = {} | |
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error handling here logs the error and shows a snackbar, which is good. However, the error message "Invalid theme mode: $modeName" would only occur if there's a programming error (mismatch between the UI theme mode strings and the ThemeMode enum values). Since the ThemeSelector component uses the same string values as the enum names ("LIGHT", "DARK", "SYSTEM"), this error should never occur in normal operation. Consider adding an assertion or removing this error handling since it represents an internal inconsistency that should be caught during development rather than at runtime.