Skip to content

Commit e0b5616

Browse files
committed
use an interface for dark mode detector
1 parent a8fbcc6 commit e0b5616

5 files changed

Lines changed: 68 additions & 94 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.github.kdroidfilter.nucleus.darkmodedetector
2+
3+
import io.github.kdroidfilter.nucleus.core.runtime.Platform
4+
import io.github.kdroidfilter.nucleus.darkmodedetector.linux.MacOSThemeDetector
5+
import io.github.kdroidfilter.nucleus.darkmodedetector.mac.MacOSThemeDetector
6+
import io.github.kdroidfilter.nucleus.darkmodedetector.windows.WindowsThemeDetector
7+
import java.util.function.Consumer
8+
9+
interface IDarkModeDetector {
10+
fun isDark(): Boolean
11+
fun registerListener(listener: Consumer<Boolean>)
12+
fun removeListener(listener: Consumer<Boolean>)
13+
}
14+
15+
object NoopDarkModeDetector : IDarkModeDetector {
16+
override fun isDark(): Boolean = false
17+
override fun registerListener(listener: Consumer<Boolean>) = Unit
18+
override fun removeListener(listener: Consumer<Boolean>) = Unit
19+
}
20+
21+
public fun getPlatformDarkModeDetector(): IDarkModeDetector {
22+
return when (Platform.Current) {
23+
Platform.MacOS -> MacOSThemeDetector
24+
Platform.Windows -> WindowsThemeDetector
25+
Platform.Linux -> LinuxPortalThemeDetector
26+
else -> NoopDarkModeDetector
27+
}
28+
}
Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package io.github.kdroidfilter.nucleus.darkmodedetector
22

33
import androidx.compose.foundation.isSystemInDarkTheme
4-
import androidx.compose.runtime.Composable
54
import androidx.compose.ui.platform.LocalInspectionMode
6-
import io.github.kdroidfilter.nucleus.core.runtime.Platform
7-
import io.github.kdroidfilter.nucleus.darkmodedetector.linux.isLinuxInDarkMode
8-
import io.github.kdroidfilter.nucleus.darkmodedetector.mac.isMacOsInDarkMode
9-
import io.github.kdroidfilter.nucleus.darkmodedetector.windows.isWindowsInDarkMode
5+
import androidx.compose.runtime.Composable
6+
import androidx.compose.runtime.DisposableEffect
7+
import androidx.compose.runtime.mutableStateOf
8+
import androidx.compose.runtime.remember
9+
import java.util.function.Consumer
1010

