diff --git a/CrossPlatformModules.csproj b/CrossPlatformModules.csproj index 6df402a84..b853675d0 100644 --- a/CrossPlatformModules.csproj +++ b/CrossPlatformModules.csproj @@ -83,6 +83,10 @@ data-binding.xml + + + list-view.xml + @@ -135,6 +139,15 @@ + + Designer + + + Always + + + Designer + @@ -2077,6 +2090,9 @@ + + PreserveNewest + diff --git a/application/application.android.ts b/application/application.android.ts index 84d213f30..75222ff5c 100644 --- a/application/application.android.ts +++ b/application/application.android.ts @@ -191,41 +191,7 @@ export class AndroidApplication extends observable.Observable implements definit private _eventsToken: any; public getActivity(intent: android.content.Intent): Object { - if (intent && intent.getAction() === android.content.Intent.ACTION_MAIN) { - // application's main activity - if (typedExports.onLaunch) { - typedExports.onLaunch(intent); - } - - typedExports.notify({ eventName: typedExports.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(); - * rootFrame.navigate({ pageModuleName: "mainPage" }); - */ - } - - var topFrame = frame.topmost(); - if (!topFrame) { - // try to navigate to the mainEntry/Module (if specified) - var navParam = typedExports.mainEntry; - if (!navParam) { - navParam = typedExports.mainModule; - } - - if (navParam) { - topFrame = new frame.Frame(); - topFrame.navigate(navParam); - } else { - // TODO: Throw an exception? - throw new Error("A Frame must be used to navigate to a Page."); - } - } - - return topFrame.android.onActivityRequested(intent); + return frame.getActivity(); } public init(nativeApp: any) { @@ -320,7 +286,7 @@ function loadCss() { } var started = false; -export function start (entry?: frame.NavigationEntry) { +export function start(entry?: frame.NavigationEntry) { if (started) { throw new Error("Application is already started."); } @@ -340,6 +306,7 @@ export function start (entry?: frame.NavigationEntry) { onCreate: function () { androidApp.init(this); + setupOrientationListener(androidApp); } }); loadCss(); @@ -352,8 +319,9 @@ typedExports.android = androidApp; var currentOrientation: number; function setupOrientationListener(androidApp: AndroidApplication) { androidApp.registerBroadcastReceiver(android.content.Intent.ACTION_CONFIGURATION_CHANGED, onConfigurationChanged); - currentOrientation = androidApp.context.getResources().getConfiguration().orientation + currentOrientation = androidApp.context.getResources().getConfiguration().orientation; } + function onConfigurationChanged(context: android.content.Context, intent: android.content.Intent) { var orientation = context.getResources().getConfiguration().orientation; diff --git a/application/application.d.ts b/application/application.d.ts index 060a13c3f..17c28e783 100644 --- a/application/application.d.ts +++ b/application/application.d.ts @@ -5,7 +5,7 @@ declare module "application" { import cssSelector = require("ui/styling/css-selector"); import observable = require("data/observable"); import frame = require("ui/frame"); - + import {View} from "ui/core/view"; /** * An extended JavaScript Error which will have the nativeError property initialized in case the error is caused by executing platform-specific code. */ @@ -76,6 +76,17 @@ declare module "application" { object: any; } + /** + * Event data containing information for launch event. + */ + export interface LaunchEventData extends ApplicationEventData { + /** + * The root view for this Window on iOS or Activity for Android. + * If not set a new Frame will be created as a root view in order to maintain backwards compatibility. + */ + root?: View; + } + /** * Event data containing information for orientation changed event. */ @@ -130,7 +141,7 @@ declare module "application" { /** * The main entry point event. This method is expected to use the root frame to navigate to the main application page. */ - export function onLaunch(context: any): void; + export function onLaunch(context?: any): void; /** * A callback to be used when an uncaught error occurs while the application is running. @@ -174,7 +185,7 @@ declare module "application" { * @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); + export function off(eventNames: string, callback?: any, thisArg?: any); /** * Notifies all the registered listeners for the event provided in the data.eventName. @@ -191,7 +202,7 @@ declare module "application" { /** * This event is raised on application launchEvent. */ - export function on(event: "launch", callback: (args: ApplicationEventData) => void, thisArg?: any); + export function on(event: "launch", callback: (args: LaunchEventData) => void, thisArg?: any); /** * This event is raised when the Application is suspended. diff --git a/application/application.ios.ts b/application/application.ios.ts index ed0674491..47599e198 100644 --- a/application/application.ios.ts +++ b/application/application.ios.ts @@ -1,5 +1,5 @@ import common = require("./application-common"); -import frame = require("ui/frame"); +import {Frame, NavigationEntry, reloadPage} from "ui/frame"; import definition = require("application"); import * as uiUtils from "ui/utils"; import * as typesModule from "utils/types"; @@ -114,32 +114,51 @@ class IOSApplication implements definition.iOSApplication { typedExports.onLaunch(undefined); } - typedExports.notify({ + let args: definition.LaunchEventData = { eventName: typedExports.launchEvent, object: this, ios: notification.userInfo && notification.userInfo.objectForKey("UIApplicationLaunchOptionsLocalNotificationKey") || null - }); + }; - var topFrame = frame.topmost(); - if (!topFrame) { + typedExports.notify(args); + + let rootView = args.root; + let frame: Frame; + let navParam: Object; + if (!rootView) { // try to navigate to the mainEntry/Module (if specified) - var navParam = typedExports.mainEntry; + navParam = typedExports.mainEntry; if (!navParam) { navParam = typedExports.mainModule; } if (navParam) { - topFrame = new frame.Frame(); - topFrame.navigate(navParam); + frame = new Frame(); + frame.navigate(navParam); } else { // TODO: Throw an exception? throw new Error("A Frame must be used to navigate to a Page."); } + + rootView = frame; } + + this._window.content = rootView; - this._window.content = topFrame; - - this.rootController = this._window.rootViewController = topFrame.ios.controller; + if (rootView instanceof Frame) { + this.rootController = this._window.rootViewController = rootView.ios.controller; + } + else if (rootView.ios instanceof UIViewController) { + this.rootController = this._window.rootViewController = rootView.ios; + } + else if (rootView.ios instanceof UIView) { + let newController = new UIViewController(); + newController.view.addSubview(rootView.ios); + this.rootController = newController; + } + else { + throw new Error("Root should be either UIViewController or UIView"); + } this._window.makeKeyAndVisible(); } @@ -229,7 +248,7 @@ function loadCss() { } var started: boolean = false; -typedExports.start = function (entry?: frame.NavigationEntry) { +typedExports.start = function (entry?: NavigationEntry) { if (!started) { if (entry) { exports.mainEntry = entry; @@ -256,5 +275,5 @@ global.__onLiveSync = function () { loadCss(); // Reload current page. - frame.reloadPage(); + reloadPage(); } \ No newline at end of file diff --git a/apps/custom-root-view/app.ts b/apps/custom-root-view/app.ts new file mode 100644 index 000000000..4c9e6cd1f --- /dev/null +++ b/apps/custom-root-view/app.ts @@ -0,0 +1,14 @@ +import app = require("application"); +import {Frame} from "ui/frame"; +import {TabView} from "ui/tab-view"; +import * as builder from "ui/builder"; + +app.on("launch", function (args) { + var tabView = builder.load((__dirname + "/main-page.xml")); + args.root = tabView; + var frame = tabView.items[0].view; + var basePath = "list-view"; + frame.navigate(basePath); +}); + +app.start(); \ No newline at end of file diff --git a/apps/custom-root-view/list-view.xml b/apps/custom-root-view/list-view.xml new file mode 100644 index 000000000..75c7a9c66 --- /dev/null +++ b/apps/custom-root-view/list-view.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/custom-root-view/main-page.css b/apps/custom-root-view/main-page.css new file mode 100644 index 000000000..115f58fbc --- /dev/null +++ b/apps/custom-root-view/main-page.css @@ -0,0 +1,29 @@ +.title { + font-size: 20; + margin: 3; +} + +.author { + font-size: 14; + horizontal-align: left; + vertical-align: bottom; + margin: 3; +} + +.comments { + color: #10C2B0; + font-size: 14; + vertical-align: bottom; + margin: 3; +} + +.thumbnail { + width: 72; + height: 72; + margin: 3; + vertical-align: top; +} + +TabView { + background-color: white; +} \ No newline at end of file diff --git a/apps/custom-root-view/main-page.xml b/apps/custom-root-view/main-page.xml new file mode 100644 index 000000000..7984dc0d6 --- /dev/null +++ b/apps/custom-root-view/main-page.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/custom-root-view/package.json b/apps/custom-root-view/package.json new file mode 100644 index 000000000..5daa2ee99 --- /dev/null +++ b/apps/custom-root-view/package.json @@ -0,0 +1,4 @@ +{ + "name": "custom-root-viewcustom-root-view", + "main": "app.js" +} diff --git a/tsconfig.json b/tsconfig.json index c59a5946c..482b8cbb0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -52,6 +52,8 @@ "apps/animations/opacity.ts", "apps/connectivity-demo/app.ts", "apps/connectivity-demo/main-page.ts", + "apps/custom-root-view/app.ts", + "apps/custom-root-view/list-view.ts", "apps/cuteness.io/app.ts", "apps/cuteness.io/details-page.ts", "apps/cuteness.io/main-page.ts", diff --git a/ui/core/view.android.ts b/ui/core/view.android.ts index c0f98b18d..fe4024d90 100644 --- a/ui/core/view.android.ts +++ b/ui/core/view.android.ts @@ -196,7 +196,7 @@ export class View extends viewCommon.View { } if (this._context) { - this._onDetached(); + this._onDetached(true); } this._context = context; diff --git a/ui/frame/frame.android.ts b/ui/frame/frame.android.ts index 1161f3e4e..2ed763d60 100644 --- a/ui/frame/frame.android.ts +++ b/ui/frame/frame.android.ts @@ -1,11 +1,12 @@ -import frameCommon = require("./frame-common"); -import definition = require("ui/frame"); +import definition = require("ui/frame"); +import frameCommon = require("./frame-common"); import pages = require("ui/page"); +import {View} from "ui/core/view"; +import {Observable} from "data/observable"; import trace = require("trace"); -import observable = require("data/observable"); import application = require("application"); import * as types from "utils/types"; -import * as utilsModule from "utils/utils"; +import * as utils from "utils/utils"; import transitionModule = require("ui/transition"); global.moduleMerge(frameCommon, exports); @@ -14,15 +15,25 @@ var TAG = "_fragmentTag"; var OWNER = "_owner"; var HIDDEN = "_hidden"; var INTENT_EXTRA = "com.tns.activity"; -var ANDROID_FRAME = "android_frame"; +var ROOT_VIEW = "_rootView"; var BACKSTACK_TAG = "_backstackTag"; var IS_BACK = "_isBack"; var NAV_DEPTH = "_navDepth"; var CLEARING_HISTORY = "_clearingHistory"; -var activityInitialized = false; +var FRAMEID = "_frameId"; var navDepth = -1; +var activityInitialized = false; + +var animationFixed; +function ensureAnimationFixed() { + if (!animationFixed) { + animationFixed = android.os.Build.VERSION.SDK_INT >= 19 // android.os.Build.VERSION.KITKAT but we don't have definition for it + ? 1 : -1; + } +} + var FragmentClass; function ensureFragmentClass() { if (FragmentClass) { @@ -30,72 +41,92 @@ function ensureFragmentClass() { } FragmentClass = (android.app.Fragment).extend({ - - onCreate: function (savedInstanceState: android.os.Bundle) { - trace.write(`${this.getTag()}.onCreate(${savedInstanceState})`, trace.categories.NativeLifecycle); - this.super.onCreate(savedInstanceState); - this.super.setHasOptionsMenu(true); - }, - onCreateView: function (inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle): android.view.View { - trace.write(`${this.getTag()}.onCreateView(inflater, container, ${savedInstanceState})`, trace.categories.NativeLifecycle); - var entry = this.entry; - var page = entry.resolvedPage; - if (savedInstanceState && savedInstanceState.getBoolean(HIDDEN, false)) { - this.super.getFragmentManager().beginTransaction().hide(this).commit(); - page._onAttached(this.getActivity()); - } - else { - onFragmentShown(this); - } - return page._nativeView; - }, + onCreate: function (savedInstanceState: android.os.Bundle) { + trace.write(`${this.getTag()}.onCreate(${savedInstanceState})`, trace.categories.NativeLifecycle); + this.super.onCreate(savedInstanceState); + this.super.setHasOptionsMenu(true); - onHiddenChanged: function (hidden: boolean) { - trace.write(`${this.getTag()}.onHiddenChanged(${hidden})`, trace.categories.NativeLifecycle); - this.super.onHiddenChanged(hidden); - if (hidden) { + // There is no entry set to the fragment, so this must be destroyed fragment that was recreated by Android. + // We should find its corresponding page in our backstack and set it manually. + if (!(this).entry) { + let frameId = (this).getArguments().getInt(FRAMEID); + let frame = getFrameById(frameId); + if (frame) { + this.frame = frame; + } + else { + throw new Error(`Cannot find Frame for ${this}`); + } + + findPageForFragment(this, this.frame); + } + }, + + onCreateView: function (inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle): android.view.View { + trace.write(`${this.getTag()}.onCreateView(inflater, container, ${savedInstanceState})`, trace.categories.NativeLifecycle); + var entry = this.entry; + var page = entry.resolvedPage; + if (savedInstanceState && savedInstanceState.getBoolean(HIDDEN, false)) { + this.super.getFragmentManager().beginTransaction().hide(this).commit(); + page._onAttached(this.getActivity()); + } + else { + onFragmentShown(this); + } + + return page._nativeView; + }, + + onHiddenChanged: function (hidden: boolean) { + trace.write(`${this.getTag()}.onHiddenChanged(${hidden})`, trace.categories.NativeLifecycle); + this.super.onHiddenChanged(hidden); + if (hidden) { + onFragmentHidden(this); + } + else { + onFragmentShown(this); + } + }, + + onSaveInstanceState: function (outState: android.os.Bundle) { + trace.write(`${this.getTag()}.onSaveInstanceState(${outState})`, trace.categories.NativeLifecycle); + this.super.onSaveInstanceState(outState); + if (this.isHidden()) { + outState.putBoolean(HIDDEN, true); + } + }, + + onDestroyView: function () { + trace.write(`${this.getTag()}.onDestroyView()`, trace.categories.NativeLifecycle); + this.super.onDestroyView(); onFragmentHidden(this); + + // When Fragment is destroyed we detach page even if cachePagesOnNavigate is true. + let entry: definition.BackstackEntry = this.entry; + let page = entry.resolvedPage; + if (page._context) { + page._onDetached(true); + } + }, + + onDestroy: function () { + trace.write(`${this.getTag()}.onDestroy()`, trace.categories.NativeLifecycle); + this.super.onDestroy(); + utils.GC(); + }, + + onCreateAnimator: function (transit: number, enter: boolean, nextAnim: number): android.animation.Animator { + var animator = transitionModule._onFragmentCreateAnimator(this, nextAnim); + + if (!animator) { + animator = this.super.onCreateAnimator(transit, enter, nextAnim); + } + + trace.write(`${this.getTag()}.onCreateAnimator(${transit}, ${enter}, ${nextAnim}): ${animator}`, trace.categories.NativeLifecycle); + return animator; } - else { - onFragmentShown(this); - } - }, - - onSaveInstanceState: function (outState: android.os.Bundle) { - trace.write(`${this.getTag()}.onSaveInstanceState(${outState})`, trace.categories.NativeLifecycle); - this.super.onSaveInstanceState(outState); - if (this.isHidden()) { - outState.putBoolean(HIDDEN, true); - } - }, - - onDestroyView: function () { - trace.write(`${this.getTag()}.onDestroyView()`, trace.categories.NativeLifecycle); - this.super.onDestroyView(); - onFragmentHidden(this); - }, - - onDestroy: function () { - trace.write(`${this.getTag()}.onDestroy()`, trace.categories.NativeLifecycle); - this.super.onDestroy(); - - var utils: typeof utilsModule = require("utils/utils"); - - utils.GC(); - }, - - onCreateAnimator: function (transit: number, enter: boolean, nextAnim: number): android.animation.Animator { - var animator = transitionModule._onFragmentCreateAnimator(this, nextAnim); - - if (!animator) { - animator = this.super.onCreateAnimator(transit, enter, nextAnim); - } - - trace.write(`${this.getTag() }.onCreateAnimator(${transit}, ${enter}, ${nextAnim}): ${animator}`, trace.categories.NativeLifecycle); - return animator; - } -}); + }); } function onFragmentShown(fragment) { @@ -153,13 +184,15 @@ function onFragmentHidden(fragment) { export class Frame extends frameCommon.Frame { private _android: AndroidFrame; private _delayedNavigationEntry: definition.BackstackEntry; - private _isFirstNavigation = false; private _containerViewId: number; - + private _listener: android.view.View.OnAttachStateChangeListener; constructor() { super(); - this._containerViewId = android.view.View.generateViewId(); this._android = new AndroidFrame(this); + this._listener = new android.view.View.OnAttachStateChangeListener({ + onViewAttachedToWindow: this.onNativeViewAttachedToWindow.bind(this), + onViewDetachedFromWindow: this.onNativeViewDetachedToWindow.bind(this) + }); } public static get defaultAnimatedNavigation(): boolean { @@ -180,7 +213,7 @@ export class Frame extends frameCommon.Frame { return this._containerViewId; } - get android(): definition.AndroidFrame { + get android(): AndroidFrame { return this._android; } @@ -191,27 +224,27 @@ export class Frame extends frameCommon.Frame { public _navigateCore(backstackEntry: definition.BackstackEntry) { trace.write(`${this}._navigateCore(page: ${backstackEntry.resolvedPage}, backstackVisible: ${this._isEntryBackstackVisible(backstackEntry)}, clearHistory: ${backstackEntry.entry.clearHistory}), navDepth: ${navDepth}`, trace.categories.Navigation); - var activity = this._android.activity; + let activity = this._android.activity; if (!activity) { // We do not have an Activity yet associated. In this case we have two execution paths: // 1. This is the main frame for the application // 2. This is an inner frame which requires a new Activity - var currentActivity = this._android.currentActivity; + let currentActivity = this._android.currentActivity; if (currentActivity) { - startActivity(currentActivity, backstackEntry.entry); + startActivity(currentActivity, this._android.frameId); } this._delayedNavigationEntry = backstackEntry; return; } - var manager = activity.getFragmentManager(); + let manager = activity.getFragmentManager(); // Clear history - if (backstackEntry.entry.clearHistory && !this._isFirstNavigation) { - var backStackEntryCount = manager.getBackStackEntryCount(); - var i = backStackEntryCount - 1; - var fragment: android.app.Fragment; + if (backstackEntry.entry.clearHistory) { + let backStackEntryCount = manager.getBackStackEntryCount(); + let i = backStackEntryCount - 1; + let fragment: android.app.Fragment; while (i >= 0) { fragment = manager.findFragmentByTag(manager.getBackStackEntryAt(i--).getName()); trace.write(`${fragment.getTag()}[CLEARING_HISTORY] = true;`, trace.categories.NativeLifecycle); @@ -228,7 +261,7 @@ export class Frame extends frameCommon.Frame { } if (backStackEntryCount) { - var firstEntryName = manager.getBackStackEntryAt(0).getName(); + let firstEntryName = manager.getBackStackEntryAt(0).getName(); trace.write(`manager.popBackStack(${firstEntryName}, android.app.FragmentManager.POP_BACK_STACK_INCLUSIVE);`, trace.categories.NativeLifecycle); manager.popBackStack(firstEntryName, android.app.FragmentManager.POP_BACK_STACK_INCLUSIVE); } @@ -238,7 +271,7 @@ export class Frame extends frameCommon.Frame { navDepth++; - var fragmentTransaction = manager.beginTransaction(); + let fragmentTransaction = manager.beginTransaction(); var currentFragmentTag: string; var currentFragment: android.app.Fragment; @@ -249,7 +282,11 @@ export class Frame extends frameCommon.Frame { var newFragmentTag = "fragment" + navDepth; ensureFragmentClass(); - var newFragment = new FragmentClass(); + let newFragment = new FragmentClass(); + + let args = new android.os.Bundle(); + args.putInt(FRAMEID, this._android.frameId); + newFragment.setArguments(args); var animated = this._getIsAnimatedNavigation(backstackEntry.entry); var navigationTransition = this._getNavigationTransition(backstackEntry.entry); @@ -257,6 +294,7 @@ export class Frame extends frameCommon.Frame { // There might be transitions left over from previous forward navigations from the current page. transitionModule._clearForwardTransitions(currentFragment); } + if (animated && navigationTransition) { transitionModule._setAndroidFragmentTransitions(navigationTransition, currentFragment, newFragment, fragmentTransaction); } @@ -270,7 +308,8 @@ export class Frame extends frameCommon.Frame { // remember the fragment tag at page level so that we can retrieve the fragment associated with a Page instance backstackEntry.resolvedPage[TAG] = newFragmentTag; - if (this._isFirstNavigation) { + let isFirstNavigation = types.isNullOrUndefined(this._currentEntry); + if (isFirstNavigation) { fragmentTransaction.add(this.containerViewId, newFragment, newFragmentTag); trace.write(`fragmentTransaction.add(${newFragmentTag});`, trace.categories.NativeLifecycle); } @@ -295,14 +334,16 @@ export class Frame extends frameCommon.Frame { // Add to backStack if needed. if (this.backStack.length > 0 && this._currentEntry) { // We add each entry in the backstack to avoid the "Stack corrupted" mismatch - var backstackTag = this._currentEntry[BACKSTACK_TAG]; + let backstackTag = this._currentEntry[BACKSTACK_TAG]; fragmentTransaction.addToBackStack(backstackTag); trace.write(`fragmentTransaction.addToBackStack(${backstackTag});`, trace.categories.NativeLifecycle); } } - if (!this._isFirstNavigation) { - if (this.android.cachePagesOnNavigate) { + if (!isFirstNavigation) { + // This bug is fixed on API19+ + ensureAnimationFixed(); + if (this.android.cachePagesOnNavigate && animationFixed < 0) { // Apparently, there is an Android bug with when hiding fragments with animation. // https://code.google.com/p/android/issues/detail?id=32405 // When bug is fixed use animated variable. @@ -326,7 +367,7 @@ export class Frame extends frameCommon.Frame { this._currentEntry[IS_BACK] = true; } - trace.write(`${this}._goBackCore(pageId: ${backstackEntry.resolvedPage.id}, backstackVisible: ${this._isEntryBackstackVisible(backstackEntry) }, clearHistory: ${backstackEntry.entry.clearHistory}), navDepth: ${navDepth}`, trace.categories.Navigation); + trace.write(`${this}._goBackCore(pageId: ${backstackEntry.resolvedPage.id}, backstackVisible: ${this._isEntryBackstackVisible(backstackEntry)}, clearHistory: ${backstackEntry.entry.clearHistory}), navDepth: ${navDepth}`, trace.categories.Navigation); var manager = this._android.activity.getFragmentManager(); if (manager.getBackStackEntryCount() > 0) { @@ -337,27 +378,22 @@ export class Frame extends frameCommon.Frame { } public _createUI() { - // TODO: Implement for nested frames - // this._android.layout = new android.widget.FrameLayout(this._context); - // this._android.layout.setId(android.view.View.generateViewId()); + let root = new org.nativescript.widgets.ContentLayout(this._context); + this._containerViewId = android.view.View.generateViewId(); + this._android.rootViewGroup = root; + this._android.rootViewGroup.setId(this._containerViewId); + this._android.rootViewGroup.addOnAttachStateChangeListener(this._listener); } - public _onActivityCreated(isRestart: boolean) { - this._onAttached(this._android.activity); - - var backstackEntry = this._currentEntry || this._delayedNavigationEntry; - - if (isRestart) { - this._onNavigatingTo(backstackEntry, false); - this._onNavigatedTo(backstackEntry, false); - } - else { - this._isFirstNavigation = true; - this._navigateCore(backstackEntry); - this._isFirstNavigation = false; + private onNativeViewAttachedToWindow(view: android.view.View): void { + if (this._delayedNavigationEntry) { + this._navigateCore(this._delayedNavigationEntry); + this._delayedNavigationEntry = undefined; } + } - this._delayedNavigationEntry = undefined; + private onNativeViewDetachedToWindow(view: android.view.View): void { + // unused for the moment. } public _popFromFrameStack() { @@ -372,7 +408,9 @@ export class Frame extends frameCommon.Frame { } public _clearAndroidReference() { + this._android.rootViewGroup.removeOnAttachStateChangeListener(this._listener); // we should keep the reference to underlying native object, since frame can contain many pages. + this._android.rootViewGroup = null; } public _printNativeBackStack() { @@ -416,32 +454,58 @@ export class Frame extends frameCommon.Frame { var NativeActivity = { - get frame(): Frame { - if (this.androidFrame) { - return this.androidFrame.owner; - } - return null; - }, - - get androidFrame(): AndroidFrame { - return this[ANDROID_FRAME]; + get rootView(): View { + return this[ROOT_VIEW]; }, onCreate: function (savedInstanceState: android.os.Bundle) { - trace.write(`NativeActivity.onCreate(${savedInstanceState})`, trace.categories.NativeLifecycle); + trace.write(`NativeScriptActivity.onCreate(${savedInstanceState})`, trace.categories.NativeLifecycle); - // Find the frame for this activity. - var frameId = this.getIntent().getExtras().getInt(INTENT_EXTRA); - for (var i = 0; i < framesCache.length; i++) { - var aliveFrame = framesCache[i].get(); - if (aliveFrame && aliveFrame.frameId === frameId) { - this[ANDROID_FRAME] = aliveFrame; - break; - } + let app = application.android; + let activity: android.app.Activity = this; + let intent = activity.getIntent(); + if (application.onLaunch) { + application.onLaunch(intent); } - if (!this.androidFrame) { - throw new Error("Could not find AndroidFrame for Activity"); + let args: application.LaunchEventData = { eventName: application.launchEvent, object: app, android: intent }; + application.notify(args); + + let frameId = -1; + let rootView = args.root; + let extras = intent.getExtras(); + + // We have extras when we call - new Frame().navigate(); + // savedInstanceState is used when activity is recreated. + if (extras) { + frameId = extras.getInt(INTENT_EXTRA, -1); + } + else if (savedInstanceState) { + frameId = savedInstanceState.getInt(INTENT_EXTRA, -1) + } + + // If we have frameId from extras - we are starting a new activity from navigation (e.g. new Frame().navigate())) + // Then we check if we have frameId from savedInstanceState - this happens when Activity is destroyed but app was not (e.g. suspend) + // Only then we fallback to the view returned from the event. This is done in order to have backwards compatibility (remove it for 2.0.0). + let frame: Frame; + let navParam; + if (frameId >= 0) { + rootView = getFrameById(frameId); + } + else if (!rootView) { + navParam = application.mainEntry; + if (!navParam) { + navParam = application.mainModule; + } + + if (navParam) { + frame = new Frame(); + } else { + // TODO: Throw an exception? + throw new Error("A Frame must be used to navigate to a Page."); + } + + rootView = frame; } // If there is savedInstanceState this call will recreate all fragments that were previously in the navigation. @@ -452,24 +516,33 @@ var NativeActivity = { var isRestart = !!savedInstanceState && activityInitialized; this.super.onCreate(isRestart ? savedInstanceState : null); - this.androidFrame.setActivity(this); + this[ROOT_VIEW] = rootView; - // Create and set content container. - var root = new org.nativescript.widgets.ContentLayout(this); + // Initialize native visual tree; + rootView._onAttached(this); + this.setContentView(rootView._nativeView, new org.nativescript.widgets.CommonLayoutParams()); + // frameId is negative w + if (frame) { + frame.navigate(navParam); + } - this.androidFrame.rootViewGroup = root; - this.androidFrame.rootViewGroup.setId(this.frame.containerViewId); - this.setContentView(this.androidFrame.rootViewGroup, new org.nativescript.widgets.CommonLayoutParams()); - - // If there is no instance state - we call navigateCore from here since Activity is created AFTER the navigate call and navigateCore will fail. - activityInitialized = true; - this.frame._onActivityCreated(isRestart); + // TODO: If the above fails because we call fragmentManager.beginTransition().commit() before + // we are added as content to activity - add if (rootview instanceof Frame) -> call navigate + //this.frame._onActivityCreated(isRestart); + }, + + onSaveInstanceState(outState: android.os.Bundle): void { + this.super.onSaveInstanceState(outState); + let view = this.rootView; + if (view instanceof Frame) { + outState.putInt(INTENT_EXTRA, (view).android.frameId); + } }, onActivityResult: function (requestCode: number, resultCode: number, data: android.content.Intent) { this.super.onActivityResult(requestCode, resultCode, data); - trace.write(`NativeActivity.onActivityResult(${requestCode}, ${resultCode}, ${data})`, trace.categories.NativeLifecycle); + trace.write(`NativeScriptActivity.onActivityResult(${requestCode}, ${resultCode}, ${data})`, trace.categories.NativeLifecycle); var result = application.android.onActivityResult; if (result) { @@ -486,66 +559,36 @@ var NativeActivity = { }); }, - onAttachFragment: function (fragment: android.app.Fragment) { - trace.write(`NativeActivity.onAttachFragment(${fragment.getTag()})`, trace.categories.NativeLifecycle); - this.super.onAttachFragment(fragment); - - if (!(fragment).entry) { - // There is no entry set to the fragment, so this must be destroyed fragment that was recreated by Android. - // We should find its corresponding page in our backstack and set it manually. - findPageForFragment(fragment, this.frame); - } - }, - onStart: function () { this.super.onStart(); - trace.write("NativeActivity.onStart()", trace.categories.NativeLifecycle); - if (!this.frame.isLoaded) { - this.frame.onLoaded(); + trace.write("NativeScriptActivity.onStart();", trace.categories.NativeLifecycle); + let rootView: View = this.rootView + if (rootView && !rootView.isLoaded) { + rootView.onLoaded(); } }, onStop: function () { this.super.onStop(); - trace.write("NativeActivity.onStop()", trace.categories.NativeLifecycle); - this.frame.onUnloaded(); + trace.write("NativeScriptActivity.onStop();", trace.categories.NativeLifecycle); + let rootView: View = this.rootView + if (rootView && rootView.isLoaded) { + rootView.onUnloaded(); + } }, onDestroy: function () { - trace.write("NativeActivity.onDestroy()", trace.categories.NativeLifecycle); - // TODO: Implement uninitialized(detached) routine - var frame = this.frame; - frame._onDetached(true); - - // There might be cached pages in the backstack - force detach them too. - for (var i = 0; i < frame.backStack.length; i++) { - frame.backStack[i].resolvedPage._onDetached(true); + let rootView: View = this.rootView + if (rootView) { + rootView._onDetached(true); } - this.androidFrame.reset(); - this.super.onDestroy(); - }, - - onOptionsItemSelected: function (menuItem: android.view.IMenuItem) { - trace.write(`NativeActivity.onOptionsItemSelected(${menuItem})`, trace.categories.NativeLifecycle); - if (!this.androidFrame.hasListeners(frameCommon.Frame.androidOptionSelectedEvent)) { - return false; - } - - var data: definition.AndroidOptionEventData = { - handled: false, - eventName: frameCommon.Frame.androidOptionSelectedEvent, - item: menuItem, - object: this.androidFrame - } - - this.androidFrame.notify(data); - return data.handled; + trace.write("NativeScriptActivity.onDestroy();", trace.categories.NativeLifecycle); }, onBackPressed: function () { - trace.write("NativeActivity.onBackPressed()", trace.categories.NativeLifecycle); + trace.write("NativeScriptActivity.onBackPressed;", trace.categories.NativeLifecycle); var args = { eventName: "activityBackPressed", @@ -565,7 +608,7 @@ var NativeActivity = { }, onLowMemory: function () { - trace.write("NativeActivity.onLowMemory()", trace.categories.NativeLifecycle); + trace.write("NativeScriptActivity.onLowMemory()", trace.categories.NativeLifecycle); gc(); java.lang.System.gc(); this.super.onLowMemory(); @@ -574,7 +617,7 @@ var NativeActivity = { }, onTrimMemory: function (level: number) { - trace.write(`NativeActivity.onTrimMemory(${level})`, trace.categories.NativeLifecycle); + trace.write(`NativeScriptActivity.onTrimMemory(${level})`, trace.categories.NativeLifecycle); gc(); java.lang.System.gc(); this.super.onTrimMemory(level); @@ -584,13 +627,12 @@ var NativeActivity = { var framesCounter = 0; var framesCache: Array> = new Array>(); -class AndroidFrame extends observable.Observable implements definition.AndroidFrame { +class AndroidFrame extends Observable implements definition.AndroidFrame { public rootViewGroup: android.view.ViewGroup; public hasOwnActivity = false; public frameId; private _showActionBar = true; - private _activity: android.app.Activity; private _owner: Frame; private _cachePagesOnNavigate: boolean; @@ -615,12 +657,13 @@ class AndroidFrame extends observable.Observable implements definition.AndroidFr } public get activity(): android.app.Activity { - if (this._activity) { - return this._activity; + let activity: android.app.Activity = this.owner._context; + if (activity) { + return activity; } // traverse the parent chain for an ancestor Frame - var currView = this._owner.parent; + let currView = this._owner.parent; while (currView) { if (currView instanceof Frame) { return (currView).android.activity; @@ -633,12 +676,12 @@ class AndroidFrame extends observable.Observable implements definition.AndroidFr } public get actionBar(): android.app.ActionBar { - var activity = this.currentActivity; + let activity = this.currentActivity; if (!activity) { return undefined; } - var bar = activity.getActionBar(); + let bar = activity.getActionBar(); if (!bar) { return undefined; } @@ -647,12 +690,12 @@ class AndroidFrame extends observable.Observable implements definition.AndroidFr } public get currentActivity(): android.app.Activity { - var activity = this.activity; + let activity = this.activity; if (activity) { return activity; } - var stack = frameCommon.stack(), + let stack = frameCommon.stack(), length = stack.length, i = length - 1, frame: definition.Frame; @@ -686,39 +729,13 @@ class AndroidFrame extends observable.Observable implements definition.AndroidFr } } - public onActivityRequested(intent: android.content.Intent): Object { - if (this.activity) { - throw new Error("Frame already attached to an Activity"); - } - - intent.putExtra(INTENT_EXTRA, this.frameId); - this.hasOwnActivity = true; - return this.createActivity(intent); - } - public canGoBack() { - if (!this._activity) { + if (!this.activity) { return false; } // can go back only if it is not the main one. - return this._activity.getIntent().getAction() !== android.content.Intent.ACTION_MAIN; - } - - public reset() { - // TODO: Cleanup, do we need more? - delete this.rootViewGroup[OWNER]; - this._activity = undefined; - this.rootViewGroup = undefined; - } - - public setActivity(value: android.app.Activity) { - this._activity = value; - } - - private createActivity(intent: android.content.Intent) { - // TODO: check intent - return NativeActivity; + return this.activity.getIntent().getAction() !== android.content.Intent.ACTION_MAIN; } } @@ -763,9 +780,27 @@ function findPageForFragment(fragment: android.app.Fragment, frame: Frame) { } } -function startActivity(activity: android.app.Activity, entry: definition.NavigationEntry) { +function startActivity(activity: android.app.Activity, frameId: number) { var intent = new android.content.Intent(activity, (com).tns.NativeScriptActivity.class); intent.setAction(android.content.Intent.ACTION_DEFAULT); + intent.putExtra(INTENT_EXTRA, frameId); + // TODO: Put the navigation context (if any) in the intent activity.startActivity(intent); +} + +function getFrameById(frameId: number): Frame { + // Find the frame for this activity. + for (let i = 0; i < framesCache.length; i++) { + let aliveFrame = framesCache[i].get(); + if (aliveFrame && aliveFrame.frameId === frameId) { + return aliveFrame.owner; + } + } + + return null; +} + +export function getActivity(): Object { + return NativeActivity; } \ No newline at end of file diff --git a/ui/frame/frame.d.ts b/ui/frame/frame.d.ts index 2b4a28490..177243dbc 100644 --- a/ui/frame/frame.d.ts +++ b/ui/frame/frame.d.ts @@ -13,6 +13,7 @@ declare module "ui/frame" { */ export class Frame extends view.View { /** + * Deprecated. * String value used when hooking to androidOptionSelected event (prefix `android` states that this event is available only in Android). */ public static androidOptionSelectedEvent: string; @@ -247,12 +248,6 @@ declare module "ui/frame" { */ actionBar: any /* android.app.ActionBar */; - /** - * A function called by the Runtime whenever a new Activity is about to be opened. - * @param intent The native [android Intent](http://developer.android.com/reference/android/content/Intent.html) object passed to the Activity's onCreate method. - */ - onActivityRequested(intent: any /* android.content.Intent */): Object; - /** * Determines whether the Activity associated with this Frame will display an action bar or not. */ @@ -286,5 +281,6 @@ declare module "ui/frame" { //@private function reloadPage(): void; function resolvePageFromEntry(entry: NavigationEntry): pages.Page; + function getActivity(): Object; //@endprivate } \ No newline at end of file diff --git a/ui/tab-view/tab-view.android.ts b/ui/tab-view/tab-view.android.ts index aa495e120..5eec24a2c 100644 --- a/ui/tab-view/tab-view.android.ts +++ b/ui/tab-view/tab-view.android.ts @@ -77,17 +77,17 @@ function ensurePagerAdapterClass() { if (this[VIEWS_STATES]) { trace.write("TabView.PagerAdapter.instantiateItem; restoreHierarchyState: " + item.view, common.traceCategory); - item.view.android.restoreHierarchyState(this[VIEWS_STATES]); + item.view._nativeView.restoreHierarchyState(this[VIEWS_STATES]); } - container.addView(item.view.android); - return item.view.android; + container.addView(item.view._nativeView); + return item.view._nativeView; } destroyItem(container: android.view.ViewGroup, index: number, _object: any) { trace.write("TabView.PagerAdapter.destroyItem; container: " + container + "; index: " + index + "; _object: " + _object, common.traceCategory); var item = this.items[index]; - var nativeView = item.view.android; + var nativeView = item.view._nativeView; if (nativeView.toString() !== _object.toString()) { throw new Error("Expected " + nativeView.toString() + " to equal " + _object.toString()); @@ -100,7 +100,7 @@ function ensurePagerAdapterClass() { container.removeView(nativeView); - // Note: this.owner._removeView will clear item.view.android. + // Note: this.owner._removeView will clear item.view._nativeView. // So call this after the native instance is removed form the container. if (item.view.parent === this.owner) { this.owner._removeView(item.view); @@ -124,7 +124,7 @@ function ensurePagerAdapterClass() { } var viewStates = this[VIEWS_STATES]; var childCallback = function (view: view.View): boolean { - var nativeView: android.view.View = view.android; + var nativeView: android.view.View = view._nativeView; if (nativeView && nativeView.isSaveFromParentEnabled && nativeView.isSaveFromParentEnabled()) { nativeView.saveHierarchyState(viewStates); } @@ -173,7 +173,7 @@ function ensurePageChangedListenerClass() { function selectedColorPropertyChanged(data: dependencyObservable.PropertyChangeData) { var tabLayout = (data.object)._getAndroidTabView(); if (tabLayout && data.newValue instanceof color.Color) { - tabLayout.setSelectedIndicatorColors([data.newValue.android]); + tabLayout.setSelectedIndicatorColors([data.newValue._nativeView]); } } (common.TabView.selectedColorProperty.metadata).onSetNativeValue = selectedColorPropertyChanged; @@ -181,7 +181,7 @@ function selectedColorPropertyChanged(data: dependencyObservable.PropertyChangeD function tabsBackgroundColorPropertyChanged(data: dependencyObservable.PropertyChangeData) { var tabLayout = (data.object)._getAndroidTabView(); if (tabLayout && data.newValue instanceof color.Color) { - tabLayout.setBackgroundColor(data.newValue.android); + tabLayout.setBackgroundColor(data.newValue._nativeView); } } (common.TabView.tabsBackgroundColorProperty.metadata).onSetNativeValue = tabsBackgroundColorPropertyChanged;