Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fc309ebe authored by mitulsheth's avatar mitulsheth
Browse files

feat:When system is in dark mode ask the user which mode they want to use

parent b7943f18
Loading
Loading
Loading
Loading
Loading
+32 −1
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -54,6 +55,7 @@ import earth.maps.cardinal.tileserver.PermissionRequest
import earth.maps.cardinal.tileserver.PermissionRequestManager
import earth.maps.cardinal.ui.core.AppContent
import earth.maps.cardinal.ui.core.MapViewModel
import earth.maps.cardinal.ui.settings.ThemeModePromptBottomSheet
import earth.maps.cardinal.ui.theme.AppTheme
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -92,6 +94,7 @@ class MainActivity : ComponentActivity() {
    private var hasNotificationPermission by mutableStateOf(false)
    private var deepLinkDestination by mutableStateOf<String?>(null)
    private var showLocationPermissionDialog by mutableStateOf(false)
    private var isLocationPermissionFlowActive by mutableStateOf(false)

    companion object {
        private const val LOCATION_PERMISSION_REQUEST_CODE = 1001
@@ -126,6 +129,7 @@ class MainActivity : ComponentActivity() {
    }

    private fun requestLocationPermission() {
        isLocationPermissionFlowActive = true
        locationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
    }

@@ -151,6 +155,7 @@ class MainActivity : ComponentActivity() {
    private val locationPermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted ->
        isLocationPermissionFlowActive = false
        hasLocationPermission = isGranted
        if (isGranted) {
            locationRepository.startContinuousLocationUpdates(this@MainActivity)
@@ -205,6 +210,7 @@ class MainActivity : ComponentActivity() {
        // Check if we should show the location permission dialog on first startup
        if (!appPreferenceRepository.hasPromptedLocation.value && !hasLocationPermission) {
            showLocationPermissionDialog = true
            isLocationPermissionFlowActive = true
        }

        CoroutineScope(Dispatchers.IO).launch {
@@ -226,7 +232,17 @@ class MainActivity : ComponentActivity() {

        setContent {
            val contrastLevel by appPreferenceRepository.contrastLevel.collectAsState()
            AppTheme(contrastLevel = contrastLevel) {
            val themeMode by appPreferenceRepository.themeMode.collectAsState()
            val hasPromptedThemeMode by appPreferenceRepository.hasPromptedThemeMode.collectAsState()
            val systemInDarkTheme = isSystemInDarkTheme()
            val darkTheme = themeMode.shouldUseDarkTheme(systemInDarkTheme)
            val shouldShowThemePrompt =
                systemInDarkTheme &&
                    !hasPromptedThemeMode &&
                    !showLocationPermissionDialog &&
                    !isLocationPermissionFlowActive

            AppTheme(darkTheme = darkTheme, contrastLevel = contrastLevel) {
                val mapViewModel: MapViewModel = hiltViewModel()

                val navController = rememberNavController()
@@ -246,11 +262,13 @@ class MainActivity : ComponentActivity() {
                    hasLocationPermission = hasLocationPermission,
                    routeRepository = routeRepository,
                    appPreferenceRepository = appPreferenceRepository,
                    useDarkTheme = darkTheme,
                    onRequestNotificationPermission = { requestNotificationPermission() },
                    hasNotificationPermission = hasNotificationPermission,
                    showLocationPermissionDialog = showLocationPermissionDialog,
                    onDismissLocationDialog = {
                        showLocationPermissionDialog = false
                        isLocationPermissionFlowActive = false
                        appPreferenceRepository.setHasPromptedLocation(true)
                    },
                    onAcceptLocationDialog = {
@@ -259,6 +277,19 @@ class MainActivity : ComponentActivity() {
                        requestLocationPermission()
                    }
                )

                if (shouldShowThemePrompt) {
                    ThemeModePromptBottomSheet(
                        initialThemeMode = themeMode,
                        onDismiss = {
                            appPreferenceRepository.setHasPromptedThemeMode(true)
                        },
                        onSave = { selectedThemeMode ->
                            appPreferenceRepository.setThemeMode(selectedThemeMode)
                            appPreferenceRepository.setHasPromptedThemeMode(true)
                        }
                    )
                }
            }
        }
    }
+22 −0
Original line number Diff line number Diff line
@@ -57,6 +57,9 @@ class AppPreferenceRepository @Inject constructor(
    private val _distanceUnit = MutableStateFlow(AppPreferences.DISTANCE_UNIT_METRIC)
    val distanceUnit: StateFlow<Int> = _distanceUnit.asStateFlow()

    private val _themeMode = MutableStateFlow(ThemeMode.SYSTEM)
    val themeMode: StateFlow<ThemeMode> = _themeMode.asStateFlow()

    private val _allowTransitInOfflineMode = MutableStateFlow(true)
    val allowTransitInOfflineMode: StateFlow<Boolean> = _allowTransitInOfflineMode.asStateFlow()

@@ -76,6 +79,9 @@ class AppPreferenceRepository @Inject constructor(
    private val _hasPromptedLocation = MutableStateFlow(appPreferences.loadHasPromptedLocation())
    val hasPromptedLocation: StateFlow<Boolean> = _hasPromptedLocation.asStateFlow()

    private val _hasPromptedThemeMode = MutableStateFlow(appPreferences.loadHasPromptedThemeMode())
    val hasPromptedThemeMode: StateFlow<Boolean> = _hasPromptedThemeMode.asStateFlow()

    // Pelias API configuration
    private val _peliasApiConfig = MutableStateFlow(
        ApiConfiguration(
@@ -99,6 +105,7 @@ class AppPreferenceRepository @Inject constructor(
        loadAnimationSpeed()
        loadOfflineMode()
        loadDistanceUnit()
        loadThemeMode()
        loadAllowTransitInOfflineMode()
        loadContinuousLocationTracking()
        loadShowZoomFabs()
@@ -155,6 +162,16 @@ class AppPreferenceRepository @Inject constructor(
        _distanceUnit.value = distanceUnit
    }

    fun setThemeMode(themeMode: ThemeMode) {
        _themeMode.value = themeMode
        appPreferences.saveThemeMode(themeMode)
    }

    private fun loadThemeMode() {
        val themeMode = appPreferences.loadThemeMode()
        _themeMode.value = themeMode
    }

    private fun loadAllowTransitInOfflineMode() {
        val allowTransitInOfflineMode = appPreferences.loadAllowTransitInOfflineMode()
        _allowTransitInOfflineMode.value = allowTransitInOfflineMode
@@ -222,6 +239,11 @@ class AppPreferenceRepository @Inject constructor(
        }
    }

    fun setHasPromptedThemeMode(hasPrompted: Boolean) {
        _hasPromptedThemeMode.value = hasPrompted
        appPreferences.saveHasPromptedThemeMode(hasPrompted)
    }

    private fun loadApiConfigurations() {
        // Load Pelias configuration
        val peliasBaseUrl = appPreferences.loadPeliasBaseUrl()
+36 −1
Original line number Diff line number Diff line
@@ -48,6 +48,8 @@ class AppPreferences(private val context: Context) {
        private const val KEY_USE_24_HOUR_FORMAT = "use_24_hour_format"

        private const val KEY_HAS_PROMPTED_LOCATION = "has_prompted_location"
        private const val KEY_THEME_MODE = "theme_mode"
        private const val KEY_HAS_PROMPTED_THEME_MODE = "has_prompted_theme_mode"

        // API configuration keys
        private const val KEY_PELIAS_BASE_URL = "pelias_base_url"
@@ -75,6 +77,7 @@ class AppPreferences(private val context: Context) {
        // Distance unit constants
        const val DISTANCE_UNIT_METRIC = 0
        const val DISTANCE_UNIT_IMPERIAL = 1

    }

    /**
@@ -305,6 +308,38 @@ class AppPreferences(private val context: Context) {
        return prefs.getBoolean(KEY_HAS_PROMPTED_LOCATION, false)
    }

    fun saveThemeMode(themeMode: ThemeMode) {
        prefs.edit {
            putString(KEY_THEME_MODE, themeMode.preferenceValue)
        }
    }

    fun loadThemeMode(): ThemeMode {
        val themeMode = runCatching {
            prefs.getString(KEY_THEME_MODE, null)
        }.getOrNull()

        if (themeMode != null) {
            return ThemeMode.fromPreferenceValue(themeMode)
        }

        val legacyThemeMode = runCatching {
            prefs.getInt(KEY_THEME_MODE, -1)
        }.getOrNull()

        return legacyThemeMode?.let(ThemeMode::fromLegacyPreferenceValue) ?: ThemeMode.SYSTEM
    }

    fun saveHasPromptedThemeMode(hasPrompted: Boolean) {
        prefs.edit {
            putBoolean(KEY_HAS_PROMPTED_THEME_MODE, hasPrompted)
        }
    }

    fun loadHasPromptedThemeMode(): Boolean {
        return prefs.getBoolean(KEY_HAS_PROMPTED_THEME_MODE, false)
    }

    /**
     * Gets the default distance unit based on the system locale.
     * Returns imperial for countries that use imperial system (US, Liberia, Myanmar),
+43 −0
Original line number Diff line number Diff line
/*
 *     Cardinal Maps
 *     Copyright (C) 2026 Cardinal Maps Authors
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package earth.maps.cardinal.data

enum class ThemeMode(val preferenceValue: String) {
    LIGHT("light"),
    DARK("dark"),
    SYSTEM("system");

    fun shouldUseDarkTheme(systemInDarkTheme: Boolean): Boolean {
        return when (this) {
            LIGHT -> false
            DARK -> true
            SYSTEM -> systemInDarkTheme
        }
    }

    companion object {
        fun fromPreferenceValue(value: String?): ThemeMode {
            return entries.firstOrNull { it.preferenceValue == value } ?: SYSTEM
        }

        fun fromLegacyPreferenceValue(value: Int): ThemeMode? {
            return entries.getOrNull(value)
        }
    }
}
+9 −3
Original line number Diff line number Diff line
@@ -150,6 +150,7 @@ fun AppContent(
    hasNotificationPermission: Boolean,
    routeRepository: RouteRepository,
    appPreferenceRepository: AppPreferenceRepository,
    useDarkTheme: Boolean,
    showLocationPermissionDialog: Boolean = false,
    onDismissLocationDialog: () -> Unit = {},
    onAcceptLocationDialog: () -> Unit = {},
@@ -178,7 +179,8 @@ fun AppContent(
        droppedPinName = droppedPinName,
        onRequestLocationPermission = onRequestLocationPermission,
        hasLocationPermission = hasLocationPermission,
        appPreferenceRepository = appPreferenceRepository
        appPreferenceRepository = appPreferenceRepository,
        useDarkTheme = useDarkTheme
    )

    NavHost(
@@ -346,7 +348,7 @@ fun AppContent(
        }

        composable(Screen.TURN_BY_TURN) { backStackEntry ->
            TurnByTurnRoute(state, routeRepository, port, backStackEntry)
            TurnByTurnRoute(state, routeRepository, useDarkTheme, port, backStackEntry)
        }
    }

@@ -391,7 +393,8 @@ private fun MapViewContainer(
    droppedPinName: String,
    onRequestLocationPermission: () -> Unit,
    hasLocationPermission: Boolean,
    appPreferenceRepository: AppPreferenceRepository
    appPreferenceRepository: AppPreferenceRepository,
    useDarkTheme: Boolean
) {
    Box(
        modifier = Modifier
@@ -452,6 +455,7 @@ private fun MapViewContainer(
                cameraState = state.cameraState,
                mapPins = state.mapPins,
                appPreferences = appPreferenceRepository,
                useDarkTheme = useDarkTheme,
                selectedOfflineArea = state.selectedOfflineArea,
                currentRoute = state.currentRoute,
                allRoutes = state.allRoutes,
@@ -1095,6 +1099,7 @@ private fun TransitItineraryDetailRoute(
private fun TurnByTurnRoute(
    state: AppContentState,
    routeRepository: RouteRepository,
    useDarkTheme: Boolean,
    port: Int?,
    backStackEntry: NavBackStackEntry
) {
@@ -1120,6 +1125,7 @@ private fun TurnByTurnRoute(
            port = port,
            mode = routingMode,
            route = ferrostarRoute,
            useDarkTheme = useDarkTheme,
        )
    }
}
Loading