feat(mode-night): apply ns-dark|ns-light class to root view at app launch

This commit is contained in:
Vasil Chimev
2019-10-02 15:30:45 +03:00
parent 93fa7edd8e
commit d81be715d2
6 changed files with 153 additions and 52 deletions

View File

@@ -42,7 +42,7 @@ import {
} from "./application";
import { CLASS_PREFIX, pushToRootViewCssClasses, removeFromRootViewCssClasses } from "../css/system-classes";
import { DeviceOrientation } from "../ui/enums/enums";
import { DeviceOrientation, SystemAppearance } from "../ui/enums/enums";
export { UnhandledErrorEventData, DiscardedErrorEventData, CssChangedEventData, LoadAppCSSEventData };
@@ -55,6 +55,7 @@ export const lowMemoryEvent = "lowMemory";
export const uncaughtErrorEvent = "uncaughtError";
export const discardedErrorEvent = "discardedError";
export const orientationChangedEvent = "orientationChanged";
export const systemAppearanceChangedEvent = "systemAppearanceChanged";
const ORIENTATION_CSS_CLASSES = [
`${CLASS_PREFIX}${DeviceOrientation.portrait}`,
@@ -62,6 +63,11 @@ const ORIENTATION_CSS_CLASSES = [
`${CLASS_PREFIX}${DeviceOrientation.unknown}`
];
const SYSTEM_APPEARANCE_CSS_CLASSES = [
`${CLASS_PREFIX}${SystemAppearance.light}`,
`${CLASS_PREFIX}${SystemAppearance.dark}`
];
let cssFile: string = "./app.css";
let resources: any = {};
@@ -126,13 +132,13 @@ export function loadAppCss(): void {
}
}
export function applyCssClass(rootView: View, cssClass: string) {
function applyCssClass(rootView: View, cssClass: string) {
pushToRootViewCssClasses(cssClass);
rootView.cssClasses.add(cssClass);
rootView._onCssStateChange();
}
export function removeCssClass(rootView: View, cssClass: string) {
function removeCssClass(rootView: View, cssClass: string) {
removeFromRootViewCssClasses(cssClass);
rootView.cssClasses.delete(cssClass);
}
@@ -145,6 +151,14 @@ export function orientationChanged(rootView: View, newOrientation: "portrait" |
}
}
export function systemAppearanceChanged(rootView: View, newSystemAppearance: "dark" | "light"): void {
const newSystemAppearanceCssClass = `${CLASS_PREFIX}${newSystemAppearance}`;
if (!rootView.cssClasses.has(newSystemAppearanceCssClass)) {
SYSTEM_APPEARANCE_CSS_CLASSES.forEach(cssClass => removeCssClass(rootView, cssClass));
applyCssClass(rootView, newSystemAppearanceCssClass);
}
}
global.__onUncaughtError = function (error: NativeScriptError) {
events.notify(<UnhandledErrorEventData>{ eventName: uncaughtErrorEvent, object: app, android: error, ios: error, error: error });
};

View File

@@ -9,12 +9,14 @@ import {
AndroidApplication as AndroidApplicationDefinition,
ApplicationEventData,
CssChangedEventData,
OrientationChangedEventData
OrientationChangedEventData,
SystemAppearanceChangedEventData
} from ".";
import {
displayedEvent, hasListeners, livesync, lowMemoryEvent, notify, Observable, on,
orientationChanged, orientationChangedEvent, setApplication, suspendEvent
orientationChanged, orientationChangedEvent, setApplication, suspendEvent,
systemAppearanceChanged, systemAppearanceChangedEvent
} from "./application-common";
import { profile } from "../profiling";
@@ -51,6 +53,7 @@ export class AndroidApplication extends Observable implements AndroidApplication
public static activityRequestPermissionsEvent = ActivityRequestPermissions;
private _orientation: "portrait" | "landscape" | "unknown";
private _systemAppearance: "light" | "dark";
public paused: boolean;
public nativeApp: android.app.Application;
public context: android.content.Context;
@@ -105,6 +108,22 @@ export class AndroidApplication extends Observable implements AndroidApplication
this._orientation = value;
}
get systemAppearance(): "light" | "dark" {
if (!this._systemAppearance) {
const resources = this.context.getResources();
const configuration = <android.content.res.Configuration>resources.getConfiguration();
const systemAppearance = configuration.uiMode & android.content.res.Configuration.UI_MODE_NIGHT_MASK;
this._systemAppearance = getSystemAppearanceValue(systemAppearance);
}
return this._systemAppearance;
}
set systemAppearance(value: "light" | "dark") {
this._systemAppearance = value;
}
public registerBroadcastReceiver(intentFilter: string, onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void) {
ensureBroadCastReceiverClass();
const that = this;
@@ -131,6 +150,7 @@ export class AndroidApplication extends Observable implements AndroidApplication
}
}
}
export interface AndroidApplication {
on(eventNames: string, callback: (data: AndroidActivityEventData) => void, thisArg?: any);
on(event: "activityCreated", callback: (args: AndroidActivityBundleEventData) => void, thisArg?: any);
@@ -259,6 +279,13 @@ on(orientationChangedEvent, (args: OrientationChangedEventData) => {
}
});
on(systemAppearanceChangedEvent, (args: SystemAppearanceChangedEventData) => {
const rootView = getRootView();
if (rootView) {
systemAppearanceChanged(rootView, args.newValue);
}
});
global.__onLiveSync = function __onLiveSync(context?: ModuleContext) {
if (androidApp && androidApp.paused) {
return;
@@ -279,6 +306,16 @@ function getOrientationValue(orientation: number): "portrait" | "landscape" | "u
}
}
function getSystemAppearanceValue(systemAppearance: number): "dark" | "light" {
switch (systemAppearance) {
case android.content.res.Configuration.UI_MODE_NIGHT_YES:
return "dark";
case android.content.res.Configuration.UI_MODE_NIGHT_NO:
case android.content.res.Configuration.UI_MODE_NIGHT_UNDEFINED:
return "light";
}
}
function initLifecycleCallbacks() {
const setThemeOnLaunch = profile("setThemeOnLaunch", (activity: androidx.appcompat.app.AppCompatActivity) => {
// Set app theme after launch screen was used during startup
@@ -394,6 +431,20 @@ function initComponentCallbacks() {
object: androidApp
});
}
const newConfigSystemAppearance = newConfig.uiMode & android.content.res.Configuration.UI_MODE_NIGHT_MASK;
const newSystemAppearance = getSystemAppearanceValue(newConfigSystemAppearance);
if (androidApp.systemAppearance !== newSystemAppearance) {
androidApp.systemAppearance = newSystemAppearance;
notify(<SystemAppearanceChangedEventData>{
eventName: systemAppearanceChangedEvent,
android: androidApp.nativeApp,
newValue: androidApp.systemAppearance,
object: androidApp
});
}
})
});

