Skip to content

Commit fbfaaf9

Browse files
Dark mode support (#288)
* Remove duplicate link addition endpoint from LocalServerRepositoryImpl * Add theme preference functionality with selection dialog
1 parent c8f5b78 commit fbfaaf9

File tree

6 files changed

+149
-3
lines changed

6 files changed

+149
-3
lines changed

app/src/main/java/com/yogeshpaliyal/deepr/MainActivity.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ class MainActivity : ComponentActivity() {
8080
getLinkFromIntent(intent)
8181

8282
setContent {
83-
DeeprTheme {
83+
val preferenceDataStore = remember { AppPreferenceDataStore(this) }
84+
val themeMode by preferenceDataStore.getThemeMode.collectAsStateWithLifecycle(initialValue = "system")
85+
86+
DeeprTheme(themeMode = themeMode) {
8487
Surface {
8588
val sharedText by sharingLink.collectAsStateWithLifecycle()
8689
Dashboard(sharedText = sharedText) {

app/src/main/java/com/yogeshpaliyal/deepr/preference/AppPreferenceDataStore.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class AppPreferenceDataStore(
3434
private val IS_THUMBNAIL_ENABLE = booleanPreferencesKey("is_thumbnail_enable")
3535
private val SERVER_PORT = stringPreferencesKey("server_port")
3636
private val VIEW_TYPE = intPreferencesKey("view_type")
37+
private val THEME_MODE = stringPreferencesKey("theme_mode")
3738
}
3839

3940
val getSortingOrder: Flow<@SortType String> =
@@ -101,6 +102,11 @@ class AppPreferenceDataStore(
101102
preferences[SERVER_PORT] ?: "" // Default to empty string
102103
}
103104

105+
val getThemeMode: Flow<String> =
106+
context.appDataStore.data.map { preferences ->
107+
preferences[THEME_MODE] ?: "light" // Default to light theme
108+
}
109+
104110
suspend fun setSortingOrder(order: @SortType String) {
105111
context.appDataStore.edit { prefs ->
106112
prefs[SORTING_ORDER] = order
@@ -184,4 +190,10 @@ class AppPreferenceDataStore(
184190
prefs[SERVER_PORT] = port
185191
}
186192
}
193+
194+
suspend fun setThemeMode(mode: String) {
195+
context.appDataStore.edit { prefs ->
196+
prefs[THEME_MODE] = mode
197+
}
198+
}
187199
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.yogeshpaliyal.deepr.ui.components
2+
3+
import androidx.compose.foundation.clickable
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.Row
6+
import androidx.compose.foundation.layout.fillMaxWidth
7+
import androidx.compose.foundation.layout.padding
8+
import androidx.compose.material3.AlertDialog
9+
import androidx.compose.material3.MaterialTheme
10+
import androidx.compose.material3.RadioButton
11+
import androidx.compose.material3.Text
12+
import androidx.compose.material3.TextButton
13+
import androidx.compose.runtime.Composable
14+
import androidx.compose.ui.Alignment
15+
import androidx.compose.ui.Modifier
16+
import androidx.compose.ui.unit.dp
17+
18+
@Composable
19+
fun ThemeSelectionDialog(
20+
currentThemeMode: String,
21+
onThemeSelect: (String) -> Unit,
22+
onDismiss: () -> Unit,
23+
) {
24+
val themeOptions =
25+
listOf(
26+
"system" to "System default",
27+
"light" to "Light",
28+
"dark" to "Dark",
29+
)
30+
31+
AlertDialog(
32+
onDismissRequest = onDismiss,
33+
title = {
34+
Text(
35+
text = "Select Theme",
36+
style = MaterialTheme.typography.headlineSmall,
37+
)
38+
},
39+
text = {
40+
Column {
41+
themeOptions.forEach { (mode, label) ->
42+
Row(
43+
modifier =
44+
Modifier
45+
.fillMaxWidth()
46+
.clickable { onThemeSelect(mode) }
47+
.padding(vertical = 8.dp),
48+
verticalAlignment = Alignment.CenterVertically,
49+
) {
50+
RadioButton(
51+
selected = currentThemeMode == mode,
52+
onClick = { onThemeSelect(mode) },
53+
)
54+
Text(
55+
text = label,
56+
modifier = Modifier.padding(start = 8.dp),
57+
style = MaterialTheme.typography.bodyLarge,
58+
)
59+
}
60+
}
61+
}
62+
},
63+
confirmButton = {
64+
TextButton(onClick = onDismiss) {
65+
Text("Close")
66+
}
67+
},
68+
)
69+
}

app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/Settings.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import com.yogeshpaliyal.deepr.MainActivity
5151
import com.yogeshpaliyal.deepr.R
5252
import com.yogeshpaliyal.deepr.ui.components.LanguageSelectionDialog
5353
import com.yogeshpaliyal.deepr.ui.components.ServerStatusBar
54+
import com.yogeshpaliyal.deepr.ui.components.ThemeSelectionDialog
5455
import com.yogeshpaliyal.deepr.util.LanguageUtil
5556
import com.yogeshpaliyal.deepr.viewmodel.AccountViewModel
5657
import compose.icons.TablerIcons
@@ -60,6 +61,7 @@ import compose.icons.tablericons.ChevronRight
6061
import compose.icons.tablericons.Download
6162
import compose.icons.tablericons.InfoCircle
6263
import compose.icons.tablericons.Language
64+
import compose.icons.tablericons.Moon
6365
import compose.icons.tablericons.Photo
6466
import compose.icons.tablericons.Server
6567
import compose.icons.tablericons.Settings
@@ -86,6 +88,10 @@ fun SettingsScreen(
8688
val languageCode by viewModel.languageCode.collectAsStateWithLifecycle()
8789
var showLanguageDialog by remember { mutableStateOf(false) }
8890

91+
// Collect theme preference state
92+
val themeMode by viewModel.themeMode.collectAsStateWithLifecycle()
93+
var showThemeDialog by remember { mutableStateOf(false) }
94+
8995
// Collect default page preference state
9096
val defaultPageFavourites by viewModel.defaultPageFavouritesEnabled.collectAsStateWithLifecycle()
9197
val isThumbnailEnable by viewModel.isThumbnailEnable.collectAsStateWithLifecycle()
@@ -205,6 +211,20 @@ fun SettingsScreen(
205211
},
206212
)
207213

214+
SettingsItem(
215+
TablerIcons.Moon,
216+
title = "Theme",
217+
description =
218+
when (themeMode) {
219+
"light" -> "Light"
220+
"dark" -> "Dark"
221+
else -> "System default"
222+
},
223+
onClick = {
224+
showThemeDialog = true
225+
},
226+
)
227+
208228
SettingsItem(
209229
TablerIcons.Star,
210230
title = stringResource(R.string.default_page),
@@ -353,6 +373,18 @@ fun SettingsScreen(
353373
onDismiss = { showLanguageDialog = false },
354374
)
355375
}
376+
377+
// Theme Selection Dialog
378+
if (showThemeDialog) {
379+
ThemeSelectionDialog(
380+
currentThemeMode = themeMode,
381+
onThemeSelect = { selectedTheme ->
382+
viewModel.setThemeMode(selectedTheme)
383+
showThemeDialog = false
384+
},
385+
onDismiss = { showThemeDialog = false },
386+
)
387+
}
356388
}
357389
}
358390

app/src/main/java/com/yogeshpaliyal/deepr/ui/theme/Theme.kt

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import androidx.compose.material3.dynamicDarkColorScheme
88
import androidx.compose.material3.dynamicLightColorScheme
99
import androidx.compose.material3.lightColorScheme
1010
import androidx.compose.runtime.Composable
11+
import androidx.compose.runtime.SideEffect
1112
import androidx.compose.ui.platform.LocalContext
13+
import androidx.compose.ui.platform.LocalView
14+
import androidx.core.view.WindowCompat
1215

1316
private val DarkColorScheme =
1417
darkColorScheme(
@@ -27,21 +30,37 @@ private val LightColorScheme =
2730
@Composable
2831
fun DeeprTheme(
2932
darkTheme: Boolean = isSystemInDarkTheme(),
33+
themeMode: String = "system",
3034
// Dynamic color is available on Android 12+
3135
dynamicColor: Boolean = true,
3236
content: @Composable () -> Unit,
3337
) {
38+
val useDarkTheme =
39+
when (themeMode) {
40+
"light" -> false
41+
"dark" -> true
42+
else -> darkTheme // "system" or any other value defaults to system
43+
}
44+
3445
val colorScheme =
3546
when {
3647
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
3748
val context = LocalContext.current
38-
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
49+
if (useDarkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
3950
}
4051

41-
darkTheme -> DarkColorScheme
52+
useDarkTheme -> DarkColorScheme
4253
else -> LightColorScheme
4354
}
4455

56+
val view = LocalView.current
57+
if (!view.isInEditMode) {
58+
SideEffect {
59+
val window = (view.context as? android.app.Activity)?.window ?: return@SideEffect
60+
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !useDarkTheme
61+
}
62+
}
63+
4564
MaterialTheme(
4665
colorScheme = colorScheme,
4766
typography = Typography,

app/src/main/java/com/yogeshpaliyal/deepr/viewmodel/AccountViewModel.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,17 @@ class AccountViewModel(
573573
}
574574
}
575575

576+
// Theme preference methods
577+
val themeMode =
578+
preferenceDataStore.getThemeMode
579+
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), "system")
580+
581+
fun setThemeMode(mode: String) {
582+
viewModelScope.launch(Dispatchers.IO) {
583+
preferenceDataStore.setThemeMode(mode)
584+
}
585+
}
586+
576587
// Auto backup preference methods
577588
val autoBackupEnabled =
578589
preferenceDataStore.getAutoBackupEnabled

0 commit comments

Comments
 (0)