1111
/**
1212
* Composable function that returns whether the system is in dark mode.
@@ -18,11 +18,26 @@ fun isSystemInDarkMode(): Boolean {
1818
if (isInPreview) {
1919
return isSystemInDarkTheme()
2020
}
21+
val darkModeDetector = remember { getPlatformDarkModeDetector() }
22+
val darkModeState = remember { mutableStateOf(darkModeDetector.isDark()) }
23+
24+
DisposableEffect(Unit) {
25+
debugln(TAG) { "Registering OS dark mode listener in Compose" }
26+
val listener =
27+
Consumer<Boolean> { newValue ->
28+
debugln(TAG) { "OS dark mode updated: $newValue" }
29+
darkModeState.value = newValue
30+
}
2131

22-
return when (Platform.Current) {
23-
Platform.MacOS -> isMacOsInDarkMode()
24-
Platform.Windows -> isWindowsInDarkMode()
25-
Platform.Linux -> isLinuxInDarkMode()
26-
else -> false
32+
darkModeDetector.registerListener(listener)
33+
34+
onDispose {
35+
debugln(TAG) { "Removing OS dark mode listener in Compose" }
36+
darkModeDetector.removeListener(listener)
37+
}
2738
}
39+
40+
return darkModeState.value
2841
}
42+
43+
private const val TAG = "PlatformThemeDetectorCompose"

darkmode-detector/src/main/kotlin/io/github/kdroidfilter/nucleus/darkmodedetector/linux/LinuxPortalThemeDetector.kt

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.compose.runtime.DisposableEffect
55
import androidx.compose.runtime.mutableStateOf
66
import androidx.compose.runtime.remember
77
import io.github.kdroidfilter.nucleus.darkmodedetector.debugln
8+
import io.github.kdroidfilter.nucleus.darkmodedetector.IDarkModeDetector
89
import java.util.function.Consumer
910

1011
private const val TAG = "LinuxPortalThemeDetector"
@@ -18,42 +19,19 @@ private const val TAG = "LinuxPortalThemeDetector"
1819
* The detector also monitors for SettingChanged signals in real-time via a background
1920
* D-Bus dispatch thread.
2021
*/
21-
internal object LinuxPortalThemeDetector {
22+
internal object LinuxPortalThemeDetector: IDarkModeDetector {
2223
init {
2324
debugln(TAG) { "Initializing Linux portal theme observer via JNI" }
2425
NativeLinuxBridge.nativeStartObserving()
2526
}
2627

27-
fun isDark(): Boolean = NativeLinuxBridge.nativeIsDark()
28+
override fun isDark(): Boolean = NativeLinuxBridge.nativeIsDark()
2829

29-
fun registerListener(listener: Consumer<Boolean>) {
30+
override fun registerListener(listener: Consumer<Boolean>) {
3031
NativeLinuxBridge.registerListener(listener)
3132
}
3233

33-
fun removeListener(listener: Consumer<Boolean>) {
34+
override fun removeListener(listener: Consumer<Boolean>) {
3435
NativeLinuxBridge.removeListener(listener)
3536
}
3637
}
37-
38-
/**
39-
* A helper composable function that returns the current Linux dark mode state
40-
* via the XDG Desktop Portal, updating automatically when the system theme changes.
41-
*/
42-
@Composable
43-
fun isLinuxInDarkMode(): Boolean {
44-
val darkModeState = remember { mutableStateOf(LinuxPortalThemeDetector.isDark()) }
45-
DisposableEffect(Unit) {
46-
debugln(TAG) { "Registering Linux portal dark mode listener in Compose" }
47-
val listener =
48-
Consumer<Boolean> { newValue ->
49-
debugln(TAG) { "Compose Linux portal dark mode updated: $newValue" }
50-
darkModeState.value = newValue
51-
}
52-
LinuxPortalThemeDetector.registerListener(listener)
53-
onDispose {
54-
debugln(TAG) { "Removing Linux portal dark mode listener in Compose" }
55-
LinuxPortalThemeDetector.removeListener(listener)
56-
}
57-
}
58-
return darkModeState.value
59-
}

darkmode-detector/src/main/kotlin/io/github/kdroidfilter/nucleus/darkmodedetector/mac/MacOSThemeDetector.kt

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.compose.runtime.DisposableEffect
55
import androidx.compose.runtime.mutableStateOf
66
import androidx.compose.runtime.remember
77
import io.github.kdroidfilter.nucleus.darkmodedetector.debugln
8+
import io.github.kdroidfilter.nucleus.darkmodedetector.IDarkModeDetector
89
import java.util.function.Consumer
910

1011
private const val TAG = "MacOSThemeDetector"
@@ -14,42 +15,19 @@ private const val TAG = "MacOSThemeDetector"
1415
* to detect theme changes in macOS. It reads the system preference "AppleInterfaceStyle"
1516
* (which is "Dark" when in dark mode) from NSUserDefaults.
1617
*/
17-
internal object MacOSThemeDetector {
18+
internal object MacOSThemeDetector: IDarkModeDetector {
1819
init {
1920
debugln(TAG) { "Initializing macOS theme observer via JNI" }
2021
NativeDarkModeBridge.nativeStartObserving()
2122
}
2223

23-
fun isDark(): Boolean = NativeDarkModeBridge.nativeIsDark()
24+
override fun isDark(): Boolean = NativeDarkModeBridge.nativeIsDark()
2425

25-
fun registerListener(listener: Consumer<Boolean>) {
26+
override fun registerListener(listener: Consumer<Boolean>) {
2627
NativeDarkModeBridge.registerListener(listener)
2728
}
2829

29-
fun removeListener(listener: Consumer<Boolean>) {
30+
override fun removeListener(listener: Consumer<Boolean>) {
3031
NativeDarkModeBridge.removeListener(listener)
3132
}
3233
}
33-
34-
/**
35-
* A helper composable function that returns the current macOS dark mode state,
36-
* updating automatically when the system theme changes.
37-
*/
38-
@Composable
39-
internal fun isMacOsInDarkMode(): Boolean {
40-
val darkModeState = remember { mutableStateOf(MacOSThemeDetector.isDark()) }
41-
DisposableEffect(Unit) {
42-
debugln(TAG) { "Registering macOS dark mode listener in Compose" }
43-
val listener =
44-
Consumer<Boolean> { newValue ->
45-
debugln(TAG) { "Compose macOS dark mode updated: $newValue" }
46-
darkModeState.value = newValue
47-
}
48-
MacOSThemeDetector.registerListener(listener)
49-
onDispose {
50-
debugln(TAG) { "Removing macOS dark mode listener in Compose" }
51-
MacOSThemeDetector.removeListener(listener)
52-
}
53-
}
54-
return darkModeState.value
55-
}

darkmode-detector/src/main/kotlin/io/github/kdroidfilter/nucleus/darkmodedetector/windows/WindowsThemeDetector.kt

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.compose.runtime.DisposableEffect
55
import androidx.compose.runtime.mutableStateOf
66
import androidx.compose.runtime.remember
77
import io.github.kdroidfilter.nucleus.darkmodedetector.debugln
8+
import io.github.kdroidfilter.nucleus.darkmodedetector.IDarkModeDetector
89
import java.util.function.Consumer
910

1011
private const val TAG = "WindowsThemeDetector"
@@ -18,45 +19,19 @@ private const val TAG = "WindowsThemeDetector"
1819
* The detector monitors the registry for changes in real-time via a native
1920
* background thread using RegNotifyChangeKeyValue in async mode.
2021
*/
21-
internal object WindowsThemeDetector {
22+
internal object WindowsThemeDetector: IDarkModeDetector {
2223
init {
2324
debugln(TAG) { "Initializing Windows theme observer via JNI" }
2425
NativeWindowsBridge.nativeStartObserving()
2526
}
2627

27-
fun isDark(): Boolean = NativeWindowsBridge.nativeIsDark()
28+
override fun isDark(): Boolean = NativeWindowsBridge.nativeIsDark()
2829

29-
fun registerListener(listener: Consumer<Boolean>) {
30+
override fun registerListener(listener: Consumer<Boolean>) {
3031
NativeWindowsBridge.registerListener(listener)
3132
}
3233

33-
fun removeListener(listener: Consumer<Boolean>) {
34+
override fun removeListener(listener: Consumer<Boolean>) {
3435
NativeWindowsBridge.removeListener(listener)
3536
}
3637
}
37-
38-
/**
39-
* Composable function that returns whether Windows is currently in dark mode.
40-
*/
41-
@Composable
42-
internal fun isWindowsInDarkMode(): Boolean {
43-
val darkModeState = remember { mutableStateOf(WindowsThemeDetector.isDark()) }
44-
45-
DisposableEffect(Unit) {
46-
debugln(TAG) { "Registering Windows dark mode listener in Compose" }
47-
val listener =
48-
Consumer<Boolean> { newValue ->
49-
debugln(TAG) { "Windows dark mode updated: $newValue" }
50-
darkModeState.value = newValue
51-
}
52-
53-
WindowsThemeDetector.registerListener(listener)
54-
55-
onDispose {
56-
debugln(TAG) { "Removing Windows dark mode listener in Compose" }
57-
WindowsThemeDetector.removeListener(listener)
58-
}
59-
}
60-
61-
return darkModeState.value
62-
}

0 commit comments

Comments
 (0)