Skip to content

Commit c2a0778

Browse files
authored
Add update_system_ui for status and navigation bar colors
Implement update_system_ui function for Android UI customization.
1 parent 1fc0269 commit c2a0778

1 file changed

Lines changed: 150 additions & 0 deletions

File tree

  • pythonforandroid/recipes/android/src/android
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
from android.runnable import Runnable
2+
from jnius import autoclass, java_method, PythonJavaClass
3+
from typing import Literal
4+
5+
__all__ = ('update_system_ui')
6+
7+
def update_system_ui(
8+
status_bar_color: list[float] | str,
9+
navigation_bar_color: list[float] | str,
10+
icon_style: Literal["Light", "Dark"] = "Dark",
11+
pad_status: bool = True,
12+
pad_nav: bool = False,
13+
) -> None:
14+
"""
15+
Provides control of colors for the status and navigation bar and also handle insets padding on Android 15 and above.
16+
17+
For `status_bar_color` and `navigation_bar_color` either provide a hex color code or rgba (tuple or list) values.
18+
`pad_status` and `pad_nav` will take effect only above Android 15.
19+
IF `icon_style` IS `Dark` THE ICONS WILL BE DARK.
20+
IF `icon_style` IS `Light` THE ICONS WILL BE LIGHT.
21+
22+
Original code at https://github.com/CarbonKivy/CarbonKivy/blob/39e360314a3885f3b462add4475e6c609b5bef53/carbonkivy/utils.py#L43 (subject to active changes ahead).
23+
"""
24+
25+
Color = autoclass("android.graphics.Color")
26+
Build_VERSION = autoclass("android.os.Build$VERSION")
27+
WindowInsetsType = autoclass("android.view.WindowInsets$Type")
28+
PythonActivity = autoclass("org.kivy.android.PythonActivity")
29+
View = autoclass("android.view.View")
30+
31+
activity = PythonActivity.mActivity
32+
window = activity.getWindow()
33+
decor_view = window.getDecorView()
34+
content_view = window.findViewById(autoclass("android.R$id").content)
35+
36+
try:
37+
WindowCompat = autoclass("androidx.core.view.WindowCompat")
38+
inset_controller = WindowCompat.getInsetsController(window, decor_view)
39+
except Exception as e:
40+
inset_controller = None
41+
42+
def parse_color(value):
43+
if isinstance(value, str):
44+
return Color.parseColor(value)
45+
elif isinstance(value, (list, tuple)) and len(value) == 4:
46+
r, g, b, a = value
47+
return Color.argb(a, r, g, b)
48+
else:
49+
raise ValueError("Color must be hex string or RGBA tuple")
50+
51+
def apply_system_bars():
52+
status_color_int = parse_color(status_bar_color)
53+
navigation_color_int = parse_color(navigation_bar_color)
54+
55+
# Beleive me, I once drew `dark icons over dark` and `light icons over light` but this won't happen ever again!
56+
if (Build_VERSION.SDK_INT >= 30):
57+
# API 30+ (Android 10+)
58+
if inset_controller and "WindowInsetsControllerCompat" in str(type(inset_controller)):
59+
# Compat wrapper (AndroidX)
60+
# I suggest to include androidx in builds, it actually helps!
61+
if icon_style == "Dark":
62+
inset_controller.setAppearanceLightStatusBars(False)
63+
inset_controller.setAppearanceLightNavigationBars(False)
64+
else:
65+
inset_controller.setAppearanceLightStatusBars(True)
66+
inset_controller.setAppearanceLightNavigationBars(True)
67+
else:
68+
# Platform controller
69+
controller = inset_controller or window.getInsetsController()
70+
WindowInsetsController = autoclass("android.view.WindowInsetsController")
71+
if icon_style == "Dark":
72+
controller.setSystemBarsAppearance(
73+
0,
74+
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
75+
| WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS,
76+
)
77+
else:
78+
controller.setSystemBarsAppearance(
79+
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
80+
| WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS,
81+
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
82+
| WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS,
83+
)
84+
85+
else:
86+
# Legacy flags for API 23–29
87+
# Yepp, python3.14 with ndk 28c doesn't support building for android <= 11 with 32 bit armeabi-v7a cpu so this may never be called but who knows??
88+
visibility_flags = decor_view.getSystemUiVisibility()
89+
90+
if icon_style == "Dark":
91+
visibility_flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
92+
if Build_VERSION.SDK_INT >= 26:
93+
visibility_flags &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
94+
else:
95+
visibility_flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
96+
if Build_VERSION.SDK_INT >= 26:
97+
visibility_flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
98+
99+
decor_view.setSystemUiVisibility(visibility_flags)
100+
101+
# Oops!! android 15+ needs a listener
102+
if Build_VERSION.SDK_INT >= 35:
103+
104+
class InsetsListener(PythonJavaClass):
105+
__javainterfaces__ = [
106+
"android/view/View$OnApplyWindowInsetsListener"
107+
]
108+
__javacontext__ = "app"
109+
110+
def __init__(self, status_color, navigation_color):
111+
super().__init__()
112+
self.status_color = status_color
113+
self.navigation_color = navigation_color
114+
115+
@java_method(
116+
"(Landroid/view/View;Landroid/view/WindowInsets;)Landroid/view/WindowInsets;"
117+
)
118+
def onApplyWindowInsets(self, view, insets):
119+
try:
120+
status_insets = insets.getInsets(
121+
WindowInsetsType.statusBars()
122+
)
123+
nav_insets = insets.getInsets(
124+
WindowInsetsType.navigationBars()
125+
)
126+
127+
top_pad = status_insets.top if pad_status else 0
128+
bottom_pad = nav_insets.bottom if pad_nav else 0
129+
130+
content_view.setPadding(0, top_pad, 0, bottom_pad)
131+
content_view.setBackgroundColor(self.status_color)
132+
133+
window.setNavigationBarColor(self.navigation_color)
134+
except Exception as e:
135+
print("Insets error:", e)
136+
import traceback
137+
traceback.print_exc()
138+
return insets
139+
140+
listener = InsetsListener(status_color_int, navigation_color_int)
141+
# I don't know why but sometimes pyjnius failed to find invoke, maybe due to garbage collection and so I made a reference
142+
activity._system_ui_listener = listener
143+
decor_view.setOnApplyWindowInsetsListener(listener)
144+
decor_view.requestApplyInsets()
145+
else:
146+
window.setStatusBarColor(status_color_int)
147+
window.setNavigationBarColor(navigation_color_int)
148+
149+
# even if it fails it fails in a separate thread
150+
Runnable(apply_system_bars)()

0 commit comments

Comments
 (0)