Skip to content

Commit c58d195

Browse files
committed
Add dark mode listener support across all Android bootstraps
Introduce `DarkModeListener` interface to detect and handle system dark mode changes. Update Java and Python APIs to support listener registration for monitoring and reacting to dark mode state changes. Document usage in APIs.
1 parent 869c74b commit c58d195

6 files changed

Lines changed: 179 additions & 0 deletions

File tree

doc/source/apis.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,36 @@ This can be used to prevent errors like:
388388
Because the python function is called from the PythonActivity thread, you
389389
need to be careful about your own calls.
390390

391+
Handling DarkMode
392+
~~~~~~~~~~~~~~~~~
393+
394+
The ``android.darkmode`` module provides functionality to detect and respond to
395+
system dark mode changes on Android devices.
396+
397+
You can set up a listener to monitor dark mode state changes using the
398+
``set_dark_mode_listener`` function::
399+
400+
from android.darkmode import set_dark_mode_listener
401+
402+
def on_dark_mode_changed(is_dark_mode):
403+
if is_dark_mode:
404+
print('Dark mode is now enabled')
405+
# Update your app's theme to dark mode
406+
else:
407+
print('Dark mode is now disabled')
408+
# Update your app's theme to light mode
409+
410+
# Register the listener
411+
set_dark_mode_listener(on_dark_mode_changed)
412+
413+
To remove the listener, simply pass ``None``::
414+
415+
set_dark_mode_listener(None)
416+
417+
The callback function receives a single boolean parameter ``is_dark_mode`` that
418+
indicates whether dark mode is currently enabled (``True``) or disabled (``False``).
419+
420+
391421

392422
Advanced Android API use
393423
------------------------

pythonforandroid/bootstraps/qt/build/src/main/java/org/kivy/android/PythonActivity.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import android.content.Context;
44
import android.content.Intent;
55
import android.content.pm.PackageManager;
6+
import android.content.res.Configuration;
67
import android.os.Bundle;
78
import android.os.PowerManager;
89
import android.os.SystemClock;
@@ -238,4 +239,26 @@ public static void stop_service() {
238239
Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
239240
PythonActivity.mActivity.stopService(serviceIntent);
240241
}
242+
243+
public interface DarkModeListener {
244+
void onDarkModeChanged(boolean isDarkMode);
245+
}
246+
247+
private DarkModeListener darkModeListener = null;
248+
249+
public void setDarkModeListener(DarkModeListener listener) {
250+
darkModeListener = listener;
251+
}
252+
253+
@Override
254+
public void onConfigurationChanged(Configuration newConfig) {
255+
int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
256+
boolean isDarkMode = currentNightMode == Configuration.UI_MODE_NIGHT_YES;
257+
258+
if (darkModeListener != null) {
259+
darkModeListener.onDarkModeChanged(isDarkMode);
260+
}
261+
262+
super.onConfigurationChanged(newConfig);
263+
}
241264
}

pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import android.content.Intent;
66
import android.content.pm.ActivityInfo;
77
import android.content.pm.PackageManager;
8+
import android.content.res.Configuration;
89
import android.content.res.Resources.NotFoundException;
910
import android.graphics.Bitmap;
1011
import android.graphics.BitmapFactory;
@@ -620,6 +621,28 @@ public void requestPermissions(String[] permissions) {
620621
requestPermissionsWithRequestCode(permissions, 1);
621622
}
622623

