New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: cross-platform light/dark mode API #11457
Conversation
|
const _t = this; | ||
const xmlFileName = 'semantic.colors.xml'; | ||
const valuesDirPath = path.join(this.buildAppMainResDir, 'values'); | ||
const valuesNightDirPath = path.join(this.buildAppMainResDir, 'values-night'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't create a "values.xml" file. We risk file collision with the app developer's own "res" files. They need to be prefixed with "ti_" or "ti-". (C-style convention.)
So, we should name the files:
- "ti_semantic_values.xml" and "ti_semantic_values_night.xml".
- Or "ti-semantic-values.xml" and "ti-semantic-values-night.xml".
Note: Drawable "res" files can't have dash '-' chars (they're illegal), but XML "res" files can.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jquick-axway These aren't creating a values.xml file, they're creating:
build/app/src/main/res/values/semantic.colors.xml
build/app/src/main/res/values-night/semantic.colors.xml
Is the suggestion we should instead name them:
build/app/src/main/res/ti_semantic_colors.xml
build/app/src/main/res/ti_semantic_colors_night.xml
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, those are folders. Sorry about that.
But we should still prefix the XML files with "ti".
How about "ti.semantic.colors.xml"?
On Android, we know when the system changes between dark/light theme on Android Q (and day/night mode on older OS versions) when Java method
Theoretically yes. Currently, colors are applied to views via the Java ColorDrawable class which is assigned a hard-coded color value. We could create our own custom The only thing that bugs me here is that we're conflating dark/light theme with day/night mode. Also, AndroidX offers a Dark/Light AppCompat theme. Perhaps we should evaluate it before the 9.0.0 release... since changing the theme could be a breaking-change (particularly for those who customize an existing theme). |
@jquick-axway Is there really a difference between Day/Night and Light/Dark modes? It seems to me to be a re-branding from Android: https://developer.android.com/guide/topics/ui/look-and-feel/darktheme |
Right, I've seen Google's docs on this. I don't know how they're differentiating the 2 concepts yet. I haven't had time to play with it. On Android, if you drag the top status bar down and tap on the "Night Light" button (ie: the "Moon" button), that will flip the Day/Night mode. This triggers the "uiMode" config-change. This is not intended to trigger Dark mode (but I suppose you can set up an app to do this). Changing Dark/Light theme under Display settings is supposed to trigger "uiMode" as well. So, we need to figure out how to differentiate between Day/Night and Dark/Light changes. |
I think you have to use the MODE_NIGHT_FOLLOW_SYSTEM constant on Android Q (and above) to have the app honor the system's light/dark theme (not day/night setting). We would also have to update Titanium's app theme to "Theme.AppCompat.DayNight". I've found more/better information below, which includes a sample app. When using a theme/style with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a minor note on the docs. The iOS changes are looking good, nothing to add there.
const _t = this; | ||
const xmlFileName = 'semantic.colors.xml'; | ||
const valuesDirPath = path.join(this.buildAppMainResDir, 'values'); | ||
const valuesNightDirPath = path.join(this.buildAppMainResDir, 'values-night'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, those are folders. Sorry about that.
But we should still prefix the XML files with "ti".
How about "ti.semantic.colors.xml"?
android/modules/ui/src/java/ti/modules/titanium/ui/UIModule.java
Outdated
Show resolved
Hide resolved
if (Ti.UI.userInterfaceStyle === Ti.UI.USER_INTERFACE_STYLE_DARK) { | ||
return UI.SEMANTIC_COLOR_TYPE_DARK; | ||
} | ||
return UI.SEMANTIC_COLOR_TYPE_LIGHT; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that "night" mode is supported on API Levels older than 29.
Simplest way to test it is by toggling "Battery Saver" mode...
- Unplug the Android device from USB. (It needs to run on battery power.)
- Open the Calculator app.
- Drag the top status bar down.
- Tap on the "Battery Saver" button.
I tested your PR on Android 9 (API Level 28) and it works. The "userinterfacestyle" event was received and I received the correct color.
So, you don't need to add any guards for Android. :)
try { | ||
return colorset[colorName][uiModule.semanticColorType].color || colorset[colorName][uiModule.semanticColorType]; | ||
const entry = colorset[colorName][UI.semanticColorType]; | ||
const hex = entry.color || entry; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our docs under the "Dark Mode" section says that we support an "alpha" property too.
https://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI
It was missing in the original JS code here before. But I can see "alpha" support is in our Android "_build.js" and in our iOS "TiUtils.m" source code.
@ewanharris, am I right about this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe @drauggres has an open PR for this at #11315
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes (halfbaked as most of it was), I believe that we handle the alpha property when generating the iOS semantic colors file, but not in the lookup in the JS code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay. I scheduled TIMOB-27519 for 9.1.0. Thanks
I tested this PR with the below code on Android 10, 9, and 4.4 using real devices. This will automatically change the background color when flipping between Light/Dark mode. It also tests the handling of multiple windows. function createThemedWindow(settings) {
if (!settings) {
settings = {};
}
settings.backgroundColor = Ti.UI.fetchSemanticColor("primaryBackground");
var window = Ti.UI.createWindow(settings);
function onUserInterfaceStyleChanged(e) {
Ti.API.info("@@@ userinterfacestyle event.value: " + e.value);
window.backgroundColor = Ti.UI.fetchSemanticColor("primaryBackground");
}
window.addEventListener("open", function() {
Ti.UI.addEventListener("userinterfacestyle", onUserInterfaceStyleChanged);
});
window.addEventListener("close", function() {
Ti.UI.removeEventListener("userinterfacestyle", onUserInterfaceStyleChanged);
});
return window;
}
var parentWindow = createThemedWindow({ title: "Parent Window" });
var openButton = Ti.UI.createButton({ title: "Show Child" });
openButton.addEventListener("click", function() {
var childWindow = createThemedWindow({ title: "Child Window" });
var closeButton = Ti.UI.createButton({ title: "Close" });
closeButton.addEventListener("click", function() {
childWindow.close();
});
childWindow.add(closeButton);
childWindow.open();
});
parentWindow.add(openButton);
parentWindow.open(); |
ok, I think I addressed the remaining review feedback here? I'm gonna tag for QE to test now... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CR: Pass
df4ed4c
to
22a0351
Compare
FR Passed. |
A tiny addition to this great step forward: TIMOB-27895 |
This is event is not triggering for me when I change the theme mode in android and iOS.
|
@KamalKant-Tech, what SDK are you using? This will ship in SDK 9.1.0, and the event is firing ok for me when I use an SDK from that branch ( |
@ewanharris Thanks, I got the issue it was not working because of SDK version. I have been using 9.0.3 and then I moved to 9.1.0 now it works. |
JIRA: https://jira.appcelerator.org/browse/TIMOB-27501
Description:
This incorporates the code from @drauggres in his PR #11301
This attempts to make a more comprehensive cross-platform API for dealing with determining the current theme/mode, and consistency in the return value of
Ti.UI.fetchSemanticColor
.API
Ti.UI.userInterfaceStyle
reports the current theme viaTi.UI
constants:USER_INTERFACE_STYLE_DARK
,USER_INTERFACE_STYLE_LIGHT
, andUSER_INTERFACE_STYLE_UNSPECIFIED
Ti.UI.fetchSemanticColor(String name)
returns aTi.UI.Color
proxy for the named color (and you can get the underlying "color" by callingtoHex()
on it to get a hex string) on iOS 13+, a string (representing the hex value) on Android and iOS < 13. The mismatched return types are necessary to make it easy to just pass the returned value to the UI properties without conversion right now.Ti.UI
now has a newuserInterfaceStyle
event which gets fired when the device theme changes. The object received in the event listener has avalue
property whose value is the constant for the newUSER_INTERFACE_STYLE_*
that was set.Deprecations
Ti.App.iOS.userInterfaceStyle
-> useTi.UI.userInterfaceStyle
Ti.App.iOS.USER_INTERFACE_STYLE_DARK
-> useTi.UI.USER_INTERFACE_STYLE_DARK
Ti.App.iOS.USER_INTERFACE_STYLE_LIGHT
-> useTi.UI.USER_INTERFACE_STYLE_LIGHT
Ti.App.iOS.USER_INTERFACE_STYLE_UNSPECIFIED
-> useTi.UI.USER_INTERFACE_STYLE_UNSPECIFIED
Ti.UI.iOS.fetchSemanticColor()
(useTi.UI.fetchSemanticColor()
)Doc updates
Ti.UI.Color
proxy. This is really an iOS-only proxy. We need a way to pass around the underlyingUIColor
objects from iOS so that we can set various UI color properties with them and they get "automatically" updated if the user changes the device theme.Notable parity issues
While passing around
Ti.UI.Color
proxies on iOS allows for the UI to automatically adjust to theme changes, we don't have the equivalent on Android. Can we hook an equivalentTi.UI.Color
proxy that could auto-update on Android?To Test
The actual API has some unit tests to verify the new properties/constants exist.
It's difficult to test on the emulator/sim, but to test the new event on theme changes you can run this app:
Resources/semantic.colors.json
Resources/app.js
On iOS Emulator, I had to use the Hardware > Siri menu and then tell Siri to "Tun Dark mode on" (or off). On Android, You need an apilevel 29+ emulator, and you can drag from he top status bar down to get a quick menu. Click the pencil icon to edit the options displayed, drag the "Dark Theme" button up into it, and then you can use that to toggle dark/light themes while the app is running.