UNPKG

27.6 kBJavaScriptView Raw
1// Types.
2import { Application } from '../../application';
3import { Observable } from '../../data/observable';
4import { Trace } from '../../trace';
5import { _stack, FrameBase, NavigationType } from './frame-common';
6import { _clearEntry, _clearFragment, _getAnimatedEntries, _reverseTransitions, _setAndroidFragmentTransitions, _updateTransitions, addNativeTransitionListener } from './fragment.transitions';
7import { profile } from '../../profiling';
8import { android as androidUtils } from '../../utils/native-helper';
9import { ensureFragmentClass, fragmentClass } from './fragment';
10import { FragmentCallbacksImplementation } from './callbacks/fragment-callbacks';
11import { ActivityCallbacksImplementation } from './callbacks/activity-callbacks';
12export * from './frame-common';
13export { setFragmentClass } from './fragment';
14const INTENT_EXTRA = 'com.tns.activity';
15const FRAMEID = '_frameId';
16const CALLBACKS = '_callbacks';
17const ownerSymbol = Symbol('_owner');
18let navDepth = -1;
19let fragmentId = -1;
20export { moduleLoaded } from './callbacks/activity-callbacks';
21export let attachStateChangeListener;
22function getAttachListener() {
23 if (!attachStateChangeListener) {
24 /**
25 * NOTE: We cannot use NativeClass here because this is used in appComponents in webpack.config
26 * Whereby it bypasses the decorator transformation, hence pure es5 style written here
27 */
28 const AttachListener = java.lang.Object.extend({
29 interfaces: [android.view.View.OnAttachStateChangeListener],
30 init() {
31 // init must be defined at least
32 },
33 onViewAttachedToWindow(view) {
34 const owner = view[ownerSymbol];
35 if (owner) {
36 owner._onAttachedToWindow();
37 }
38 },
39 onViewDetachedFromWindow(view) {
40 const owner = view[ownerSymbol];
41 if (owner) {
42 owner._onDetachedFromWindow();
43 }
44 },
45 });
46 attachStateChangeListener = new AttachListener();
47 }
48 return attachStateChangeListener;
49}
50export class Frame extends FrameBase {
51 constructor() {
52 super();
53 this._containerViewId = -1;
54 this._tearDownPending = false;
55 this._attachedToWindow = false;
56 this._wasReset = false;
57 this._android = new AndroidFrame(this);
58 }
59 static reloadPage(context) {
60 const activity = androidUtils.getCurrentActivity();
61 const callbacks = activity[CALLBACKS];
62 if (callbacks) {
63 const rootView = callbacks.getRootView();
64 // Handle application root module
65 const isAppRootModuleChanged = context && context.path && context.path.includes(Application.getMainEntry().moduleName) && context.type !== 'style';
66 // Reset activity content when:
67 // + Application root module is changed
68 // + View did not handle the change
69 // Note:
70 // The case when neither app root module is changed, neighter livesync is handled on View,
71 // then changes will not apply until navigate forward to the module.
72 if (isAppRootModuleChanged || !rootView || !rootView._onLivesync(context)) {
73 callbacks.resetActivityContent(activity);
74 }
75 }
76 else {
77 Trace.error(`${activity}[CALLBACKS] is null or undefined`);
78 }
79 }
80 static get defaultAnimatedNavigation() {
81 return FrameBase.defaultAnimatedNavigation;
82 }
83 static set defaultAnimatedNavigation(value) {
84 FrameBase.defaultAnimatedNavigation = value;
85 }
86 static get defaultTransition() {
87 return FrameBase.defaultTransition;
88 }
89 static set defaultTransition(value) {
90 FrameBase.defaultTransition = value;
91 }
92 get containerViewId() {
93 return this._containerViewId;
94 }
95 // @ts-ignore
96 get android() {
97 return this._android;
98 }
99 get _hasFragments() {
100 return true;
101 }
102 _onAttachedToWindow() {
103 super._onAttachedToWindow();
104 // _onAttachedToWindow called from OS again after it was detach
105 // still happens with androidx.fragment:1.3.2
106 const activity = androidUtils.getCurrentActivity();
107 const lifecycleState = activity?.getLifecycle?.()?.getCurrentState() || androidx.lifecycle.Lifecycle.State.CREATED;
108 if ((this._manager && this._manager.isDestroyed()) || !lifecycleState.isAtLeast(androidx.lifecycle.Lifecycle.State.CREATED)) {
109 return;
110 }
111 this._attachedToWindow = true;
112 this._wasReset = false;
113 this._processNextNavigationEntry();
114 }
115 _onDetachedFromWindow() {
116 super._onDetachedFromWindow();
117 this._attachedToWindow = false;
118 }
119 _processNextNavigationEntry() {
120 // In case activity was destroyed because of back button pressed (e.g. app exit)
121 // and application is restored from recent apps, current fragment isn't recreated.
122 // In this case call _navigateCore in order to recreate the current fragment.
123 // Don't call navigate because it will fire navigation events.
124 // As JS instances are alive it is already done for the current page.
125 if (!this.isLoaded || this._executingContext) {
126 return;
127 }
128 // in case the activity is "reset" using resetRootView we must wait for
129 // the attachedToWindow event to make the first navigation or it will crash
130 // https://github.com/NativeScript/NativeScript/commit/9dd3e1a8076e5022e411f2f2eeba34aabc68d112
131 // though we should not do it on app "start"
132 // or it will create a "flash" to activity background color
133 if (this._wasReset && !this._attachedToWindow) {
134 return;
135 }
136 const animatedEntries = _getAnimatedEntries(this._android.frameId);
137 if (animatedEntries) {
138 // Wait until animations are completed.
139 if (animatedEntries.size > 0) {
140 return;
141 }
142 }
143 const manager = this._getFragmentManager();
144 const entry = this._currentEntry;
145 const isNewEntry = !this._cachedTransitionState || entry !== this._cachedTransitionState.entry;
146 if (isNewEntry && entry && manager && !manager.findFragmentByTag(entry.fragmentTag)) {
147 // Simulate first navigation (e.g. no animations or transitions)
148 // we need to cache the original animation settings so we can restore them later; otherwise as the
149 // simulated first navigation is not animated (it is actually a zero duration animator) the "popExit" animation
150 // is broken when transaction.setCustomAnimations(...) is used in a scenario with:
151 // 1) forward navigation
152 // 2) suspend / resume app
153 // 3) back navigation -- the exiting fragment is erroneously animated with the exit animator from the
154 // simulated navigation (NoTransition, zero duration animator) and thus the fragment immediately disappears;
155 // the user only sees the animation of the entering fragment as per its specific enter animation settings.
156 // NOTE: we are restoring the animation settings in Frame.setCurrent(...) as navigation completes asynchronously
157 const cachedTransitionState = getTransitionState(this._currentEntry);
158 if (cachedTransitionState) {
159 this._cachedTransitionState = cachedTransitionState;
160 this._currentEntry = null;
161 // NavigateCore will eventually call _processNextNavigationEntry again.
162 this._navigateCore(entry);
163 this._currentEntry = entry;
164 }
165 else {
166 super._processNextNavigationEntry();
167 }
168 }
169 else {
170 super._processNextNavigationEntry();
171 }
172 }
173 _getChildFragmentManager() {
174 let backstackEntry;
175 if (this._executingContext && this._executingContext.entry) {
176 backstackEntry = this._executingContext.entry;
177 }
178 else {
179 backstackEntry = this._currentEntry;
180 }
181 if (backstackEntry && backstackEntry.fragment && backstackEntry.fragment.isAdded()) {
182 return backstackEntry.fragment.getChildFragmentManager();
183 }
184 return null;
185 }
186 _onRootViewReset() {
187 super._onRootViewReset();
188 // used to handle the "first" navigate differently on first run and on reset
189 this._wasReset = true;
190 // call this AFTER the super call to ensure descendants apply their rootview-reset logic first
191 // i.e. in a scenario with nested frames / frame with tabview let the descendandt cleanup the inner
192 // fragments first, and then cleanup the parent fragments
193 this.disposeCurrentFragment();
194 }
195 onLoaded() {
196 if (this._originalBackground) {
197 this.backgroundColor = null;
198 this.backgroundColor = this._originalBackground;
199 this._originalBackground = null;
200 }
201 this._frameCreateTimeout = setTimeout(() => {
202 // there's a bug with nested frames where sometimes the nested fragment is not recreated at all
203 // so we manually check on loaded event if the fragment is not recreated and recreate it
204 const currentEntry = this._currentEntry || this._executingContext?.entry;
205 if (currentEntry) {
206 if (!currentEntry.fragment) {
207 const manager = this._getFragmentManager();
208 const transaction = manager.beginTransaction();
209 currentEntry.fragment = this.createFragment(currentEntry, currentEntry.fragmentTag);
210 _updateTransitions(currentEntry);
211 transaction.replace(this.containerViewId, currentEntry.fragment, currentEntry.fragmentTag);
212 transaction.commitAllowingStateLoss();
213 }
214 }
215 }, 0);
216 super.onLoaded();
217 }
218 onUnloaded() {
219 super.onUnloaded();
220 if (typeof this._frameCreateTimeout === 'number') {
221 clearTimeout(this._frameCreateTimeout);
222 this._frameCreateTimeout = null;
223 }
224 }
225 disposeCurrentFragment() {
226 if (!this._currentEntry || !this._currentEntry.fragment || !this._currentEntry.fragment.isAdded()) {
227 return;
228 }
229 const fragment = this._currentEntry.fragment;
230 const fragmentManager = fragment.getFragmentManager();
231 const transaction = fragmentManager.beginTransaction();
232 const fragmentExitTransition = fragment.getExitTransition();
233 // Reset animation to its initial state to prevent mirrored effect when restore current fragment transitions
234 if (fragmentExitTransition && fragmentExitTransition instanceof org.nativescript.widgets.CustomTransition) {
235 fragmentExitTransition.setResetOnTransitionEnd(true);
236 }
237 transaction.remove(fragment);
238 transaction.commitNowAllowingStateLoss();
239 }
240 createFragment(backstackEntry, fragmentTag) {
241 ensureFragmentClass();
242 const newFragment = new fragmentClass();
243 const args = new android.os.Bundle();
244 args.putInt(FRAMEID, this._android.frameId);
245 newFragment.setArguments(args);
246 setFragmentCallbacks(newFragment);
247 const callbacks = newFragment[CALLBACKS];
248 callbacks.frame = this;
249 callbacks.entry = backstackEntry;
250 // backstackEntry
251 backstackEntry.fragment = newFragment;
252 backstackEntry.fragmentTag = fragmentTag;
253 backstackEntry.navDepth = navDepth;
254 return newFragment;
255 }
256 setCurrent(entry, navigationType) {
257 const current = this._currentEntry;
258 const currentEntryChanged = current !== entry;
259 if (currentEntryChanged) {
260 this._updateBackstack(entry, navigationType);
261 // If activity was destroyed we need to destroy fragment and UI
262 // of current and new entries.
263 if (this._tearDownPending) {
264 this._tearDownPending = false;
265 if (!entry.recreated) {
266 clearEntry(entry);
267 }
268 if (current && !current.recreated) {
269 clearEntry(current);
270 }
271 // If we have context activity was recreated. Create new fragment
272 // and UI for the new current page.
273 const context = this._context;
274 if (context && !entry.recreated) {
275 entry.fragment = this.createFragment(entry, entry.fragmentTag);
276 entry.resolvedPage._setupUI(context);
277 }
278 entry.recreated = false;
279 if (current) {
280 current.recreated = false;
281 }
282 }
283 super.setCurrent(entry, navigationType);
284 // If we had real navigation process queue.
285 this._processNavigationQueue(entry.resolvedPage);
286 }
287 else {
288 // Otherwise currentPage was recreated so this wasn't real navigation.
289 // Continue with next item in the queue.
290 this._processNextNavigationEntry();
291 }
292 // restore cached animation settings if we just completed simulated first navigation (no animation)
293 if (this._cachedTransitionState) {
294 restoreTransitionState(this._currentEntry, this._cachedTransitionState);
295 this._cachedTransitionState = null;
296 }
297 // restore original fragment transitions if we just completed replace navigation (hmr)
298 if (navigationType === NavigationType.replace) {
299 _clearEntry(entry);
300 const animated = this._getIsAnimatedNavigation(entry.entry);
301 const navigationTransition = this._getNavigationTransition(entry.entry);
302 const currentEntry = null;
303 const newEntry = entry;
304 const transaction = null;
305 _setAndroidFragmentTransitions(animated, navigationTransition, currentEntry, newEntry, this._android.frameId, transaction);
306 }
307 }
308 onBackPressed() {
309 if (this.canGoBack()) {
310 this.goBack();
311 return true;
312 }
313 if (!this.navigationQueueIsEmpty()) {
314 const manager = this._getFragmentManager();
315 if (manager) {
316 manager.executePendingTransactions();
317 return true;
318 }
319 }
320 return false;
321 }
322 // HACK: This @profile decorator creates a circular dependency
323 // HACK: because the function parameter type is evaluated with 'typeof'
324 _navigateCore(newEntry) {
325 // should be (newEntry: BackstackEntry)
326 super._navigateCore(newEntry);
327 // set frameId here so that we could use it in fragment.transitions
328 newEntry.frameId = this._android.frameId;
329 const activity = this._android.activity;
330 if (!activity) {
331 // Activity not associated. In this case we have two execution paths:
332 // 1. This is the main frame for the application
333 // 2. This is an inner frame which requires a new Activity
334 const currentActivity = this._android.currentActivity;
335 if (currentActivity) {
336 startActivity(currentActivity, this._android.frameId);
337 }
338 return;
339 }
340 const manager = this._getFragmentManager();
341 const clearHistory = newEntry.entry.clearHistory;
342 const currentEntry = this._currentEntry;
343 // New Fragment
344 if (clearHistory) {
345 navDepth = -1;
346 }
347 const isReplace = this._executingContext && this._executingContext.navigationType === NavigationType.replace;
348 if (!isReplace) {
349 navDepth++;
350 }
351 fragmentId++;
352 const newFragmentTag = `fragment${fragmentId}[${navDepth}]`;
353 const newFragment = this.createFragment(newEntry, newFragmentTag);
354 const transaction = manager.beginTransaction();
355 const animated = currentEntry ? this._getIsAnimatedNavigation(newEntry.entry) : false;
356 // NOTE: Don't use transition for the initial navigation (same as on iOS)
357 // On API 21+ transition won't be triggered unless there was at least one
358 // layout pass so we will wait forever for transitionCompleted handler...
359 // https://github.com/NativeScript/NativeScript/issues/4895
360 let navigationTransition;
361 if (this._currentEntry) {
362 navigationTransition = this._getNavigationTransition(newEntry.entry);
363 }
364 else {
365 navigationTransition = null;
366 }
367 const isNestedDefaultTransition = !currentEntry;
368 _setAndroidFragmentTransitions(animated, navigationTransition, currentEntry, newEntry, this._android.frameId, transaction, isNestedDefaultTransition);
369 if (currentEntry && animated && !navigationTransition) {
370 //TODO: Check whether or not this is still necessary. For Modal views?
371 // transaction.setTransition(androidx.fragment.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
372 }
373 transaction.replace(this.containerViewId, newFragment, newFragmentTag);
374 navigationTransition?.instance?.androidFragmentTransactionCallback?.(transaction, currentEntry, newEntry);
375 transaction.commitAllowingStateLoss();
376 }
377 _goBackCore(backstackEntry) {
378 super._goBackCore(backstackEntry);
379 navDepth = backstackEntry.navDepth;
380 const manager = this._getFragmentManager();
381 const transaction = manager.beginTransaction();
382 if (!backstackEntry.fragment) {
383 // Happens on newer API levels. On older all fragments
384 // are recreated once activity is created.
385 // This entry fragment was destroyed by app suspend.
386 // We need to recreate its animations and then reverse it.
387 backstackEntry.fragment = this.createFragment(backstackEntry, backstackEntry.fragmentTag);
388 _updateTransitions(backstackEntry);
389 }
390 _reverseTransitions(backstackEntry, this._currentEntry);
391 transaction.replace(this.containerViewId, backstackEntry.fragment, backstackEntry.fragmentTag);
392 backstackEntry.transition?.androidFragmentTransactionCallback?.(transaction, this._currentEntry, backstackEntry);
393 transaction.commitAllowingStateLoss();
394 }
395 _removeEntry(removed) {
396 super._removeEntry(removed);
397 if (removed.fragment) {
398 _clearEntry(removed);
399 }
400 removed.fragment = null;
401 removed.viewSavedState = null;
402 }
403 createNativeView() {
404 // Create native view with available _currentEntry occur in Don't Keep Activities
405 // scenario when Activity is recreated on app suspend/resume. Push frame back in frame stack
406 // since it was removed in disposeNativeView() method.
407 if (this._currentEntry) {
408 this._pushInFrameStack();
409 }
410 return new org.nativescript.widgets.ContentLayout(this._context);
411 }
412 initNativeView() {
413 super.initNativeView();
414 const listener = getAttachListener();
415 this.nativeViewProtected.addOnAttachStateChangeListener(listener);
416 this.nativeViewProtected[ownerSymbol] = this;
417 this._android.rootViewGroup = this.nativeViewProtected;
418 if (this._containerViewId < 0) {
419 this._containerViewId = android.view.View.generateViewId();
420 }
421 this._android.rootViewGroup.setId(this._containerViewId);
422 }
423 disposeNativeView() {
424 const listener = getAttachListener();
425 this.nativeViewProtected.removeOnAttachStateChangeListener(listener);
426 this.nativeViewProtected[ownerSymbol] = null;
427 this._tearDownPending = !!this._executingContext;
428 const current = this._currentEntry;
429 const executingEntry = this._executingContext ? this._executingContext.entry : null;
430 this.backStack.forEach((entry) => {
431 // Don't destroy current and executing entries or UI will look blank.
432 // We will do it in setCurrent.
433 if (entry !== executingEntry) {
434 clearEntry(entry);
435 }
436 });
437 if (current && !executingEntry) {
438 clearEntry(current);
439 }
440 this._android.rootViewGroup = null;
441 this._removeFromFrameStack();
442 super.disposeNativeView();
443 }
444 _popFromFrameStack() {
445 if (!this._isInFrameStack) {
446 return;
447 }
448 super._popFromFrameStack();
449 }
450 _getNavBarVisible(page) {
451 switch (this.actionBarVisibility) {
452 case 'never':
453 return false;
454 case 'always':
455 return true;
456 default:
457 if (page.actionBarHidden !== undefined) {
458 return !page.actionBarHidden;
459 }
460 if (this._android && this._android.showActionBar !== undefined) {
461 return this._android.showActionBar;
462 }
463 return true;
464 }
465 }
466 _saveFragmentsState() {
467 // We save only fragments in backstack.
468 // Current fragment is saved by FragmentManager.
469 this.backStack.forEach((entry) => {
470 const view = entry.resolvedPage.nativeViewProtected;
471 if (!entry.viewSavedState && view) {
472 const viewState = new android.util.SparseArray();
473 view.saveHierarchyState(viewState);
474 entry.viewSavedState = viewState;
475 }
476 });
477 }
478}
479__decorate([
480 profile,
481 __metadata("design:type", Function),
482 __metadata("design:paramtypes", [Object]),
483 __metadata("design:returntype", void 0)
484], Frame.prototype, "_navigateCore", null);
485export function reloadPage(context) {
486 console.warn('reloadPage() is deprecated. Use Frame.reloadPage() instead.');
487 return Frame.reloadPage(context);
488}
489// attach on global, so it can be overwritten in NativeScript Angular
490global.__onLiveSyncCore = Frame.reloadPage;
491function cloneExpandedTransitionListener(expandedTransitionListener) {
492 if (!expandedTransitionListener) {
493 return null;
494 }
495 const cloneTransition = expandedTransitionListener.transition.clone();
496 return addNativeTransitionListener(expandedTransitionListener.entry, cloneTransition);
497}
498function getTransitionState(entry) {
499 const expandedEntry = entry;
500 const transitionState = {};
501 if (expandedEntry.enterTransitionListener && expandedEntry.exitTransitionListener) {
502 transitionState.enterTransitionListener = cloneExpandedTransitionListener(expandedEntry.enterTransitionListener);
503 transitionState.exitTransitionListener = cloneExpandedTransitionListener(expandedEntry.exitTransitionListener);
504 transitionState.reenterTransitionListener = cloneExpandedTransitionListener(expandedEntry.reenterTransitionListener);
505 transitionState.returnTransitionListener = cloneExpandedTransitionListener(expandedEntry.returnTransitionListener);
506 transitionState.transitionName = expandedEntry.transitionName;
507 transitionState.entry = entry;
508 }
509 else {
510 return null;
511 }
512 return transitionState;
513}
514function restoreTransitionState(entry, snapshot) {
515 const expandedEntry = entry;
516 if (snapshot.enterTransitionListener) {
517 expandedEntry.enterTransitionListener = snapshot.enterTransitionListener;
518 }
519 if (snapshot.exitTransitionListener) {
520 expandedEntry.exitTransitionListener = snapshot.exitTransitionListener;
521 }
522 if (snapshot.reenterTransitionListener) {
523 expandedEntry.reenterTransitionListener = snapshot.reenterTransitionListener;
524 }
525 if (snapshot.returnTransitionListener) {
526 expandedEntry.returnTransitionListener = snapshot.returnTransitionListener;
527 }
528 expandedEntry.transitionName = snapshot.transitionName;
529}
530function clearEntry(entry) {
531 if (entry.fragment) {
532 _clearFragment(entry);
533 }
534 entry.recreated = false;
535 entry.fragment = null;
536 const page = entry.resolvedPage;
537 if (page && page._context) {
538 entry.resolvedPage._tearDownUI(true);
539 }
540}
541let framesCounter = 0;
542const framesCache = new Array();
543class AndroidFrame extends Observable {
544 constructor(owner) {
545 super();
546 this._showActionBar = true;
547 this._owner = owner;
548 this.frameId = framesCounter++;
549 framesCache.push(new WeakRef(this));
550 }
551 get showActionBar() {
552 return this._showActionBar;
553 }
554 set showActionBar(value) {
555 if (this._showActionBar !== value) {
556 this._showActionBar = value;
557 if (this.owner.currentPage) {
558 this.owner.currentPage.actionBar.update();
559 }
560 }
561 }
562 get activity() {
563 const activity = this.owner._context;
564 if (activity) {
565 return activity;
566 }
567 // traverse the parent chain for an ancestor Frame
568 let currView = this._owner.parent;
569 while (currView) {
570 if (currView instanceof Frame) {
571 return currView.android.activity;
572 }
573 currView = currView.parent;
574 }
575 return undefined;
576 }
577 get actionBar() {
578 const activity = this.currentActivity;
579 if (!activity) {
580 return undefined;
581 }
582 const bar = activity.getActionBar();
583 if (!bar) {
584 return undefined;
585 }
586 return bar;
587 }
588 get currentActivity() {
589 let activity = this.activity;
590 if (activity) {
591 return activity;
592 }
593 const frames = _stack();
594 for (let length = frames.length, i = length - 1; i >= 0; i--) {
595 activity = frames[i].android.activity;
596 if (activity) {
597 return activity;
598 }
599 }
600 return undefined;
601 }
602 get owner() {
603 return this._owner;
604 }
605 canGoBack() {
606 if (!this.activity) {
607 return false;
608 }
609 // can go back only if it is not the main one.
610 return this.activity.getIntent().getAction() !== android.content.Intent.ACTION_MAIN;
611 }
612 fragmentForPage(entry) {
613 const tag = entry && entry.fragmentTag;
614 if (tag) {
615 return this.owner._getFragmentManager().findFragmentByTag(tag);
616 }
617 return undefined;
618 }
619}
620function startActivity(activity, frameId) {
621 // TODO: Implicitly, we will open the same activity type as the current one
622 const intent = new android.content.Intent(activity, activity.getClass());
623 intent.setAction(android.content.Intent.ACTION_DEFAULT);
624 intent.putExtra(INTENT_EXTRA, frameId);
625 // TODO: Put the navigation context (if any) in the intent
626 activity.startActivity(intent);
627}
628export function getFrameByNumberId(frameId) {
629 // Find the frame for this activity.
630 for (let i = 0; i < framesCache.length; i++) {
631 const aliveFrame = framesCache[i].get();
632 if (aliveFrame && aliveFrame.frameId === frameId) {
633 return aliveFrame.owner;
634 }
635 }
636 return null;
637}
638export function setActivityCallbacks(activity) {
639 activity[CALLBACKS] = new ActivityCallbacksImplementation();
640}
641export function setFragmentCallbacks(fragment) {
642 fragment[CALLBACKS] = new FragmentCallbacksImplementation();
643}
644//# sourceMappingURL=index.android.js.map
\No newline at end of file