View File

@@ -101,6 +101,16 @@ export interface OrientationChangedEventData extends ApplicationEventData {
newValue: "portrait" | "landscape" | "unknown";
}
/**
* Event data containing information for system appearance changed event.
*/
export interface SystemAppearanceChangedEventData extends ApplicationEventData {
/**
* New system appearance value.
*/
newValue: "light" | "dark";
}
/**
* Event data containing information about unhandled application errors.
*/
@@ -281,30 +291,20 @@ export function on(event: "uncaughtError", callback: (args: UnhandledErrorEventD
export function on(event: "discardedError", callback: (args: DiscardedErrorEventData) => void, thisArg?: any);
/**
* This event is raised the orientation of the current device has changed.
* This event is raised when the orientation of the application changes.
*/
export function on(event: "orientationChanged", callback: (args: OrientationChangedEventData) => void, thisArg?: any);
/**
<<<<<<< HEAD
* This event is raised when the system appearance changes.
*/
export function on(event: "systemAppearanceChanged", callback: (args: SystemAppearanceChangedEventData) => void, thisArg?: any);
/**
* Gets the orientation of the application.
* Available values: "portrait", "landscape", "unknown".
*/
export function orientation(): "portrait" | "landscape" | "unknown";
=======
* Appends new CSS class to the system classes and applies it to the root view.
* @param rootView - The root view of the application.
* @param cssClass - The CSS class to apply.
*/
export function applyCssClass(rootView: View, cssClass: string);
/**
* Removes CSS class from the system classes and deletes it from the root view.
* @param rootView - The root view of the application.
* @param cssClass - The CSS class to delete.
*/
export function removeCssClass(rootView: View, cssClass: string);
>>>>>>> refactor(dark-mode): application module
/**
* This is the Android-specific application object instance.
@@ -440,6 +440,12 @@ export class AndroidApplication extends Observable {
*/
orientation: "portrait" | "landscape" | "unknown";
/**
* Gets the system appearance.
* Available values: "dark", "light".
*/
systemAppearance: "dark" | "light";
/**
* The name of the application package.
*/
@@ -619,6 +625,12 @@ export interface iOSApplication {
*/
orientation: "portrait" | "landscape" | "unknown";
/**
* Gets the system appearance.
* Available values: "dark", "light".
*/
systemAppearance: "dark" | "light";
/**
* The [UIApplication](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplication_Class/index.html).
*/

View File

@@ -4,12 +4,14 @@ import {
iOSApplication as IOSApplicationDefinition,
LaunchEventData,
LoadAppCSSEventData,
OrientationChangedEventData
OrientationChangedEventData,
SystemAppearanceChangedEventData
} from ".";
import {
applyCssClass, displayedEvent, exitEvent, getCssFileName, launchEvent, livesync, lowMemoryEvent, notify, on,
orientationChanged, orientationChangedEvent, removeCssClass, resumeEvent, setApplication, suspendEvent
displayedEvent, exitEvent, getCssFileName, launchEvent, livesync, lowMemoryEvent, notify, on,
orientationChanged, orientationChangedEvent, resumeEvent, setApplication, suspendEvent,
systemAppearanceChanged, systemAppearanceChangedEvent
} from "./application-common";
// First reexport so that app module is initialized.
@@ -25,18 +27,12 @@ import {
} from "../css/system-classes";
import { ios as iosView, View } from "../ui/core/view";
import { Frame, NavigationEntry } from "../ui/frame";
import { UserInterfaceStyle } from "../ui/enums/enums";
import { device } from "../platform/platform";
import { profile } from "../profiling";
import { ios } from "../utils/utils";
const IOS_PLATFORM = "ios";
const UI_STYLE_CSS_CLASSES = [
`${CLASS_PREFIX}${UserInterfaceStyle.light}`,
`${CLASS_PREFIX}${UserInterfaceStyle.dark}`
];
const getVisibleViewController = ios.getVisibleViewController;
// NOTE: UIResponder with implementation of window - related to https://github.com/NativeScript/ios-runtime/issues/430
@@ -97,6 +93,7 @@ class IOSApplication implements IOSApplicationDefinition {
private _observers: Array<NotificationObserver>;
private _orientation: "portrait" | "landscape" | "unknown";
private _rootView: View;
private _systemAppearance: "light" | "dark";
constructor() {
this._observers = new Array<NotificationObserver>();
@@ -121,6 +118,15 @@ class IOSApplication implements IOSApplicationDefinition {
return this._window.rootViewController;
}
get systemAppearance(): "light" | "dark" {
if (!this._systemAppearance) {
const userInterfaceStyle = this.rootController.traitCollection.userInterfaceStyle;
this._systemAppearance = getSystemAppearanceValue(userInterfaceStyle);
}
return this._systemAppearance;
}
get nativeApp(): UIApplication {
return UIApplication.sharedApplication;
}
@@ -293,9 +299,21 @@ class IOSApplication implements IOSApplicationDefinition {
this._window.makeKeyAndVisible();
}
setupRootViewCssClasses(controller, rootView);
setupRootViewCssClasses(rootView);
rootView.on(iosView.traitCollectionColorAppearanceChangedEvent, () => {
traitCollectionColorAppearanceChanged(controller, rootView);
const userInterfaceStyle = controller.traitCollection.userInterfaceStyle;
const newSystemAppearance = getSystemAppearanceValue(userInterfaceStyle);
if (this._systemAppearance !== newSystemAppearance) {
this._systemAppearance = newSystemAppearance;
notify(<SystemAppearanceChangedEventData>{
eventName: systemAppearanceChangedEvent,
ios: this,
newValue: this._systemAppearance,
object: this
});
}
});
}
}
@@ -312,17 +330,6 @@ setApplication(iosApp);
let mainEntry: NavigationEntry;
function traitCollectionColorAppearanceChanged(controller: UIViewController, rootView: View) {
const newUserInterfaceStyle = controller.traitCollection.userInterfaceStyle;
const newUserInterfaceStyleValue = getUserInterfaceStyleValue(newUserInterfaceStyle);
const newUserInterfaceStyleCssClass = `${CLASS_PREFIX}${newUserInterfaceStyleValue}`;
if (!rootView.cssClasses.has(newUserInterfaceStyleCssClass)) {
UI_STYLE_CSS_CLASSES.forEach(cssClass => removeCssClass(rootView, cssClass));
applyCssClass(rootView, newUserInterfaceStyleCssClass);
}
}
function createRootView(v?: View) {
let rootView = v;
if (!rootView) {
@@ -379,9 +386,21 @@ export function _start(entry?: string | NavigationEntry) {
visibleVC.presentViewControllerAnimatedCompletion(controller, true, null);
}
setupRootViewCssClasses(controller, rootView);
setupRootViewCssClasses(rootView);
rootView.on(iosView.traitCollectionColorAppearanceChangedEvent, () => {
traitCollectionColorAppearanceChanged(controller, rootView);
const userInterfaceStyle = controller.traitCollection.userInterfaceStyle;
const newSystemAppearance = getSystemAppearanceValue(userInterfaceStyle);
if (this._systemAppearance !== newSystemAppearance) {
this._systemAppearance = newSystemAppearance;
notify(<SystemAppearanceChangedEventData>{
eventName: systemAppearanceChangedEvent,
ios: this,
newValue: this._systemAppearance,
object: this
});
}
});
iosApp.notifyAppStarted();
}
@@ -413,7 +432,7 @@ export function getNativeApplication(): UIApplication {
return iosApp.nativeApp;
}
function getUserInterfaceStyleValue(userInterfaceStyle: number): "dark" | "light" | "unspecified" {
function getSystemAppearanceValue(userInterfaceStyle: number): "dark" | "light" {
switch (userInterfaceStyle) {
case UIUserInterfaceStyle.Unspecified:
case UIUserInterfaceStyle.Light:
@@ -449,17 +468,14 @@ function setViewControllerView(view: View): void {
}
}
function setupRootViewCssClasses(controller: UIViewController, rootView: View): void {
function setupRootViewCssClasses(rootView: View): void {
resetRootViewCssClasses();
const deviceType = device.deviceType.toLowerCase();
const userInterfaceStyle = controller.traitCollection.userInterfaceStyle;
const userInterfaceStyleValue = getUserInterfaceStyleValue(userInterfaceStyle);
pushToRootViewCssClasses(`${CLASS_PREFIX}${IOS_PLATFORM}`);
pushToRootViewCssClasses(`${CLASS_PREFIX}${deviceType}`);
pushToRootViewCssClasses(`${CLASS_PREFIX}${iosApp.orientation}`);
pushToRootViewCssClasses(`${CLASS_PREFIX}${userInterfaceStyleValue}`);
pushToRootViewCssClasses(`${CLASS_PREFIX}${iosApp.systemAppearance}`);
const rootViewCssClasses = getRootViewCssClasses();
rootViewCssClasses.forEach(c => rootView.cssClasses.add(c));
@@ -476,6 +492,13 @@ on(orientationChangedEvent, (args: OrientationChangedEventData) => {
}
});
on(systemAppearanceChangedEvent, (args: SystemAppearanceChangedEventData) => {
const rootView = getRootView();
if (rootView) {
systemAppearanceChanged(rootView, args.newValue);
}
});
global.__onLiveSync = function __onLiveSync(context?: ModuleContext) {
if (!started) {
return;

View File

@@ -188,7 +188,7 @@ export module StatusBarStyle {
export const dark = "dark";
}
export module UserInterfaceStyle {
export module SystemAppearance {
export const light = "light";
export const dark = "dark";
}

View File

@@ -1289,6 +1289,7 @@ class ActivityCallbacksImplementation implements AndroidActivityCallbacks {
pushToRootViewCssClasses(`${CLASS_PREFIX}${ANDROID_PLATFORM}`);
pushToRootViewCssClasses(`${CLASS_PREFIX}${deviceType}`);
pushToRootViewCssClasses(`${CLASS_PREFIX}${application.android.orientation}`);
pushToRootViewCssClasses(`${CLASS_PREFIX}${application.android.systemAppearance}`);
const rootViewCssClasses = getRootViewCssClasses();
rootViewCssClasses.forEach(c => this._rootView.cssClasses.add(c));