mirror of
https://github.com/rt-bishop/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Added light theme option suitable for e-ink devices (#256)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
de3d5a6d84
commit
b7ac9c913a
7 files changed
+72
-10
No files matched your search
@@ -27,6 +27,8 @@ import android.view.View
|
|||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.rtbishop.look4sat.core.domain.repository.IContainerProvider
|
import com.rtbishop.look4sat.core.domain.repository.IContainerProvider
|
||||||
@@ -48,8 +50,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
observeNightFilterState()
|
observeNightFilterState()
|
||||||
|
val settingsRepo = (applicationContext as IContainerProvider).getMainContainer().settingsRepo
|
||||||
setContent {
|
setContent {
|
||||||
MainTheme(isDarkTheme = true) { NavRoot() }
|
val otherSettings by settingsRepo.otherSettings.collectAsState()
|
||||||
|
MainTheme(isDarkTheme = !otherSettings.stateOfLightTheme) { NavRoot() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -61,8 +61,8 @@ fun MainTheme(isDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable
|
|||||||
SideEffect {
|
SideEffect {
|
||||||
val window = (view.context as ComponentActivity).window
|
val window = (view.context as ComponentActivity).window
|
||||||
val insetsController = WindowCompat.getInsetsController(window, view)
|
val insetsController = WindowCompat.getInsetsController(window, view)
|
||||||
insetsController.isAppearanceLightStatusBars = false
|
insetsController.isAppearanceLightStatusBars = !isDarkTheme
|
||||||
insetsController.isAppearanceLightNavigationBars = false
|
insetsController.isAppearanceLightNavigationBars = !isDarkTheme
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
window.isNavigationBarContrastEnforced = false
|
window.isNavigationBarContrastEnforced = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -266,6 +266,7 @@
|
|||||||
<string name="prefs_other_switch_sweep">Radar sweep</string>
|
<string name="prefs_other_switch_sweep">Radar sweep</string>
|
||||||
<string name="prefs_other_switch_sensors">Use sensors</string>
|
<string name="prefs_other_switch_sensors">Use sensors</string>
|
||||||
<string name="prefs_other_switch_night_mode">Night filter</string>
|
<string name="prefs_other_switch_night_mode">Night filter</string>
|
||||||
|
<string name="prefs_other_switch_light_theme">Light theme</string>
|
||||||
<string name="prefs_other_compass_offset">Radar compass offset</string>
|
<string name="prefs_other_compass_offset">Radar compass offset</string>
|
||||||
<string name="prefs_other_compass_offset_elev">Radar compass offset (elev)</string>
|
<string name="prefs_other_compass_offset_elev">Radar compass offset (elev)</string>
|
||||||
|
|
||||||
|
|||||||
@@ -122,6 +122,39 @@ private val moonIconPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|||||||
android.graphics.PorterDuffColorFilter("#E0E0E0".toColorInt(), android.graphics.PorterDuff.Mode.SRC_IN)
|
android.graphics.PorterDuffColorFilter("#E0E0E0".toColorInt(), android.graphics.PorterDuff.Mode.SRC_IN)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Accent used for the station marker; recolored per theme so it stays visible on a light map. */
|
||||||
|
private var mapAccentColor = "#FFE082".toColorInt()
|
||||||
|
|
||||||
|
/** The dark-map filter (grayscale + invert) is only applied when NOT in light theme; cached to avoid rebuilds. */
|
||||||
|
private val darkTileFilter by lazy { createColorFilter() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recolor the shared overlay paints for the current theme. The light OSM tiles are white, so the
|
||||||
|
* dark-theme amber/grey overlays would vanish; light theme swaps them for dark, high-contrast colors.
|
||||||
|
* Must run before the set*() overlay builders on each frame, as several icons cache the paint color.
|
||||||
|
*/
|
||||||
|
private fun applyMapColors(isLightUi: Boolean) {
|
||||||
|
if (isLightUi) {
|
||||||
|
mapAccentColor = "#715C0C".toColorInt() // dark amber (matches app lightScheme primary)
|
||||||
|
footprintPaint.color = mapAccentColor
|
||||||
|
textPaint.color = "#1E1B13".toColorInt()
|
||||||
|
textPaint.setShadowLayer(3f, 3f, 3f, Color.WHITE)
|
||||||
|
sunIconPaint.colorFilter =
|
||||||
|
android.graphics.PorterDuffColorFilter(mapAccentColor, android.graphics.PorterDuff.Mode.SRC_IN)
|
||||||
|
moonIconPaint.colorFilter =
|
||||||
|
android.graphics.PorterDuffColorFilter("#4C4639".toColorInt(), android.graphics.PorterDuff.Mode.SRC_IN)
|
||||||
|
} else {
|
||||||
|
mapAccentColor = "#FFE082".toColorInt()
|
||||||
|
footprintPaint.color = mapAccentColor
|
||||||
|
textPaint.color = mapAccentColor
|
||||||
|
textPaint.setShadowLayer(3f, 3f, 3f, Color.BLACK)
|
||||||
|
sunIconPaint.colorFilter =
|
||||||
|
android.graphics.PorterDuffColorFilter(mapAccentColor, android.graphics.PorterDuff.Mode.SRC_IN)
|
||||||
|
moonIconPaint.colorFilter =
|
||||||
|
android.graphics.PorterDuffColorFilter("#E0E0E0".toColorInt(), android.graphics.PorterDuff.Mode.SRC_IN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MapDestination() {
|
fun MapDestination() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -177,6 +210,8 @@ private fun MapScreen(uiState: MapState, onAction: (MapAction) -> Unit, mapView:
|
|||||||
ElevatedCard(modifier = Modifier.weight(1f)) {
|
ElevatedCard(modifier = Modifier.weight(1f)) {
|
||||||
Box(contentAlignment = Alignment.BottomCenter) {
|
Box(contentAlignment = Alignment.BottomCenter) {
|
||||||
AndroidView({ mapView }) { view ->
|
AndroidView({ mapView }) { view ->
|
||||||
|
applyMapColors(uiState.isLightUi)
|
||||||
|
view.overlayManager.tilesOverlay.setColorFilter(if (uiState.isLightUi) null else darkTileFilter)
|
||||||
uiState.stationPosition?.let { setStationPosition(it, view) }
|
uiState.stationPosition?.let { setStationPosition(it, view) }
|
||||||
uiState.track?.let { setSatelliteTrack(it, view) }
|
uiState.track?.let { setSatelliteTrack(it, view) }
|
||||||
uiState.footprint?.let { setFootprint(it, view) }
|
uiState.footprint?.let { setFootprint(it, view) }
|
||||||
@@ -288,7 +323,9 @@ private fun setStationPosition(stationPos: GeoPos, mapView: MapView) {
|
|||||||
mapView.overlays[OVERLAY_STATION] = Marker(mapView).apply {
|
mapView.overlays[OVERLAY_STATION] = Marker(mapView).apply {
|
||||||
setInfoWindow(null)
|
setInfoWindow(null)
|
||||||
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
||||||
icon = ContextCompat.getDrawable(mapView.context, R.drawable.ic_position)
|
icon = ContextCompat.getDrawable(mapView.context, R.drawable.ic_position)?.apply {
|
||||||
|
setTint(mapAccentColor)
|
||||||
|
}
|
||||||
position = GeoPoint(stationPos.latitude, stationPos.longitude)
|
position = GeoPoint(stationPos.latitude, stationPos.longitude)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -585,7 +622,7 @@ private fun rememberMapViewWithLifecycle(): MapView {
|
|||||||
zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER)
|
zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER)
|
||||||
overlayManager.tilesOverlay.loadingBackgroundColor = Color.TRANSPARENT
|
overlayManager.tilesOverlay.loadingBackgroundColor = Color.TRANSPARENT
|
||||||
overlayManager.tilesOverlay.loadingLineColor = Color.TRANSPARENT
|
overlayManager.tilesOverlay.loadingLineColor = Color.TRANSPARENT
|
||||||
overlayManager.tilesOverlay.setColorFilter(createColorFilter())
|
// Tile color filter (dark inversion vs light) is applied per-theme in the AndroidView update block.
|
||||||
setScrollableAreaLimitLatitude(maxLat, minLat, 0)
|
setScrollableAreaLimitLatitude(maxLat, minLat, 0)
|
||||||
overlays.addAll(Array(OVERLAY_COUNT) { FolderOverlay() })
|
overlays.addAll(Array(OVERLAY_COUNT) { FolderOverlay() })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class MapViewModel(
|
|||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
settingsRepo.otherSettings.collectLatest { settings ->
|
settingsRepo.otherSettings.collectLatest { settings ->
|
||||||
_uiState.update { it.copy(isUtc = settings.stateOfUtc) }
|
_uiState.update { it.copy(isUtc = settings.stateOfUtc, isLightUi = settings.stateOfLightTheme) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val (selectedCatNum, _) = satelliteRepo.selectedPass.value
|
val (selectedCatNum, _) = satelliteRepo.selectedPass.value
|
||||||
|
|||||||
+20
-3
@@ -495,12 +495,24 @@ private fun OtherCard(settings: OtherSettings, onAction: (SettingsAction) -> Uni
|
|||||||
color = MaterialTheme.colorScheme.primary
|
color = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
// Appearance: light theme (paired with a spacer to align with the two-column rows)
|
||||||
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
SwitchTile(R.string.prefs_other_switch_light_theme, settings.stateOfLightTheme) {
|
||||||
|
onAction(SettingsAction.ToggleLightTheme(it))
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
// Display preferences: UTC clock + night filter
|
// Display preferences: UTC clock + night filter
|
||||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
SwitchTile(R.string.prefs_other_switch_utc, settings.stateOfUtc) {
|
SwitchTile(R.string.prefs_other_switch_utc, settings.stateOfUtc) {
|
||||||
onAction(SettingsAction.ToggleUtc(it))
|
onAction(SettingsAction.ToggleUtc(it))
|
||||||
}
|
}
|
||||||
SwitchTile(R.string.prefs_other_switch_night_mode, settings.stateOfNightMode) {
|
SwitchTile(
|
||||||
|
R.string.prefs_other_switch_night_mode,
|
||||||
|
settings.stateOfNightMode,
|
||||||
|
enabled = !settings.stateOfLightTheme
|
||||||
|
) {
|
||||||
onAction(SettingsAction.ToggleNightMode(it))
|
onAction(SettingsAction.ToggleNightMode(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -573,7 +585,12 @@ private fun SwitchRow(labelResId: Int, checked: Boolean, onCheckedChange: (Boole
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RowScope.SwitchTile(labelResId: Int, checked: Boolean, onCheckedChange: (Boolean) -> Unit) {
|
private fun RowScope.SwitchTile(
|
||||||
|
labelResId: Int,
|
||||||
|
checked: Boolean,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
onCheckedChange: (Boolean) -> Unit
|
||||||
|
) {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
@@ -583,7 +600,7 @@ private fun RowScope.SwitchTile(labelResId: Int, checked: Boolean, onCheckedChan
|
|||||||
text = stringResource(id = labelResId),
|
text = stringResource(id = labelResId),
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
Switch(checked = checked, onCheckedChange = onCheckedChange)
|
Switch(checked = checked, enabled = enabled, onCheckedChange = onCheckedChange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -127,7 +127,10 @@ class SettingsViewModel(
|
|||||||
is SettingsAction.ToggleUpdate -> settingsRepo.updateOtherSettings { it.copy(stateOfAutoUpdate = action.value) }
|
is SettingsAction.ToggleUpdate -> settingsRepo.updateOtherSettings { it.copy(stateOfAutoUpdate = action.value) }
|
||||||
is SettingsAction.ToggleSweep -> settingsRepo.updateOtherSettings { it.copy(stateOfSweep = action.value) }
|
is SettingsAction.ToggleSweep -> settingsRepo.updateOtherSettings { it.copy(stateOfSweep = action.value) }
|
||||||
is SettingsAction.ToggleSensor -> settingsRepo.updateOtherSettings { it.copy(stateOfSensors = action.value) }
|
is SettingsAction.ToggleSensor -> settingsRepo.updateOtherSettings { it.copy(stateOfSensors = action.value) }
|
||||||
is SettingsAction.ToggleLightTheme -> settingsRepo.updateOtherSettings { it.copy(stateOfLightTheme = action.value) }
|
is SettingsAction.ToggleLightTheme -> settingsRepo.updateOtherSettings {
|
||||||
|
// Night filter (red-only overlay) is incompatible with the light theme, so turn it off.
|
||||||
|
it.copy(stateOfLightTheme = action.value, stateOfNightMode = if (action.value) false else it.stateOfNightMode)
|
||||||
|
}
|
||||||
is SettingsAction.ToggleNightMode -> settingsRepo.updateOtherSettings { it.copy(stateOfNightMode = action.value) }
|
is SettingsAction.ToggleNightMode -> settingsRepo.updateOtherSettings { it.copy(stateOfNightMode = action.value) }
|
||||||
is SettingsAction.SetRadarCompassOffset -> settingsRepo.updateOtherSettings { it.copy(radarCompassOffset = action.value) }
|
is SettingsAction.SetRadarCompassOffset -> settingsRepo.updateOtherSettings { it.copy(radarCompassOffset = action.value) }
|
||||||
is SettingsAction.SetRadarCompassOffsetElev -> settingsRepo.updateOtherSettings { it.copy(radarCompassOffsetElev = action.value) }
|
is SettingsAction.SetRadarCompassOffsetElev -> settingsRepo.updateOtherSettings { it.copy(radarCompassOffsetElev = action.value) }
|
||||||
|
|||||||
Reference in new issue
Block a user