1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
//! L1 of the theme ladder: the night-qualified `Context` control creation
//! builds every view against,
//! plus the one Android-only primitive L2's `Setter::ThemedBackground`
//! needs (`dp_to_px`). See [`crate::api::theme`] for L2's platform-neutral
//! token resolution — the module split is deliberate: that one is
//! host-testable (no `target_os` gate), this one is JNI-only.
//!
//! # L1: why a wrapped `Context`, not just tint lists
//!
//! An explicit tint list (L2) recolors what an app told a control to show,
//! but a framework `Button`/`Switch`/`SeekBar` also carries platform-owned
//! chrome an app never explicitly sets at all — the default ripple/state-layer
//! colour, and the `Switch`/`SeekBar` thumb-and-track drawables' own resting
//! colours before any tint is applied. Those resolve from `?android:attr/…`
//! theme attributes the *Context* a view was constructed with supplies,
//! which in turn come from that Context's resolved `Configuration` —
//! including its night-mode bit. A Glyph-dark app whose device is in light
//! mode (or vice versa) would otherwise show a light-resolved ripple/thumb
//! colour underneath an otherwise dark-themed control.
//!
//! [`night_qualified_context`] fixes exactly that bit before `create`
//! constructs anything: `Context.createConfigurationContext(Configuration)`
//! is **API 17+**
//! (<https://developer.android.com/reference/android/content/Context#createConfigurationContext(android.content.res.Configuration)>,
//! verified 2026-07-26) — this plugin's floor is `minSdk` 26, so it is
//! unconditionally available, no version gate needed.
//!
//! # Baked at construction, not live
//!
//! The wrapped Context only affects a view at the moment `new
//! Button(context)`/etc. runs. A live in-place brightness toggle (the
//! catalog's demo row) does NOT recreate the
//! view, so L1's platform-chrome effect stays fixed at whichever brightness
//! the control was first created under — only L2's own explicit setters
//! (tint lists, text/background colours, corner radius) actually re-paint
//! live on a later `update`, because those ride the ordinary
//! Props-diff-then-setter path every other property already uses. This is a
//! documented approximation, not a bug: a fully brightness-correct chrome
//! swap needs recreating the control, which "live re-theme without remount"
//! deliberately rules out for this module.
use OnceLock;
use ;
use ;
use crateNativeWidgetError;
use crate;
use crateParams;
/// Whether a slot's create params requested the dark half of the active
/// theme — read straight off the raw wire ([`crate::controls::DARK`])
/// *before* any per-control typed `Props` decode runs: `create_control`
/// calls this ahead of `NativeRuntime::create`'s own kind dispatch, so there
/// is no registered kind to decode against yet.
///
/// Absent (an older api layer, or params from a control kind this task
/// didn't fold theme tokens into) degrades to `false` (light) rather than an
/// error — night-qualification is a cosmetic improvement, never a reason to
/// fail control creation.
pub
/// L1: wrap `base` in a `Configuration.uiMode`-forced `Context` — see the
/// module doc's *why a wrapped Context* section.
///
/// Copies `base`'s own live `Configuration` (locale, screen size, density —
/// every axis but the night bits) rather than building one from scratch, so
/// this never touches layout or text scale, only which day/night resource
/// set the platform's own chrome resolves against.
///
/// # Errors
/// [`NativeWidgetError::Platform`] on any JNI failure. The caller
/// (`android::create_control`) falls back to the unqualified `base` Context
/// on `Err` rather than failing the whole control creation over a cosmetic
/// miss.
pub
// ---------------------------------------------------------------------------
// L2's one Android-only helper: dp -> device px for a raw-pixel drawable API
// ---------------------------------------------------------------------------
/// The cached display density (module doc's *baked at construction* note
/// applies here too, in miniature: a mid-session display move is unhandled,
/// matching this plugin's static-first placement doctrine).
static DENSITY: = new;
/// Convert a dp value into device pixels — `GradientDrawable.setCornerRadius`
/// (backing [`crate::controls::Setter::ThemedBackground`]) takes raw device
/// pixels, unlike `TextView.setTextSize(float)` (already SP, self-scaling),
/// so this is the one place in the plugin that needs the conversion.
///
/// Falls back to a 1:1 (density 1.0) conversion — logged once — only if the
/// density has never been read AND this call has no `Context` to read it
/// from (an `update`/`dispose` path with nothing cached yet). Every
/// control's own `create` always has a `Context`, so a themed control's
/// FIRST corner-radius application seeds the real density before any later
/// `update` for that same slot ever needs the fallback.
pub
/// See [`dp_to_px`].
/// `context.getResources().getDisplayMetrics().density`.