diff --git a/CrossPlatformModules.csproj b/CrossPlatformModules.csproj index 7b6e29e65..343b95001 100644 --- a/CrossPlatformModules.csproj +++ b/CrossPlatformModules.csproj @@ -79,6 +79,7 @@ data-binding.xml + main-page.xml @@ -107,6 +108,9 @@ + + Designer + Designer @@ -135,6 +139,7 @@ page-title-icon.xml + @@ -1777,6 +1782,9 @@ PreserveNewest + + PreserveNewest + diff --git a/application/application-common.ts b/application/application-common.ts index 89357cfdf..44aa07c6d 100644 --- a/application/application-common.ts +++ b/application/application-common.ts @@ -14,6 +14,7 @@ export var resumeEvent = "resume"; export var exitEvent = "exit"; export var lowMemoryEvent = "lowMemory"; export var uncaughtErrorEvent = "uncaughtError"; +export var orientationChangedEvent = "orientationChanged"; export var cssFile: string = "app.css" diff --git a/application/application.android.ts b/application/application.android.ts index fcbcbd2bb..213a44bd7 100644 --- a/application/application.android.ts +++ b/application/application.android.ts @@ -3,6 +3,7 @@ import dts = require("application"); import frame = require("ui/frame"); import types = require("utils/types"); import observable = require("data/observable"); +import enums = require("ui/enums"); global.moduleMerge(appModule, exports); @@ -206,6 +207,8 @@ export class AndroidApplication extends observable.Observable implements dts.And exports.notify({ eventName: dts.launchEvent, object: this, android: intent }); + setupOrientationListener(this); + /* In the onLaunch event we expect the following setup, which ensures a root frame: * var frame = require("ui/frame"); * var rootFrame = new frame.Frame(); @@ -315,3 +318,36 @@ exports.start = function () { } exports.android = new AndroidApplication(); + +var currentOrientation: number; +function setupOrientationListener(androidApp: AndroidApplication) { + androidApp.registerBroadcastReceiver(android.content.Intent.ACTION_CONFIGURATION_CHANGED, onConfigurationChanged); + currentOrientation = androidApp.context.getResources().getConfiguration().orientation +} +function onConfigurationChanged(context: android.content.Context, intent: android.content.Intent) { + var orientation = context.getResources().getConfiguration().orientation; + + if (currentOrientation !== orientation) { + currentOrientation = orientation; + + var newValue; + switch (orientation) { + case android.content.res.Configuration.ORIENTATION_LANDSCAPE: + newValue = enums.DeviceOrientation.landscape; + break; + case android.content.res.Configuration.ORIENTATION_PORTRAIT: + newValue = enums.DeviceOrientation.portrait; + break; + default: + newValue = enums.DeviceOrientation.unknown; + break; + } + + exports.notify( { + eventName: dts.orientationChangedEvent, + android: context, + newValue: newValue, + object: exports.android, + }); + } +} diff --git a/application/application.d.ts b/application/application.d.ts index b9b502cad..0f247614c 100644 --- a/application/application.d.ts +++ b/application/application.d.ts @@ -45,6 +45,11 @@ declare module "application" { */ export var lowMemoryEvent: string; + /** + * String value used when hooking to orientationChanged event. + */ + export var orientationChangedEvent: string; + /** * Event data containing information for the application events. */ @@ -70,6 +75,16 @@ declare module "application" { object: any; } + /** + * Event data containing information for orientation changed event. + */ + export interface OrientationChangedEventData extends ApplicationEventData { + /** + * New orientation value. + */ + newValue: string; + } + /** * The main page path (without the file extension) for the application starting from the application root. * For example if you have page called "main.js" in a folder called "subFolder" and your root folder is "app" you can specify mainModule like this: @@ -146,6 +161,14 @@ declare module "application" { */ export function on(eventNames: string, callback: (data: any) => void, thisArg?: any); + /** + * Shortcut alias to the removeEventListener method. + * @param eventNames - String corresponding to events (e.g. "onLaunch"). + * @param callback - Callback function which will be removed. + * @param thisArg - An optional parameter which will be used as `this` context for callback execution. + */ + export function off(eventNames: string, callback ?: any, thisArg ?: any); + /** * Notifies all the registered listeners for the event provided in the data.eventName. * @param data The data associated with the event. @@ -188,6 +211,11 @@ declare module "application" { */ export function on(event: "uncaughtError", callback: (args: ApplicationEventData) => void, thisArg?: any); + /** + * This event is raised the orientation of the current device has changed. + */ + export function on(event: "orientationChanged", callback: (args: OrientationChangedEventData) => void, thisArg?: any); + /** * This is the Android-specific application object instance. * Encapsulates methods and properties specific to the Android platform. diff --git a/application/application.ios.ts b/application/application.ios.ts index 00d8ca8e9..ac3226e23 100644 --- a/application/application.ios.ts +++ b/application/application.ios.ts @@ -4,7 +4,7 @@ import utils = require("utils/utils"); import types = require("utils/types"); import view = require("ui/core/view"); import definition = require("application"); - +import enums = require("ui/enums"); global.moduleMerge(appModule, exports); export var mainModule: string; @@ -61,10 +61,11 @@ class TNSAppDelegate extends UIResponder implements UIApplicationDelegate { return; } } + var app: IOSApplication = exports.ios; + setupOrientationListener(app); this.window.content = topFrame; this.window.rootViewController = topFrame.ios.controller; - var app: IOSApplication = exports.ios; app.rootController = this.window.rootViewController; this.window.makeKeyAndVisible(); return true; @@ -193,4 +194,40 @@ exports.start = function () { definition.notify({ eventName: definition.uncaughtErrorEvent, object: definition.ios, ios: error }); } -} \ No newline at end of file +} + +var currentOrientation: number; +function setupOrientationListener(iosApp: IOSApplication) { + iosApp.addNotificationObserver(UIDeviceOrientationDidChangeNotification, onOreintationDidChange) + currentOrientation = UIDevice.currentDevice().orientation; +} + +function onOreintationDidChange(notification: NSNotification) { + var orientation = UIDevice.currentDevice().orientation; + + if (currentOrientation !== orientation) { + currentOrientation = orientation; + + var newValue; + switch (orientation) { + case UIDeviceOrientation.UIDeviceOrientationLandscapeRight: + case UIDeviceOrientation.UIDeviceOrientationLandscapeLeft: + newValue = enums.DeviceOrientation.landscape; + break; + case UIDeviceOrientation.UIDeviceOrientationPortrait: + case UIDeviceOrientation.UIDeviceOrientationPortraitUpsideDown: + newValue = enums.DeviceOrientation.portrait; + break; + default: + newValue = enums.DeviceOrientation.unknown; + break; + } + + exports.notify({ + eventName: definition.orientationChangedEvent, + ios: exports.ios, + newValue: newValue, + object: exports.ios, + }); + } +} diff --git a/apps/notifications-demo/main-page.ts b/apps/notifications-demo/main-page.ts index b931bfe47..ac1a15874 100644 --- a/apps/notifications-demo/main-page.ts +++ b/apps/notifications-demo/main-page.ts @@ -5,6 +5,7 @@ import labelModule = require("ui/label"); var batteryLabel: labelModule.Label; var registered = false; + export function onPageLoaded(args: observable.EventData) { var page = args.object; batteryLabel = page.getViewById("batteryLabel"); @@ -36,4 +37,4 @@ export function onPageLoaded(args: observable.EventData) { application.ios.addNotificationObserver(UIDeviceBatteryLevelDidChangeNotification, onReceiveCallback); } registered = true; -} \ No newline at end of file +} diff --git a/apps/notifications-demo/main-page.xml b/apps/notifications-demo/main-page.xml index fd325a4c8..03b37f36a 100644 --- a/apps/notifications-demo/main-page.xml +++ b/apps/notifications-demo/main-page.xml @@ -1,4 +1,4 @@ - + diff --git a/apps/orientation-demo/app.ts b/apps/orientation-demo/app.ts new file mode 100644 index 000000000..0708869a4 --- /dev/null +++ b/apps/orientation-demo/app.ts @@ -0,0 +1,14 @@ +import application = require("application"); + +application.mainModule = "main-page"; + +application.on(application.exitEvent, () => { + if (application.android) { + application.android.unregisterBroadcastReceiver(android.content.Intent.ACTION_BATTERY_CHANGED); + } + else { + application.ios.removeNotificationObserver(UIDeviceBatteryLevelDidChangeNotification); + } +}); + +application.start(); diff --git a/apps/orientation-demo/main-page.port.xml b/apps/orientation-demo/main-page.port.xml new file mode 100644 index 000000000..5ac9e933c --- /dev/null +++ b/apps/orientation-demo/main-page.port.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/orientation-demo/main-page.ts b/apps/orientation-demo/main-page.ts new file mode 100644 index 000000000..bd6b6529e --- /dev/null +++ b/apps/orientation-demo/main-page.ts @@ -0,0 +1,21 @@ +import application = require("application"); +import observable = require("data/observable"); +import pageModule = require("ui/page"); + +var vm = new observable.Observable(); +function orientationChanged(data) { + console.log("Orientation changed: " + data.newValue); + vm.set("oreintation", data.newValue); +} +export function onPageLoaded(args: observable.EventData) { + var page = args.object; + application.on(application.orientationChangedEvent, orientationChanged, page); + + page.bindingContext = vm; + vm.set("oreintation", "not changed"); +} + +export function onPageUnloaded(args: observable.EventData) { + var page = args.object; + application.off(application.orientationChangedEvent, orientationChanged, page); +} diff --git a/apps/orientation-demo/package.json b/apps/orientation-demo/package.json new file mode 100644 index 000000000..8f939eb01 --- /dev/null +++ b/apps/orientation-demo/package.json @@ -0,0 +1,2 @@ +{ "name" : "page-reload-demo", + "main" : "app.js" } \ No newline at end of file diff --git a/file-system/file-name-resolver.d.ts b/file-system/file-name-resolver.d.ts index 257263414..2d9e5d370 100644 --- a/file-system/file-name-resolver.d.ts +++ b/file-system/file-name-resolver.d.ts @@ -14,7 +14,10 @@ declare module "file-system/file-name-resolver" { resolveFileName(path: string, ext: string): string; } + export function resolveFileName(path: string, ext: string): string; + //@private export function findFileMatch(path: string, ext: string, candidates: Array, context: PlatformContext): string //@endprivate + } \ No newline at end of file diff --git a/file-system/file-name-resolver.ts b/file-system/file-name-resolver.ts index 481206a11..fdf023498 100644 --- a/file-system/file-name-resolver.ts +++ b/file-system/file-name-resolver.ts @@ -2,6 +2,8 @@ import fs = require("file-system"); import types = require("utils/types"); import trace = require("trace"); +import platform = require("platform"); +import application = require("application"); var MIN_WH: string = "minWH"; var MIN_W: string = "minW"; @@ -79,7 +81,7 @@ var paltformQualifier: QualifierSpec = { value === "ios"; }, - getMatchValue(value: string, context: definition.PlatformContext): number{ + getMatchValue(value: string, context: definition.PlatformContext): number { return value === context.os.toLowerCase() ? 1 : -1; } } @@ -90,7 +92,7 @@ var orientationQualifier: QualifierSpec = { value === "port"; }, - getMatchValue(value: string, context: definition.PlatformContext): number{ + getMatchValue(value: string, context: definition.PlatformContext): number { var isLandscape: number = (context.width > context.height) ? 1 : -1; return (value === "land") ? isLandscape : -isLandscape; } @@ -213,3 +215,27 @@ function checkQualifier(value: string, context: definition.PlatformContext) { return -1; } + +var appEventAttached: boolean = false; +var resolverInstance: FileNameResolver; + +export function resolveFileName(path: string, ext: string): string { + if (!appEventAttached) { + application.on(application.orientationChangedEvent, (data) => { + resolverInstance = undefined; + }); + appEventAttached = true; + } + + if (!resolverInstance) { + resolverInstance = new FileNameResolver({ + width: platform.screen.mainScreen.widthDIPs, + height: platform.screen.mainScreen.heightDIPs, + os: platform.device.os, + deviceType: platform.device.deviceType + }); + } + + return resolverInstance.resolveFileName(path, ext); +} + diff --git a/platform/platform.android.ts b/platform/platform.android.ts index 82f17988f..69f008092 100644 --- a/platform/platform.android.ts +++ b/platform/platform.android.ts @@ -92,22 +92,39 @@ export class device implements definition.device { } } -var mainScreenInfo: definition.ScreenMetrics; +var mainScreen: MainScreen; -// This is a "static" class and it is used like a name-space. +// This is a "static" class and it is used like a namespace. // It is not meant to be initialized - thus it is not capitalized export class screen implements definition.screen { static get mainScreen(): definition.ScreenMetrics { - if (!mainScreenInfo) { + if (!mainScreen) { var metrics = utils.ad.getApplicationContext().getResources().getDisplayMetrics(); - mainScreenInfo = { - widthPixels: metrics.widthPixels, - heightPixels: metrics.heightPixels, - scale: metrics.density, - widthDIPs: metrics.widthPixels / metrics.density, - heightDIPs: metrics.heightPixels / metrics.density - } + mainScreen = new MainScreen(metrics); } - return mainScreenInfo; + return mainScreen; } } + +class MainScreen implements definition.ScreenMetrics { + private _metrics: android.util.DisplayMetrics; + constructor(metrics: android.util.DisplayMetrics) { + this._metrics = metrics; + } + + get widthPixels(): number { + return this._metrics.widthPixels; + } + get heightPixels(): number { + return this._metrics.heightPixels; + } + get scale(): number { + return this._metrics.density; + } + get widthDIPs(): number { + return this._metrics.widthPixels / this._metrics.density; + } + get heightDIPs(): number { + return this._metrics.heightPixels / this._metrics.density; + } +} \ No newline at end of file diff --git a/platform/platform.ios.ts b/platform/platform.ios.ts index 44e1ff22e..1f76a58c5 100644 --- a/platform/platform.ios.ts +++ b/platform/platform.ios.ts @@ -86,26 +86,38 @@ export class device implements definition.device { } } -var mainScreenInfo: definition.ScreenMetrics = null; +var mainScreen: MainScreen; // This is a "static" class and it is used like a name-space. // It is not meant to be initialized - thus it is not capitalized export class screen implements definition.screen { static get mainScreen(): definition.ScreenMetrics { - if (!mainScreenInfo) { - var mainScreen = UIScreen.mainScreen(); - if (mainScreen) { - var size = mainScreen.bounds.size; - var scale = mainScreen.scale; - mainScreenInfo = { - widthPixels: size.width * scale, - heightPixels: size.height * scale, - scale: scale, - widthDIPs: size.width, - heightDIPs: size.height - } - } + if (!mainScreen) { + mainScreen = new MainScreen(UIScreen.mainScreen()); } - return mainScreenInfo; + return mainScreen; } } + +class MainScreen implements definition.ScreenMetrics { + private _screen: UIScreen; + constructor(metrics: UIScreen) { + this._screen = metrics; + } + + get widthPixels(): number { + return this.widthDIPs * this.scale; + } + get heightPixels(): number { + return this.heightDIPs * this.scale; + } + get scale(): number { + return this._screen.scale; + } + get widthDIPs(): number { + return this._screen.bounds.size.width; + } + get heightDIPs(): number { + return this._screen.bounds.size.height; + } +} \ No newline at end of file diff --git a/ui/builder/builder.ts b/ui/builder/builder.ts index 060c4a325..343f143f4 100644 --- a/ui/builder/builder.ts +++ b/ui/builder/builder.ts @@ -193,11 +193,11 @@ function loadCustomComponent(componentPath: string, componentName?: string, attr fullComponentPathFilePathWithoutExt = fs.path.join(fs.knownFolders.currentApp().path, componentPath, componentName); } - var xmlFilePath = resolveFilePath(fullComponentPathFilePathWithoutExt, "xml"); + var xmlFilePath = fileResolverModule.resolveFileName(fullComponentPathFilePathWithoutExt, "xml"); if (xmlFilePath) { // Custom components with XML - var jsFilePath = resolveFilePath(fullComponentPathFilePathWithoutExt, "js"); + var jsFilePath = fileResolverModule.resolveFileName(fullComponentPathFilePathWithoutExt, "js"); var subExports; if (jsFilePath) { @@ -220,7 +220,7 @@ function loadCustomComponent(componentPath: string, componentName?: string, attr } // Add component CSS file if exists. - var cssFilePath = resolveFilePath(fullComponentPathFilePathWithoutExt, "css"); + var cssFilePath = fileResolverModule.resolveFileName(fullComponentPathFilePathWithoutExt, "css"); if (cssFilePath) { if (parentPage) { parentPage.addCssFile(cssFilePath); @@ -232,19 +232,6 @@ function loadCustomComponent(componentPath: string, componentName?: string, attr return result; } -var fileNameResolver: fileResolverModule.FileNameResolver; -function resolveFilePath(path, ext): string { - if (!fileNameResolver) { - fileNameResolver = new fileResolverModule.FileNameResolver({ - width: platform.screen.mainScreen.widthDIPs, - height: platform.screen.mainScreen.heightDIPs, - os: platform.device.os, - deviceType: platform.device.deviceType - }); - } - return fileNameResolver.resolveFileName(path, ext); -} - export function load(pathOrOptions: string | definition.LoadOptions, context?: any): view.View { var viewToReturn: view.View; var componentModule: componentBuilder.ComponentModule; diff --git a/ui/enums/enums.d.ts b/ui/enums/enums.d.ts index eed1541b0..00f5466ba 100644 --- a/ui/enums/enums.d.ts +++ b/ui/enums/enums.d.ts @@ -104,6 +104,24 @@ export var vertical: string; } + /** + * Orientation of a device. + */ + module DeviceOrientation { + /** + * Portrait orientation. + */ + export var portrait: string; + /** + * Landscape orientation. + */ + export var landscape: string; + /** + * Orientation cannot be determined. + */ + export var unknown: string; + } + /** * HorizontalAlignment indicates where an element should be displayed on the horizontal axis relative to the allocated layout slot of the parent element. */ diff --git a/ui/enums/enums.ts b/ui/enums/enums.ts index ebaa1a3d8..fc0ba9749 100644 --- a/ui/enums/enums.ts +++ b/ui/enums/enums.ts @@ -25,6 +25,12 @@ export module Orientation { export var vertical = "vertical"; } +export module DeviceOrientation { + export var portrait = "portrait"; + export var landscape = "landscape"; + export var unknown = "unknown"; +} + export module HorizontalAlignment { export var left = "left"; export var center = "center"; diff --git a/ui/frame/frame-common.ts b/ui/frame/frame-common.ts index ba3769f30..eef4bfff3 100644 --- a/ui/frame/frame-common.ts +++ b/ui/frame/frame-common.ts @@ -6,7 +6,6 @@ import trace = require("trace"); import builder = require("ui/builder"); import fs = require("file-system"); import utils = require("utils/utils"); -import platform = require("platform"); import fileResolverModule = require("file-system/file-name-resolver"); var frameStack: Array = []; @@ -47,7 +46,7 @@ export function resolvePageFromEntry(entry: definition.NavigationEntry): pages.P var moduleNamePath = fs.path.join(currentAppPath, entry.moduleName); var moduleExports; - var moduleExportsResolvedPath = resolveFilePath(moduleNamePath, "js"); + var moduleExportsResolvedPath = fileResolverModule.resolveFileName(moduleNamePath, "js"); if (moduleExportsResolvedPath) { trace.write("Loading JS file: " + moduleExportsResolvedPath, trace.categories.Navigation); @@ -72,25 +71,12 @@ export function resolvePageFromEntry(entry: definition.NavigationEntry): pages.P return page; } -var fileNameResolver: fileResolverModule.FileNameResolver; -function resolveFilePath(path, ext) : string { - if (!fileNameResolver) { - fileNameResolver = new fileResolverModule.FileNameResolver({ - width: platform.screen.mainScreen.widthDIPs, - height: platform.screen.mainScreen.heightDIPs, - os: platform.device.os, - deviceType: platform.device.deviceType - }); - } - return fileNameResolver.resolveFileName(path, ext); -} - function pageFromBuilder(moduleNamePath: string, moduleExports: any): pages.Page { var page: pages.Page; var element: view.View; // Possible XML file path. - var fileName = resolveFilePath(moduleNamePath, "xml"); + var fileName = fileResolverModule.resolveFileName(moduleNamePath, "xml"); if (fileName) { trace.write("Loading XML file: " + fileName, trace.categories.Navigation); @@ -100,7 +86,7 @@ function pageFromBuilder(moduleNamePath: string, moduleExports: any): pages.Page page = element; // Possible CSS file path. - var cssFileName = resolveFilePath(moduleNamePath, "css"); + var cssFileName = fileResolverModule.resolveFileName(moduleNamePath, "css"); if (cssFileName) { page.addCssFile(cssFileName); }