624+
public interface DarkModeListener {
625+
void onDarkModeChanged(boolean isDarkMode);
626+
}
627+
628+
private DarkModeListener darkModeListener = null;
629+
630+
public void setDarkModeListener(DarkModeListener listener) {
631+
darkModeListener = listener;
632+
}
633+
634+
@Override
635+
public void onConfigurationChanged(Configuration newConfig) {
636+
int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
637+
boolean isDarkMode = currentNightMode == Configuration.UI_MODE_NIGHT_YES;
638+
639+
if (darkModeListener != null) {
640+
darkModeListener.onDarkModeChanged(isDarkMode);
641+
}
642+
643+
super.onConfigurationChanged(newConfig);
644+
}
645+
623646
public static void changeKeyboard(int inputType) {
624647
if (SDLActivity.keyboardInputType != inputType) {
625648
SDLActivity.keyboardInputType = inputType;

pythonforandroid/bootstraps/sdl3/build/src/main/java/org/kivy/android/PythonActivity.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import android.content.Intent;
66
import android.content.pm.ActivityInfo;
77
import android.content.pm.PackageManager;
8+
import android.content.res.Configuration;
89
import android.content.res.Resources.NotFoundException;
910
import android.graphics.Bitmap;
1011
import android.graphics.BitmapFactory;
@@ -619,6 +620,28 @@ public void requestPermissions(String[] permissions) {
619620
requestPermissionsWithRequestCode(permissions, 1);
620621
}
621622

623+
public interface DarkModeListener {
624+
void onDarkModeChanged(boolean isDarkMode);
625+
}
626+
627+
private DarkModeListener darkModeListener = null;
628+
629+
public void setDarkModeListener(DarkModeListener listener) {
630+
darkModeListener = listener;
631+
}
632+
633+
@Override
634+
public void onConfigurationChanged(Configuration newConfig) {
635+
int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
636+
boolean isDarkMode = currentNightMode == Configuration.UI_MODE_NIGHT_YES;
637+
638+
if (darkModeListener != null) {
639+
darkModeListener.onDarkModeChanged(isDarkMode);
640+
}
641+
642+
super.onConfigurationChanged(newConfig);
643+
}
644+
622645
public static void changeKeyboard(int inputType) {
623646
/*
624647
if (SDLActivity.keyboardInputType != inputType){

pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/PythonActivity.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import android.content.DialogInterface;
77
import android.content.Intent;
88
import android.content.pm.PackageManager;
9+
import android.content.res.Configuration;
910
import android.graphics.Bitmap;
1011
import android.graphics.BitmapFactory;
1112
import android.graphics.Color;
@@ -547,6 +548,28 @@ public void requestPermissionsWithRequestCode(String[] permissions, int requestC
547548
public void requestPermissions(String[] permissions) {
548549
requestPermissionsWithRequestCode(permissions, 1);
549550
}
551+
552+
public interface DarkModeListener {
553+
void onDarkModeChanged(boolean isDarkMode);
554+
}
555+
556+
private DarkModeListener darkModeListener = null;
557+
558+
public void setDarkModeListener(DarkModeListener listener) {
559+
darkModeListener = listener;
560+
}
561+
562+
@Override
563+
public void onConfigurationChanged(Configuration newConfig) {
564+
int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
565+
boolean isDarkMode = currentNightMode == Configuration.UI_MODE_NIGHT_YES;
566+
567+
if (darkModeListener != null) {
568+
darkModeListener.onDarkModeChanged(isDarkMode);
569+
}
570+
571+
super.onConfigurationChanged(newConfig);
572+
}
550573
}
551574

552575
class PythonMain implements Runnable {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from typing import Callable
2+
3+
from jnius import PythonJavaClass, java_method, autoclass
4+
from android.config import ACTIVITY_CLASS_NAME, ACTIVITY_CLASS_NAMESPACE
5+
6+
_listener = None
7+
8+
9+
class DarkModeListener(PythonJavaClass):
10+
"""
11+
A listener class for detecting and handling dark mode changes.
12+
13+
This class implements the `DarkModeListener` interface in a Python-Java
14+
hybrid context through Kivy Android functionality. It listens for changes
15+
in the system's dark mode settings and executes a callback upon detecting
16+
a change.
17+
18+
Attributes:
19+
on_dark_mode_changed (Callable[[bool], None]): A callback function to
20+
handle the event when dark mode status changes. The callback
21+
receives a single parameter `is_dark_mode`, which is a boolean
22+
indicating whether dark mode is currently enabled.
23+
"""
24+
__javacontext__ = "app"
25+
__javainterfaces__ = ["org/kivy/android/PythonActivity$DarkModeListener"]
26+
27+
def __init__(self, on_dark_mode_changed: Callable[[bool], None]):
28+
self.on_dark_mode_changed = on_dark_mode_changed
29+
30+
@java_method("(Z)V")
31+
def onDarkModeChanged(self, is_dark_mode):
32+
self.on_dark_mode_changed(is_dark_mode)
33+
34+
35+
def set_dark_mode_listener(on_dark_mode_changed: Callable[[bool], None] | None) -> None:
36+
"""
37+
Sets a listener to monitor changes in the dark mode state.
38+
39+
This function assigns a provided callback to handle changes in the
40+
dark mode settings. The callback will be invoked with a boolean
41+
argument indicating the current dark mode state.
42+
43+
Args:
44+
on_dark_mode_changed: A callable that accepts a single boolean
45+
parameter indicating whether dark mode is active.
46+
47+
Returns:
48+
None
49+
"""
50+
global _listener
51+
activity = autoclass(ACTIVITY_CLASS_NAME).mActivity
52+
if on_dark_mode_changed:
53+
_listener = DarkModeListener(on_dark_mode_changed)
54+
activity.setDarkModeListener(_listener)
55+
else:
56+
activity.setDarkModeListener(on_dark_mode_changed)
57+
_listener = None

0 commit comments

Comments
 (0)