From e2c3c8c0845949efc8a4b1f13f88e27f209bb42d Mon Sep 17 00:00:00 2001 From: Vasil Chimev Date: Wed, 14 Aug 2019 13:47:15 +0300 Subject: [PATCH] feat: expose application orientation (#7602) --- .../application/application-tests-common.ts | 19 ++---- .../application/application-tests.android.ts | 7 +- tests/app/application/application-tests.d.ts | 4 +- .../app/application/application-tests.ios.ts | 10 +++ tests/app/application/application.md | 34 ---------- tests/app/platform/platform-tests.ts | 59 ++++++++-------- .../application/application-common.ts | 4 +- .../application/application.android.ts | 68 +++++++++++-------- tns-core-modules/application/application.d.ts | 22 ++++-- .../application/application.ios.ts | 60 +++++++++------- tns-core-modules/platform/platform.android.ts | 8 +-- tns-core-modules/platform/platform.d.ts | 2 +- tns-core-modules/platform/platform.ios.ts | 4 +- tns-core-modules/utils/utils.d.ts | 2 + tns-core-modules/utils/utils.ios.ts | 13 ++-- 15 files changed, 161 insertions(+), 155 deletions(-) delete mode 100644 tests/app/application/application.md diff --git a/tests/app/application/application-tests-common.ts b/tests/app/application/application-tests-common.ts index aa821b436..47dc01e94 100644 --- a/tests/app/application/application-tests-common.ts +++ b/tests/app/application/application-tests-common.ts @@ -1,21 +1,16 @@ -// >> application-require -import * as app from "tns-core-modules/application"; +import * as app from "tns-core-modules/application"; import * as platform from "tns-core-modules/platform"; -// << application-require - -// >> application-app-check -if (app.android) { - console.log("We are running on Android device!"); -} else if (app.ios) { - console.log("We are running on iOS device"); -} -// << application-app-check import * as TKUnit from "../tk-unit"; +if (app.android) { + console.log("We are running on an Android device!"); +} else if (app.ios) { + console.log("We are running on an iOS device!"); +} + export function testInitialized() { if (platform.device.os === platform.platformNames.android) { - // we have the android defined TKUnit.assert(app.android, "Application module not properly intialized"); } else if (platform.device.os === platform.platformNames.ios) { TKUnit.assert(app.ios, "Application module not properly intialized"); diff --git a/tests/app/application/application-tests.android.ts b/tests/app/application/application-tests.android.ts index 3150ba958..1c76429fe 100644 --- a/tests/app/application/application-tests.android.ts +++ b/tests/app/application/application-tests.android.ts @@ -37,12 +37,13 @@ if (app.android) { } // << application-app-android-broadcast -export var testAndroidApplicationInitialized = function () { +export function testAndroidApplicationInitialized() { TKUnit.assert(app.android, "Android application not initialized."); TKUnit.assert(app.android.context, "Android context not initialized."); TKUnit.assert(app.android.foregroundActivity, "Android foregroundActivity not initialized."); - TKUnit.assert(app.android.foregroundActivity.isNativeScriptActivity, "Andorid foregroundActivity.isNativeScriptActivity is true"); + TKUnit.assert(app.android.foregroundActivity.isNativeScriptActivity, "Andorid foregroundActivity.isNativeScriptActivity is false."); TKUnit.assert(app.android.startActivity, "Android startActivity not initialized."); TKUnit.assert(app.android.nativeApp, "Android nativeApp not initialized."); + TKUnit.assert(app.android.orientation, "Android orientation not initialized."); TKUnit.assert(app.android.packageName, "Android packageName not initialized."); -}; +} diff --git a/tests/app/application/application-tests.d.ts b/tests/app/application/application-tests.d.ts index 5b8d14752..4f5ea1a4c 100644 --- a/tests/app/application/application-tests.d.ts +++ b/tests/app/application/application-tests.d.ts @@ -1,4 +1,2 @@ -/* tslint:disable */ -//@private import * as android from "./application-tests.android"; -import * as iOS from "./application-tests.ios"; \ No newline at end of file +import * as iOS from "./application-tests.ios"; diff --git a/tests/app/application/application-tests.ios.ts b/tests/app/application/application-tests.ios.ts index 42239a6ca..a6c9a054e 100644 --- a/tests/app/application/application-tests.ios.ts +++ b/tests/app/application/application-tests.ios.ts @@ -1,5 +1,6 @@ /* tslint:disable:no-unused-variable */ import * as app from "tns-core-modules/application"; +import * as TKUnit from "../tk-unit"; export * from "./application-tests-common"; @@ -39,3 +40,12 @@ if (app.ios) { } // << application-ios-delegate + +export function testIOSApplicationInitialized() { + TKUnit.assert(app.ios, "iOS application not initialized."); + TKUnit.assert(app.ios.delegate, "iOS delegate not initialized."); + TKUnit.assert(app.ios.nativeApp, "iOS nativeApp not initialized."); + TKUnit.assert(app.ios.orientation, "iOS orientation not initialized."); + TKUnit.assert(app.ios.window, "iOS window not initialized."); + TKUnit.assert(app.ios.rootController, "iOS root controller not initialized."); +} diff --git a/tests/app/application/application.md b/tests/app/application/application.md deleted file mode 100644 index 52d311cb3..000000000 --- a/tests/app/application/application.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -nav-title: "application How-To" -title: "application" -environment: nativescript -description: "Examples for using application" -previous_url: /ApiReference/application/HOW-TO ---- -# Application -The Application module provides abstraction over the platform-specific Application implementations. -It is the main BCL module and is required for other BCL modules to work properly. -The default bootstrap.js implementation for each platform loads and initializes this module. -{%snippet application-require%} - -The pre-required `app` module is used throughout the following code snippets. -### Checking the target platform -Use the following code in case you need to check somewhere in your code the platform you are running against: -{%snippet application-app-check%} - -### Using the Android-specific implementation -Accessing the Android-specific object instance (will be undefined if running on iOS) - -{%snippet application-app-android%} - -### Using the Android Application context -{%snippet application-app-android-context%} - -### Tracking the current Activity -{%snippet application-app-android-current%} - -### Registering a Broadcast Receiver (Android) -{%snippet application-app-android-broadcast%} - -### Adding a Notification Observer (iOS) -{%snippet application-ios-observer%} diff --git a/tests/app/platform/platform-tests.ts b/tests/app/platform/platform-tests.ts index 9b6b98ceb..7d858d656 100644 --- a/tests/app/platform/platform-tests.ts +++ b/tests/app/platform/platform-tests.ts @@ -1,46 +1,41 @@ import * as TKUnit from "../tk-unit"; import * as app from "tns-core-modules/application"; -import { isIOS, isAndroid } from "tns-core-modules/platform"; - -// >> platform-require import * as platformModule from "tns-core-modules/platform"; -// << platform-require -export function test_setTimeout_isDefined() { - var expected; +export function test_platform() { + let expectedPlatform; if (app.android) { - expected = "Android"; + expectedPlatform = "Android"; + } else { + expectedPlatform = "iOS"; } - else { - expected = "iOS"; - } - TKUnit.assertEqual(platformModule.device.os, expected, "device.os"); + TKUnit.assertEqual(platformModule.device.os, expectedPlatform); } -export function snippet_print_all() { - // >> platform-current - console.log("Device model: " + platformModule.device.model); - console.log("Device type: " + platformModule.device.deviceType); - console.log("Device manufacturer: " + platformModule.device.manufacturer); - console.log("Preferred language: " + platformModule.device.language); - console.log("Preferred region: " + platformModule.device.region); - console.log("OS: " + platformModule.device.os); - console.log("OS version: " + platformModule.device.osVersion); - console.log("SDK version: " + platformModule.device.sdkVersion); - console.log("Device UUID: " + platformModule.device.uuid); +export function test_device_screen() { + TKUnit.assert(platformModule.device.model, "Device model not initialized."); + TKUnit.assert(platformModule.device.manufacturer, "Device manufacturer not initialized."); + TKUnit.assert(platformModule.device.deviceType, "Device type not initialized."); + TKUnit.assert(platformModule.device.uuid, "Device UUID not initialized."); - console.log("Screen width (px): " + platformModule.screen.mainScreen.widthPixels); - console.log("Screen height (px): " + platformModule.screen.mainScreen.heightPixels); - console.log("Screen width (DIPs): " + platformModule.screen.mainScreen.widthDIPs); - console.log("Screen height (DIPs): " + platformModule.screen.mainScreen.heightDIPs); - console.log("Screen scale: " + platformModule.screen.mainScreen.scale); - // << platform-current + TKUnit.assert(platformModule.device.language, "Preferred language not initialized."); + TKUnit.assert(platformModule.device.region, "Preferred region not initialized."); + + TKUnit.assert(platformModule.device.os, "OS not initialized."); + TKUnit.assert(platformModule.device.osVersion, "OS version not initialized."); + TKUnit.assert(platformModule.device.sdkVersion, "SDK version not initialized."); + + TKUnit.assert(platformModule.screen.mainScreen.widthPixels, "Screen width (px) not initialized."); + TKUnit.assert(platformModule.screen.mainScreen.heightPixels, "Screen height (px) not initialized."); + TKUnit.assert(platformModule.screen.mainScreen.widthDIPs, "Screen width (DIPs) not initialized."); + TKUnit.assert(platformModule.screen.mainScreen.heightDIPs, "Screen height (DIPs) not initialized."); + TKUnit.assert(platformModule.screen.mainScreen.scale, "Screen scale not initialized."); } -export function testIsIOSandIsAndroid() { - if (isIOS) { +export function test_IsAndroid_IsIOS() { + if (platformModule.isIOS) { TKUnit.assertTrue(!!NSObject, "isIOS is true-ish but common iOS APIs are not available."); - } else if (isAndroid) { - TKUnit.assertTrue(!!android, "isAndroid is true but common 'android' package is not available."); + } else if (platformModule.isAndroid) { + TKUnit.assertTrue(!!android, "isAndroid is true-ish but common 'android' package is not available."); } } diff --git a/tns-core-modules/application/application-common.ts b/tns-core-modules/application/application-common.ts index fc06b8afd..d043e91c0 100644 --- a/tns-core-modules/application/application-common.ts +++ b/tns-core-modules/application/application-common.ts @@ -35,10 +35,10 @@ export { Observable }; import { AndroidApplication, CssChangedEventData, + DiscardedErrorEventData, iOSApplication, LoadAppCSSEventData, - UnhandledErrorEventData, - DiscardedErrorEventData, + UnhandledErrorEventData } from "./application"; export { UnhandledErrorEventData, DiscardedErrorEventData, CssChangedEventData, LoadAppCSSEventData }; diff --git a/tns-core-modules/application/application.android.ts b/tns-core-modules/application/application.android.ts index 79f542c28..a256c3760 100644 --- a/tns-core-modules/application/application.android.ts +++ b/tns-core-modules/application/application.android.ts @@ -42,6 +42,7 @@ export class AndroidApplication extends Observable implements AndroidApplication public static activityNewIntentEvent = ActivityNewIntent; public static activityRequestPermissionsEvent = ActivityRequestPermissions; + private _orientation: "portrait" | "landscape" | "unknown"; public paused: boolean; public nativeApp: android.app.Application; public context: android.content.Context; @@ -80,6 +81,22 @@ export class AndroidApplication extends Observable implements AndroidApplication this._pendingReceiverRegistrations.length = 0; } + get orientation(): "portrait" | "landscape" | "unknown" { + if (!this._orientation) { + const resources = this.context.getResources(); + const configuration = resources.getConfiguration(); + const orientation = configuration.orientation; + + this._orientation = getOrientationValue(orientation); + } + + return this._orientation; + } + + set orientation(value: "portrait" | "landscape" | "unknown") { + this._orientation = value; + } + public registerBroadcastReceiver(intentFilter: string, onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void) { ensureBroadCastReceiverClass(); const that = this; @@ -232,6 +249,17 @@ global.__onLiveSync = function __onLiveSync(context?: ModuleContext) { livesync(rootView, context); }; +function getOrientationValue(orientation: number): "portrait" | "landscape" | "unknown" { + switch (orientation) { + case android.content.res.Configuration.ORIENTATION_LANDSCAPE: + return "landscape"; + case android.content.res.Configuration.ORIENTATION_PORTRAIT: + return "portrait"; + default: + return "unknown"; + } +} + function initLifecycleCallbacks() { const setThemeOnLaunch = profile("setThemeOnLaunch", (activity: androidx.appcompat.app.AppCompatActivity) => { // Set app theme after launch screen was used during startup @@ -321,7 +349,6 @@ function initLifecycleCallbacks() { return lifecycleCallbacks; } -let currentOrientation: number; function initComponentCallbacks() { let componentCallbacks = new android.content.ComponentCallbacks2({ onLowMemory: profile("onLowMemory", function () { @@ -335,32 +362,19 @@ function initComponentCallbacks() { }), onConfigurationChanged: profile("onConfigurationChanged", function (newConfig: android.content.res.Configuration) { - const newOrientation = newConfig.orientation; - if (newOrientation === currentOrientation) { - return; + const newConfigOrientation = newConfig.orientation; + const newOrientation = getOrientationValue(newConfigOrientation); + + if (androidApp.orientation !== newOrientation) { + androidApp.orientation = newOrientation; + + notify({ + eventName: orientationChangedEvent, + android: androidApp.nativeApp, + newValue: androidApp.orientation, + object: androidApp + }); } - - currentOrientation = newOrientation; - let newValue; - - switch (newOrientation) { - case android.content.res.Configuration.ORIENTATION_LANDSCAPE: - newValue = "landscape"; - break; - case android.content.res.Configuration.ORIENTATION_PORTRAIT: - newValue = "portrait"; - break; - default: - newValue = "unknown"; - break; - } - - notify({ - eventName: orientationChangedEvent, - android: androidApp.nativeApp, - newValue: newValue, - object: androidApp - }); }) }); @@ -399,4 +413,4 @@ declare namespace com { static getInstance(): NativeScriptApplication; } } -} \ No newline at end of file +} diff --git a/tns-core-modules/application/application.d.ts b/tns-core-modules/application/application.d.ts index c6a08f56c..672e55cc5 100644 --- a/tns-core-modules/application/application.d.ts +++ b/tns-core-modules/application/application.d.ts @@ -413,6 +413,12 @@ export class AndroidApplication extends Observable { */ startActivity: any /* androidx.appcompat.app.AppCompatActivity */; + /** + * Gets the orientation of the application. + * Available values: "portrait", "landscape", "unknown". + */ + orientation: "portrait" | "landscape" | "unknown"; + /** * The name of the application package. */ @@ -581,16 +587,22 @@ export interface iOSApplication { */ window: any /* UIWindow */; - /** - * The [UIApplication](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplication_Class/index.html). - */ - nativeApp: any /* UIApplication */; - /** * The [UIApplicationDelegate](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/index.html) class. */ delegate: any /* typeof UIApplicationDelegate */; + /** + * Gets or sets the orientation of the application. + * Available values: "portrait", "landscape", "unknown". + */ + orientation: "portrait" | "landscape" | "unknown"; + + /** + * The [UIApplication](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplication_Class/index.html). + */ + nativeApp: any /* UIApplication */; + /** * Adds an observer to the default notification center for the specified notification. * For more information, please visit 'https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/#//apple_ref/occ/instm/NSNotificationCenter/addObserver:selector:name:object:' diff --git a/tns-core-modules/application/application.ios.ts b/tns-core-modules/application/application.ios.ts index b490b35bb..957ad29a5 100644 --- a/tns-core-modules/application/application.ios.ts +++ b/tns-core-modules/application/application.ios.ts @@ -78,9 +78,9 @@ class CADisplayLinkTarget extends NSObject { class IOSApplication implements IOSApplicationDefinition { private _delegate: typeof UIApplicationDelegate; - private _currentOrientation = UIDevice.currentDevice.orientation; private _window: UIWindow; private _observers: Array; + private _orientation: "portrait" | "landscape" | "unknown"; private _rootView: View; constructor() { @@ -90,7 +90,16 @@ class IOSApplication implements IOSApplicationDefinition { this.addNotificationObserver(UIApplicationDidEnterBackgroundNotification, this.didEnterBackground.bind(this)); this.addNotificationObserver(UIApplicationWillTerminateNotification, this.willTerminate.bind(this)); this.addNotificationObserver(UIApplicationDidReceiveMemoryWarningNotification, this.didReceiveMemoryWarning.bind(this)); - this.addNotificationObserver(UIDeviceOrientationDidChangeNotification, this.orientationDidChange.bind(this)); + this.addNotificationObserver(UIApplicationDidChangeStatusBarOrientationNotification, this.didChangeStatusBarOrientation.bind(this)); + } + + get orientation(): "portrait" | "landscape" | "unknown" { + if (!this._orientation) { + const statusBarOrientation = UIApplication.sharedApplication.statusBarOrientation; + this._orientation = this.getOrientationValue(statusBarOrientation); + } + + return this._orientation; } get rootController(): UIViewController { @@ -197,40 +206,39 @@ class IOSApplication implements IOSApplicationDefinition { } } - private didReceiveMemoryWarning(notification: NSNotification) { - notify({ eventName: lowMemoryEvent, object: this, ios: UIApplication.sharedApplication }); - } + private didChangeStatusBarOrientation(notification: NSNotification) { + const statusBarOrientation = UIApplication.sharedApplication.statusBarOrientation; + const newOrientation = this.getOrientationValue(statusBarOrientation); - private orientationDidChange(notification: NSNotification) { - const orientation = UIDevice.currentDevice.orientation; - - if (this._currentOrientation !== orientation) { - this._currentOrientation = orientation; - - let newValue: "portrait" | "landscape" | "unknown"; - switch (orientation) { - case UIDeviceOrientation.LandscapeRight: - case UIDeviceOrientation.LandscapeLeft: - newValue = "landscape"; - break; - case UIDeviceOrientation.Portrait: - case UIDeviceOrientation.PortraitUpsideDown: - newValue = "portrait"; - break; - default: - newValue = "unknown"; - break; - } + if (this._orientation !== newOrientation) { + this._orientation = newOrientation; notify({ eventName: orientationChangedEvent, ios: this, - newValue: newValue, + newValue: this._orientation, object: this }); } } + private didReceiveMemoryWarning(notification: NSNotification) { + notify({ eventName: lowMemoryEvent, object: this, ios: UIApplication.sharedApplication }); + } + + private getOrientationValue(orientation: number): "portrait" | "landscape" | "unknown" { + switch (orientation) { + case UIInterfaceOrientation.LandscapeRight: + case UIInterfaceOrientation.LandscapeLeft: + return "landscape"; + case UIInterfaceOrientation.PortraitUpsideDown: + case UIInterfaceOrientation.Portrait: + return "portrait"; + case UIInterfaceOrientation.Unknown: + return "unknown"; + } + } + public _onLivesync(context?: ModuleContext): void { // Handle application root module const isAppRootModuleChanged = context && context.path && context.path.includes(getMainEntry().moduleName) && context.type !== "style"; diff --git a/tns-core-modules/platform/platform.android.ts b/tns-core-modules/platform/platform.android.ts index 5dbe0ccf6..16deb1119 100644 --- a/tns-core-modules/platform/platform.android.ts +++ b/tns-core-modules/platform/platform.android.ts @@ -19,10 +19,6 @@ class Device implements DeviceDefinition { private _language: string; private _region: string; - get os(): string { - return platformNames.android; - } - get manufacturer(): string { if (!this._manufacturer) { this._manufacturer = android.os.Build.MANUFACTURER; @@ -31,6 +27,10 @@ class Device implements DeviceDefinition { return this._manufacturer; } + get os(): string { + return platformNames.android; + } + get osVersion(): string { if (!this._osVersion) { this._osVersion = android.os.Build.VERSION.RELEASE; diff --git a/tns-core-modules/platform/platform.d.ts b/tns-core-modules/platform/platform.d.ts index bc08f2c58..4335eb274 100644 --- a/tns-core-modules/platform/platform.d.ts +++ b/tns-core-modules/platform/platform.d.ts @@ -125,4 +125,4 @@ export module screen { /** * Gets the current device information. */ -export const device: Device; +export const device: Device; diff --git a/tns-core-modules/platform/platform.ios.ts b/tns-core-modules/platform/platform.ios.ts index 1eaaf9ea6..5c56c394d 100644 --- a/tns-core-modules/platform/platform.ios.ts +++ b/tns-core-modules/platform/platform.ios.ts @@ -78,7 +78,7 @@ class Device implements DeviceDefinition { const languages = NSLocale.preferredLanguages; this._language = languages[0]; } - + return this._language; } @@ -93,7 +93,7 @@ class Device implements DeviceDefinition { class MainScreen implements ScreenMetricsDefinition { private _screen: UIScreen; - + private get screen(): UIScreen { if (!this._screen) { this._screen = UIScreen.mainScreen; diff --git a/tns-core-modules/utils/utils.d.ts b/tns-core-modules/utils/utils.d.ts index ce9923659..f17be503d 100644 --- a/tns-core-modules/utils/utils.d.ts +++ b/tns-core-modules/utils/utils.d.ts @@ -215,6 +215,8 @@ export module ios { } /** + * @deprecated use application.orientation instead + * * Gets an information about if current mode is Landscape. */ export function isLandscape(): boolean; diff --git a/tns-core-modules/utils/utils.ios.ts b/tns-core-modules/utils/utils.ios.ts index 335497660..5c075eb73 100644 --- a/tns-core-modules/utils/utils.ios.ts +++ b/tns-core-modules/utils/utils.ios.ts @@ -8,7 +8,8 @@ export * from "./utils-common"; let mainScreenScale; function isOrientationLandscape(orientation: number) { - return orientation === UIDeviceOrientation.LandscapeLeft || orientation === UIDeviceOrientation.LandscapeRight; + return orientation === UIDeviceOrientation.LandscapeLeft /* 3 */ || + orientation === UIDeviceOrientation.LandscapeRight /* 4 */; } export module layout { @@ -79,11 +80,15 @@ export module ios { } export function isLandscape(): boolean { - const device = UIDevice.currentDevice; + console.log("utils.ios.isLandscape() is deprecated; use application.orientation instead"); + + const deviceOrientation = UIDevice.currentDevice.orientation; const statusBarOrientation = UIApplication.sharedApplication.statusBarOrientation; + + const isDeviceOrientationLandscape = isOrientationLandscape(deviceOrientation); const isStatusBarOrientationLandscape = isOrientationLandscape(statusBarOrientation); - return isOrientationLandscape(device.orientation) || isStatusBarOrientationLandscape; + return isDeviceOrientationLandscape || isStatusBarOrientationLandscape; } export const MajorVersion = NSString.stringWithString(UIDevice.currentDevice.systemVersion).intValue; @@ -153,7 +158,7 @@ export function openFile(filePath: string): boolean { } // Need this so that we can use this function inside the ios module (avoid name clashing). -const openFileAtRootModule = openFile; +const openFileAtRootModule = openFile; export function GC() { __collect();