Skip to content

Commit 3adba68

Browse files
authored
feat: add CSS classes to app/modal root views to target platform/device/orientation/type (NativeScript#7606)
1 parent 70f8d70 commit 3adba68

File tree

10 files changed

+284
-70
lines changed

10 files changed

+284
-70
lines changed

tests/app/test-runner.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ if (platform.isIOS && ios.MajorVersion > 10) {
154154
allTests["SAFEAREA-WEBVIEW"] = webViewSafeAreaTests;
155155
}
156156

157+
import * as rootViewsCssClassesTests from "./ui/styling/root-views-css-classes-tests";
158+
allTests["ROOT-VIEWS-CSS-CLASSES"] = rootViewsCssClassesTests;
159+
157160
import * as stylePropertiesTests from "./ui/styling/style-properties-tests";
158161
allTests["STYLE-PROPERTIES"] = stylePropertiesTests;
159162

tests/app/ui/page/page-tests-common.ts

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -505,11 +505,6 @@ function _test_WhenInnerViewCallsCloseModal(closeModalGetter: (ShownModallyData)
505505
helper.navigate(masterPageFactory);
506506

507507
TKUnit.waitUntilReady(() => modalClosedWithResult);
508-
509-
if (isIOS) {
510-
// Remove this line when we have a good way to detect actual modal close on ios
511-
TKUnit.waitUntilReady(() => !(<UIViewController>topmost().currentPage.viewController).presentedViewController);
512-
}
513508
}
514509

515510
export function test_WhenViewBaseCallsShowModal_WithArguments_ShouldOpenModal() {
@@ -572,11 +567,6 @@ export function test_WhenViewBaseCallsShowModal_WithArguments_ShouldOpenModal()
572567
helper.navigate(masterPageFactory);
573568

574569
TKUnit.waitUntilReady(() => modalClosed);
575-
576-
if (isIOS) {
577-
// Remove this line when we have a good way to detect actual modal close on ios
578-
TKUnit.waitUntilReady(() => !(<UIViewController>topmost().currentPage.viewController).presentedViewController);
579-
}
580570
}
581571

582572
export function test_WhenViewBaseCallsShowModal_WithShowModalOptionsArguments_ShouldOpenModal() {
@@ -794,11 +784,6 @@ export function test_WhenRootTabViewShownModallyItCanCloseModal() {
794784
helper.navigate(masterPageFactory);
795785

796786
TKUnit.waitUntilReady(() => modalClosed);
797-
798-
if (isIOS) {
799-
// Remove this line when we have a good way to detect actual modal close on ios
800-
TKUnit.waitUntilReady(() => !(<UIViewController>topmost().currentPage.viewController).presentedViewController);
801-
}
802787
}
803788

804789
export function test_WhenPageIsNavigatedToItCanShowAnotherPageAsModal() {
@@ -885,11 +870,6 @@ export function test_WhenPageIsNavigatedToItCanShowAnotherPageAsModal() {
885870
TKUnit.assertEqual(modalUnloaded, 1, "modalUnloaded");
886871

887872
masterPage.off(Page.navigatedToEvent, navigatedToEventHandler);
888-
889-
if (isIOS) {
890-
// Remove this line when we have a good way to detect actual modal close on ios
891-
TKUnit.waitUntilReady(() => !(<UIViewController>topmost().currentPage.viewController).presentedViewController);
892-
}
893873
}
894874

895875
export function test_WhenModalPageShownHostPageNavigationEventsShouldNotBeRaised() {
@@ -967,11 +947,6 @@ export function test_WhenModalPageShownHostPageNavigationEventsShouldNotBeRaised
967947

968948
TKUnit.waitUntilReady(() => ready);
969949

970-
if (isIOS) {
971-
// Remove this line when we have a good way to detect actual modal close on ios
972-
TKUnit.waitUntilReady(() => !(<UIViewController>topmost().currentPage.viewController).presentedViewController);
973-
}
974-
975950
// only raised by the initial navigation to the master page
976951
TKUnit.assertTrue(hostNavigatingToCount === 1);
977952
TKUnit.assertTrue(hostNavigatedToCount === 1);
@@ -1058,11 +1033,6 @@ export function test_WhenModalPageShownModalNavigationToEventsShouldBeRaised() {
10581033

10591034
TKUnit.waitUntilReady(() => ready && !modalFrame.isLoaded);
10601035

1061-
if (isIOS) {
1062-
// Remove this line when we have a good way to detect actual modal close on ios
1063-
TKUnit.waitUntilReady(() => !(<UIViewController>topmost().currentPage.viewController).presentedViewController);
1064-
}
1065-
10661036
// only raised by the initial show modal navigation
10671037
TKUnit.assertTrue(modalNavigatingToCount === 1);
10681038
TKUnit.assertTrue(modalNavigatedToCount === 1);
@@ -1138,12 +1108,6 @@ export function test_WhenModalFrameShownModalEventsRaisedOnRootModalFrame() {
11381108
helper.navigate(masterPageFactory);
11391109

11401110
TKUnit.waitUntilReady(() => ready && !modalFrame.isLoaded);
1141-
1142-
if (isIOS) {
1143-
// Remove this line when we have a good way to detect actual modal close on ios
1144-
TKUnit.waitUntilReady(() => !(<UIViewController>topmost().currentPage.viewController).presentedViewController);
1145-
}
1146-
11471111
TKUnit.assertTrue(showingModallyCount === 1);
11481112
TKUnit.assertTrue(shownModallyCount === 1);
11491113
}
@@ -1206,12 +1170,6 @@ export function test_WhenModalPageShownShowModalEventsRaisedOnRootModalPage() {
12061170
helper.navigate(masterPageFactory);
12071171

12081172
TKUnit.waitUntilReady(() => ready);
1209-
1210-
if (isIOS) {
1211-
// Remove this line when we have a good way to detect actual modal close on ios
1212-
TKUnit.waitUntilReady(() => !(<UIViewController>topmost().currentPage.viewController).presentedViewController);
1213-
}
1214-
12151173
TKUnit.assertTrue(showingModallyCount === 1);
12161174
TKUnit.assertTrue(shownModallyCount === 1);
12171175
}
@@ -1279,12 +1237,6 @@ export function test_WhenModalPageShownShowModalEventsRaisedOnRootModalTabView()
12791237
TKUnit.assertEqual(_stack().length, 2, "Host and modal tab frame should be instantiated at this point!");
12801238

12811239
TKUnit.waitUntilReady(() => ready);
1282-
1283-
if (isIOS) {
1284-
// Remove this line when we have a good way to detect actual modal close on ios
1285-
TKUnit.waitUntilReady(() => !(<UIViewController>topmost().currentPage.viewController).presentedViewController);
1286-
}
1287-
12881240
TKUnit.assertEqual(_stack().length, 1, "Single host frame should be instantiated at this point!");
12891241

12901242
TKUnit.assertTrue(showingModallyCount === 1);
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import * as helper from "../../ui-helper";
2+
import * as TKUnit from "../../tk-unit";
3+
4+
import {
5+
android,
6+
getRootView,
7+
ios
8+
} from "tns-core-modules/application";
9+
import {
10+
isAndroid,
11+
device
12+
} from "tns-core-modules/platform";
13+
import { Button } from "tns-core-modules/ui/button/button";
14+
import { Page } from "tns-core-modules/ui/page";
15+
import {
16+
ShownModallyData,
17+
ShowModalOptions,
18+
View
19+
} from "tns-core-modules/ui/frame";
20+
import {
21+
_rootModalViews
22+
} from "tns-core-modules/ui/core/view/view-common";
23+
import { DeviceType } from "tns-core-modules/ui/enums/enums";
24+
25+
const ROOT_CSS_CLASS = "ns-root";
26+
const MODAL_CSS_CLASS = "ns-modal";
27+
const ANDROID_PLATFORM_CSS_CLASS = "ns-android";
28+
const IOS_PLATFORM_CSS_CLASS = "ns-ios";
29+
const PHONE_DEVICE_TYPE_CSS_CLASS = "ns-phone";
30+
const TABLET_DEVICE_TYPE_CSS_CLASS = "ns-tablet";
31+
const PORTRAIT_ORIENTATION_CSS_CLASS = "ns-portrait";
32+
const LANDSCAPE_ORIENTATION_CSS_CLASS = "ns-landscape";
33+
const UNKNOWN_ORIENTATION_CSS_CLASS = "ns-unknown";
34+
35+
export function test_root_view_root_css_class() {
36+
const rootViewCssClasses = getRootView().cssClasses;
37+
38+
TKUnit.assertTrue(rootViewCssClasses.has(
39+
ROOT_CSS_CLASS),
40+
`${ROOT_CSS_CLASS} CSS class is missing`
41+
);
42+
}
43+
44+
export function test_root_view_platform_css_class() {
45+
const rootViewCssClasses = getRootView().cssClasses;
46+
47+
if (isAndroid) {
48+
TKUnit.assertTrue(rootViewCssClasses.has(
49+
ANDROID_PLATFORM_CSS_CLASS),
50+
`${ANDROID_PLATFORM_CSS_CLASS} CSS class is missing`
51+
);
52+
TKUnit.assertFalse(rootViewCssClasses.has(
53+
IOS_PLATFORM_CSS_CLASS),
54+
`${IOS_PLATFORM_CSS_CLASS} CSS class is present`
55+
);
56+
} else {
57+
TKUnit.assertTrue(rootViewCssClasses.has(
58+
IOS_PLATFORM_CSS_CLASS),
59+
`${IOS_PLATFORM_CSS_CLASS} CSS class is missing`
60+
);
61+
TKUnit.assertFalse(rootViewCssClasses.has(
62+
ANDROID_PLATFORM_CSS_CLASS),
63+
`${ANDROID_PLATFORM_CSS_CLASS} CSS class is present`
64+
);
65+
}
66+
}
67+
68+
export function test_root_view_device_type_css_class() {
69+
const rootViewCssClasses = getRootView().cssClasses;
70+
const deviceType = device.deviceType;
71+
72+
if (deviceType === DeviceType.Phone) {
73+
TKUnit.assertTrue(rootViewCssClasses.has(
74+
PHONE_DEVICE_TYPE_CSS_CLASS),
75+
`${PHONE_DEVICE_TYPE_CSS_CLASS} CSS class is missing`
76+
);
77+
TKUnit.assertFalse(rootViewCssClasses.has(
78+
TABLET_DEVICE_TYPE_CSS_CLASS),
79+
`${TABLET_DEVICE_TYPE_CSS_CLASS} CSS class is present`
80+
);
81+
} else {
82+
TKUnit.assertTrue(rootViewCssClasses.has(
83+
TABLET_DEVICE_TYPE_CSS_CLASS),
84+
`${TABLET_DEVICE_TYPE_CSS_CLASS} CSS class is missing`
85+
);
86+
TKUnit.assertFalse(rootViewCssClasses.has(
87+
PHONE_DEVICE_TYPE_CSS_CLASS),
88+
`${PHONE_DEVICE_TYPE_CSS_CLASS} CSS class is present`
89+
);
90+
}
91+
}
92+
93+
export function test_root_view_orientation_css_class() {
94+
const rootViewCssClasses = getRootView().cssClasses;
95+
let appOrientation;
96+
97+
if (isAndroid) {
98+
appOrientation = android.orientation;
99+
} else {
100+
appOrientation = ios.orientation;
101+
}
102+
103+
if (appOrientation === "portrait") {
104+
TKUnit.assertTrue(rootViewCssClasses.has(
105+
PORTRAIT_ORIENTATION_CSS_CLASS),
106+
`${PORTRAIT_ORIENTATION_CSS_CLASS} CSS class is missing`
107+
);
108+
TKUnit.assertFalse(rootViewCssClasses.has(
109+
LANDSCAPE_ORIENTATION_CSS_CLASS),
110+
`${LANDSCAPE_ORIENTATION_CSS_CLASS} CSS class is present`
111+
);
112+
TKUnit.assertFalse(rootViewCssClasses.has(
113+
UNKNOWN_ORIENTATION_CSS_CLASS),
114+
`${UNKNOWN_ORIENTATION_CSS_CLASS} CSS class is present`
115+
);
116+
} else if (appOrientation === "landscape") {
117+
TKUnit.assertTrue(rootViewCssClasses.has(
118+
LANDSCAPE_ORIENTATION_CSS_CLASS),
119+
`${LANDSCAPE_ORIENTATION_CSS_CLASS} CSS class is missing`
120+
);
121+
TKUnit.assertFalse(rootViewCssClasses.has(
122+
PORTRAIT_ORIENTATION_CSS_CLASS),
123+
`${PORTRAIT_ORIENTATION_CSS_CLASS} CSS class is present`
124+
);
125+
TKUnit.assertFalse(rootViewCssClasses.has(
126+
UNKNOWN_ORIENTATION_CSS_CLASS),
127+
`${UNKNOWN_ORIENTATION_CSS_CLASS} CSS class is present`
128+
);
129+
} else if (appOrientation === "landscape") {
130+
TKUnit.assertTrue(rootViewCssClasses.has(
131+
UNKNOWN_ORIENTATION_CSS_CLASS),
132+
`${UNKNOWN_ORIENTATION_CSS_CLASS} CSS class is missing`
133+
);
134+
TKUnit.assertFalse(rootViewCssClasses.has(
135+
LANDSCAPE_ORIENTATION_CSS_CLASS),
136+
`${LANDSCAPE_ORIENTATION_CSS_CLASS} CSS class is present`
137+
);
138+
TKUnit.assertFalse(rootViewCssClasses.has(
139+
PORTRAIT_ORIENTATION_CSS_CLASS),
140+
`${PORTRAIT_ORIENTATION_CSS_CLASS} CSS class is present`
141+
);
142+
}
143+
}
144+
145+
export function test_modal_root_view_modal_css_class() {
146+
let modalClosed = false;
147+
148+
const modalCloseCallback = function () {
149+
modalClosed = true;
150+
};
151+
152+
const modalPageShownModallyEventHandler = function (args: ShownModallyData) {
153+
const page = <Page>args.object;
154+
page.off(View.shownModallyEvent, modalPageShownModallyEventHandler);
155+
156+
TKUnit.assertTrue(_rootModalViews[0].cssClasses.has(MODAL_CSS_CLASS));
157+
args.closeCallback();
158+
};
159+
160+
const hostNavigatedToEventHandler = function (args) {
161+
const page = <Page>args.object;
162+
page.off(Page.navigatedToEvent, hostNavigatedToEventHandler);
163+
164+
const modalPage = new Page();
165+
modalPage.on(View.shownModallyEvent, modalPageShownModallyEventHandler);
166+
const button = <Button>page.content;
167+
const options: ShowModalOptions = {
168+
context: {},
169+
closeCallback: modalCloseCallback,
170+
fullscreen: false,
171+
animated: false
172+
};
173+
button.showModal(modalPage, options);
174+
};
175+
176+
const hostPageFactory = function (): Page {
177+
const hostPage = new Page();
178+
hostPage.on(Page.navigatedToEvent, hostNavigatedToEventHandler);
179+
180+
const button = new Button();
181+
hostPage.content = button;
182+
183+
return hostPage;
184+
};
185+
186+
helper.navigate(hostPageFactory);
187+
TKUnit.waitUntilReady(() => modalClosed);
188+
}

tns-core-modules/application/application-common.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
LoadAppCSSEventData,
4141
UnhandledErrorEventData
4242
} from "./application";
43+
import { DeviceOrientation } from "../ui/enums/enums";
4344

4445
export { UnhandledErrorEventData, DiscardedErrorEventData, CssChangedEventData, LoadAppCSSEventData };
4546

@@ -53,6 +54,13 @@ export const uncaughtErrorEvent = "uncaughtError";
5354
export const discardedErrorEvent = "discardedError";
5455
export const orientationChangedEvent = "orientationChanged";
5556

57+
export const CSS_CLASS_PREFIX = "ns-";
58+
const ORIENTATION_CSS_CLASSES = [
59+
`${CSS_CLASS_PREFIX}${DeviceOrientation.portrait}`,
60+
`${CSS_CLASS_PREFIX}${DeviceOrientation.landscape}`,
61+
`${CSS_CLASS_PREFIX}${DeviceOrientation.unknown}`
62+
];
63+
5664
let cssFile: string = "./app.css";
5765

5866
let resources: any = {};
@@ -92,7 +100,7 @@ export function livesync(rootView: View, context?: ModuleContext) {
92100
}
93101

94102
// Handle application styles
95-
if (reapplyAppStyles && rootView) {
103+
if (rootView && reapplyAppStyles) {
96104
rootView._onCssStateChange();
97105
} else if (liveSyncCore) {
98106
liveSyncCore(context);
@@ -117,6 +125,15 @@ export function loadAppCss(): void {
117125
}
118126
}
119127

128+
export function orientationChanged(rootView: View, newOrientation: "portrait" | "landscape" | "unknown"): void {
129+
const newOrientationCssClass = `${CSS_CLASS_PREFIX}${newOrientation}`;
130+
if (!rootView.cssClasses.has(newOrientationCssClass)) {
131+
ORIENTATION_CSS_CLASSES.forEach(c => rootView.cssClasses.delete(c));
132+
rootView.cssClasses.add(newOrientationCssClass);
133+
rootView._onCssStateChange();
134+
}
135+
}
136+
120137
global.__onUncaughtError = function (error: NativeScriptError) {
121138
events.notify(<UnhandledErrorEventData>{ eventName: uncaughtErrorEvent, object: app, android: error, ios: error, error: error });
122139
};

0 commit comments

Comments
 (0)