diff --git a/doc/source/apis.rst b/doc/source/apis.rst index c9e30699ce..0f139c5192 100644 --- a/doc/source/apis.rst +++ b/doc/source/apis.rst @@ -388,6 +388,36 @@ This can be used to prevent errors like: Because the python function is called from the PythonActivity thread, you need to be careful about your own calls. +Handling DarkMode +~~~~~~~~~~~~~~~~~ + +The ``android.darkmode`` module provides functionality to detect and respond to +system dark mode changes on Android devices. + +You can set up a listener to monitor dark mode state changes using the +``set_dark_mode_listener`` function:: + + from android.darkmode import set_dark_mode_listener + + def on_dark_mode_changed(is_dark_mode): + if is_dark_mode: + print('Dark mode is now enabled') + # Update your app's theme to dark mode + else: + print('Dark mode is now disabled') + # Update your app's theme to light mode + + # Register the listener + set_dark_mode_listener(on_dark_mode_changed) + +To remove the listener, simply pass ``None``:: + + set_dark_mode_listener(None) + +The callback function receives a single boolean parameter ``is_dark_mode`` that +indicates whether dark mode is currently enabled (``True``) or disabled (``False``). + + Advanced Android API use ------------------------ diff --git a/pythonforandroid/bootstraps/qt/build/src/main/java/org/kivy/android/PythonActivity.java b/pythonforandroid/bootstraps/qt/build/src/main/java/org/kivy/android/PythonActivity.java index 01bdd96805..bffe3cef71 100644 --- a/pythonforandroid/bootstraps/qt/build/src/main/java/org/kivy/android/PythonActivity.java +++ b/pythonforandroid/bootstraps/qt/build/src/main/java/org/kivy/android/PythonActivity.java @@ -3,6 +3,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.os.Bundle; import android.os.PowerManager; import android.os.SystemClock; @@ -238,4 +239,26 @@ public static void stop_service() { Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class); PythonActivity.mActivity.stopService(serviceIntent); } + + public interface DarkModeListener { + void onDarkModeChanged(boolean isDarkMode); + } + + private DarkModeListener darkModeListener = null; + + public void setDarkModeListener(DarkModeListener listener) { + darkModeListener = listener; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; + boolean isDarkMode = currentNightMode == Configuration.UI_MODE_NIGHT_YES; + + if (darkModeListener != null) { + darkModeListener.onDarkModeChanged(isDarkMode); + } + + super.onConfigurationChanged(newConfig); + } } diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java index 04d3eee30a..4b57f0ca8a 100644 --- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java +++ b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java @@ -5,6 +5,7 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.content.res.Resources.NotFoundException; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -620,6 +621,28 @@ public void requestPermissions(String[] permissions) { requestPermissionsWithRequestCode(permissions, 1); } + public interface DarkModeListener { + void onDarkModeChanged(boolean isDarkMode); + } + + private DarkModeListener darkModeListener = null; + + public void setDarkModeListener(DarkModeListener listener) { + darkModeListener = listener; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; + boolean isDarkMode = currentNightMode == Configuration.UI_MODE_NIGHT_YES; + + if (darkModeListener != null) { + darkModeListener.onDarkModeChanged(isDarkMode); + } + + super.onConfigurationChanged(newConfig); + } + public static void changeKeyboard(int inputType) { if (SDLActivity.keyboardInputType != inputType) { SDLActivity.keyboardInputType = inputType; diff --git a/pythonforandroid/bootstraps/sdl3/build/src/main/java/org/kivy/android/PythonActivity.java b/pythonforandroid/bootstraps/sdl3/build/src/main/java/org/kivy/android/PythonActivity.java index 8feed58ee4..34bb10236a 100644 --- a/pythonforandroid/bootstraps/sdl3/build/src/main/java/org/kivy/android/PythonActivity.java +++ b/pythonforandroid/bootstraps/sdl3/build/src/main/java/org/kivy/android/PythonActivity.java @@ -5,6 +5,7 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.content.res.Resources.NotFoundException; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -619,6 +620,28 @@ public void requestPermissions(String[] permissions) { requestPermissionsWithRequestCode(permissions, 1); } + public interface DarkModeListener { + void onDarkModeChanged(boolean isDarkMode); + } + + private DarkModeListener darkModeListener = null; + + public void setDarkModeListener(DarkModeListener listener) { + darkModeListener = listener; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; + boolean isDarkMode = currentNightMode == Configuration.UI_MODE_NIGHT_YES; + + if (darkModeListener != null) { + darkModeListener.onDarkModeChanged(isDarkMode); + } + + super.onConfigurationChanged(newConfig); + } + public static void changeKeyboard(int inputType) { /* if (SDLActivity.keyboardInputType != inputType){ diff --git a/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/PythonActivity.java b/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/PythonActivity.java index 9ad9503a6f..55f1e2e3a4 100644 --- a/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/PythonActivity.java +++ b/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/PythonActivity.java @@ -6,6 +6,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; @@ -547,6 +548,28 @@ public void requestPermissionsWithRequestCode(String[] permissions, int requestC public void requestPermissions(String[] permissions) { requestPermissionsWithRequestCode(permissions, 1); } + + public interface DarkModeListener { + void onDarkModeChanged(boolean isDarkMode); + } + + private DarkModeListener darkModeListener = null; + + public void setDarkModeListener(DarkModeListener listener) { + darkModeListener = listener; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; + boolean isDarkMode = currentNightMode == Configuration.UI_MODE_NIGHT_YES; + + if (darkModeListener != null) { + darkModeListener.onDarkModeChanged(isDarkMode); + } + + super.onConfigurationChanged(newConfig); + } } class PythonMain implements Runnable { diff --git a/pythonforandroid/recipes/android/src/android/darkmode.py b/pythonforandroid/recipes/android/src/android/darkmode.py new file mode 100644 index 0000000000..6e06662de8 --- /dev/null +++ b/pythonforandroid/recipes/android/src/android/darkmode.py @@ -0,0 +1,57 @@ +from typing import Callable + +from jnius import PythonJavaClass, java_method, autoclass +from android.config import ACTIVITY_CLASS_NAME, ACTIVITY_CLASS_NAMESPACE + +_listener = None + + +class DarkModeListener(PythonJavaClass): + """ + A listener class for detecting and handling dark mode changes. + + This class implements the `DarkModeListener` interface in a Python-Java + hybrid context through Kivy Android functionality. It listens for changes + in the system's dark mode settings and executes a callback upon detecting + a change. + + Attributes: + on_dark_mode_changed (Callable[[bool], None]): A callback function to + handle the event when dark mode status changes. The callback + receives a single parameter `is_dark_mode`, which is a boolean + indicating whether dark mode is currently enabled. + """ + __javacontext__ = "app" + __javainterfaces__ = [ACTIVITY_CLASS_NAMESPACE + '$DarkModeListener'] + + def __init__(self, on_dark_mode_changed: Callable[[bool], None]): + self.on_dark_mode_changed = on_dark_mode_changed + + @java_method('(Z)V') + def onDarkModeChanged(self, is_dark_mode): + self.on_dark_mode_changed(is_dark_mode) + + +def set_dark_mode_listener(on_dark_mode_changed: Callable[[bool], None] | None) -> None: + """ + Sets a listener to monitor changes in the dark mode state. + + This function assigns a provided callback to handle changes in the + dark mode settings. The callback will be invoked with a boolean + argument indicating the current dark mode state. + + Args: + on_dark_mode_changed: A callable that accepts a single boolean + parameter indicating whether dark mode is active. + + Returns: + None + """ + global _listener + activity = autoclass(ACTIVITY_CLASS_NAME).mActivity + if on_dark_mode_changed: + _listener = DarkModeListener(on_dark_mode_changed) + activity.setDarkModeListener(_listener) + else: + activity.setDarkModeListener(on_dark_mode_changed) + _listener = None