mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-18 05:18:39 +08:00
refactor: circular deps part 15
This commit is contained in:
@ -425,11 +425,11 @@ export var testFileNameExtension = function () {
|
||||
var file = documents.getFile('Test.txt');
|
||||
// Getting the file name "Test.txt".
|
||||
var fileName = file.name;
|
||||
// Getting the file extension ".txt".
|
||||
// Getting the file extension "txt".
|
||||
var fileExtension = file.extension;
|
||||
// >> (hide)
|
||||
TKUnit.assert(fileName === 'Test.txt', 'Wrong file name.');
|
||||
TKUnit.assert(fileExtension === '.txt', 'Wrong extension.');
|
||||
TKUnit.assert(fileExtension === 'txt', 'Wrong extension.');
|
||||
file.remove();
|
||||
// << (hide)
|
||||
// << file-system-extension
|
||||
@ -633,7 +633,7 @@ export function test_FSEntity_Properties() {
|
||||
var documents = fs.knownFolders.documents();
|
||||
var file = documents.getFile('Test_File.txt');
|
||||
|
||||
TKUnit.assert(file.extension === '.txt', 'FileEntity.extension not working.');
|
||||
TKUnit.assert(file.extension === 'txt', 'FileEntity.extension not working.');
|
||||
TKUnit.assert(file.isLocked === false, 'FileEntity.isLocked not working.');
|
||||
TKUnit.assert(file.lastModified instanceof Date, 'FileEntity.lastModified not working.');
|
||||
TKUnit.assert(file.size === 0, 'FileEntity.size not working.');
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as textModule from '../text';
|
||||
import { getFileExtension, android as androidUtils } from '../utils';
|
||||
import { android as androidUtils } from '../utils';
|
||||
import { getFileExtension } from '../utils/utils-shared';
|
||||
import { SDK_VERSION } from '../utils/constants';
|
||||
|
||||
import type { IFileSystemAccess } from './file-system-access';
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { encoding as textEncoding } from '../text';
|
||||
import { ios as iosUtils } from '../utils';
|
||||
import { getFileExtension } from '../utils/utils-shared';
|
||||
|
||||
// TODO: Implement all the APIs receiving callback using async blocks
|
||||
// TODO: Check whether we need try/catch blocks for the iOS implementation
|
||||
@ -611,24 +612,8 @@ export class FileSystemAccess {
|
||||
return url.path;
|
||||
}
|
||||
|
||||
// TODO: This method is the same as in the iOS implementation.
|
||||
// Make it in a separate file / module so it can be reused from both implementations.
|
||||
public getFileExtension(path: string): string {
|
||||
// TODO [For Panata]: The definitions currently specify "any" as a return value of this method
|
||||
//const nsString = Foundation.NSString.stringWithString(path);
|
||||
//const extension = nsString.pathExtension();
|
||||
|
||||
//if (extension && extension.length > 0) {
|
||||
// extension = extension.concat(".", extension);
|
||||
//}
|
||||
|
||||
//return extension;
|
||||
const dotIndex = path.lastIndexOf('.');
|
||||
if (dotIndex && dotIndex >= 0 && dotIndex < path.length) {
|
||||
return path.substring(dotIndex);
|
||||
}
|
||||
|
||||
return '';
|
||||
return getFileExtension(path);
|
||||
}
|
||||
|
||||
private deleteEntity(path: string, onError?: (error: any) => any) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
// imported for definition purposes only
|
||||
import * as httpModule from '../../http';
|
||||
import type { Headers, HttpResponse, HttpRequestOptions } from '../../http';
|
||||
import { ImageSource } from '../../image-source';
|
||||
import { Screen } from '../../platform';
|
||||
import { Screen } from '../../platform/screen';
|
||||
import { File } from '../../file-system';
|
||||
import { HttpResponseEncoding } from '../http-interfaces';
|
||||
import { getFilenameFromUrl } from './http-request-common';
|
||||
@ -51,7 +51,7 @@ function onRequestComplete(requestId: number, result: org.nativescript.widgets.A
|
||||
}
|
||||
|
||||
// read the headers
|
||||
const headers: httpModule.Headers = {};
|
||||
const headers: Headers = {};
|
||||
if (result.headers) {
|
||||
const jHeaders = result.headers;
|
||||
const length = jHeaders.size();
|
||||
@ -177,7 +177,7 @@ function onRequestError(error: string, requestId: number) {
|
||||
}
|
||||
}
|
||||
|
||||
function buildJavaOptions(options: httpModule.HttpRequestOptions) {
|
||||
function buildJavaOptions(options: HttpRequestOptions) {
|
||||
if (typeof options.url !== 'string') {
|
||||
throw new Error('Http request must provide a valid url.');
|
||||
}
|
||||
@ -224,13 +224,13 @@ function buildJavaOptions(options: httpModule.HttpRequestOptions) {
|
||||
return javaOptions;
|
||||
}
|
||||
|
||||
export function request(options: httpModule.HttpRequestOptions): Promise<httpModule.HttpResponse> {
|
||||
export function request(options: HttpRequestOptions): Promise<HttpResponse> {
|
||||
if (options === undefined || options === null) {
|
||||
// TODO: Shouldn't we throw an error here - defensive programming
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise<httpModule.HttpResponse>((resolve, reject) => {
|
||||
return new Promise<HttpResponse>((resolve, reject) => {
|
||||
try {
|
||||
// initialize the options
|
||||
const javaOptions = buildJavaOptions(options);
|
||||
@ -289,7 +289,7 @@ function decodeResponse(raw: any, encoding?: HttpResponseEncoding) {
|
||||
return raw.toString(charsetName);
|
||||
}
|
||||
|
||||
export function addHeader(headers: httpModule.Headers, key: string, value: string): void {
|
||||
export function addHeader(headers: Headers, key: string, value: string): void {
|
||||
if (!headers[key]) {
|
||||
headers[key] = value;
|
||||
} else if (Array.isArray(headers[key])) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { SDK_VERSION } from '../../utils/constants';
|
||||
import { isRealDevice } from '../../utils';
|
||||
import { isRealDevice } from '../../utils/native-helper';
|
||||
import * as types from '../../utils/types';
|
||||
import * as domainDebugger from '../../debugger';
|
||||
import { getFilenameFromUrl } from './http-request-common';
|
||||
|
@ -63,7 +63,7 @@
|
||||
// module.exports.EasySAXParser = EasySAXParser;
|
||||
// };
|
||||
|
||||
export function EasySAXParser() {
|
||||
function EasySAXParser() {
|
||||
'use strict';
|
||||
|
||||
if (!this) return null;
|
||||
@ -780,3 +780,5 @@ EasySAXParser.prototype.parse = function(xml) {
|
||||
j += 1;
|
||||
};
|
||||
};
|
||||
|
||||
export { EasySAXParser };
|
@ -1,5 +1,5 @@
|
||||
import { EventData, Observable } from '../data/observable';
|
||||
import { Screen } from '../platform';
|
||||
import { Screen } from '../platform/screen';
|
||||
import { getApplicationProperties, toggleApplicationEventListeners } from '../application/helpers-common';
|
||||
import type { ApplicationEventData } from '../application/application-interfaces';
|
||||
import { matchQuery, MediaQueryType } from '../css-mediaquery';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { isEmbedded, getEmbeddedView } from '../embedding';
|
||||
import { setFragmentCallbacks } from '.';
|
||||
import { setFragmentCallbacks } from './frame-helper-for-android';
|
||||
|
||||
declare const com: any;
|
||||
|
||||
|
342
packages/core/ui/frame/frame-helper-for-android.ts
Normal file
342
packages/core/ui/frame/frame-helper-for-android.ts
Normal file
@ -0,0 +1,342 @@
|
||||
import { Trace } from '../../trace';
|
||||
import { _clearEntry, _clearFragment, _getAnimatedEntries, _reverseTransitions, _setAndroidFragmentTransitions, _updateTransitions } from './fragment.transitions';
|
||||
import type { BackstackEntry } from '.';
|
||||
import { profile } from '../../profiling';
|
||||
import { getNativeApp } from '../../application/helpers-common';
|
||||
import { Color } from '../../color';
|
||||
import type { Page } from '../page';
|
||||
import type { AndroidFrame as Frame } from '.';
|
||||
export const FRAMEID = '_frameId';
|
||||
export const CALLBACKS = '_callbacks';
|
||||
export const framesCache = new Array<WeakRef<any>>();
|
||||
|
||||
export interface AndroidFragmentCallbacks {
|
||||
onHiddenChanged(fragment: any, hidden: boolean, superFunc: Function): void;
|
||||
onCreateAnimator(fragment: any, transit: number, enter: boolean, nextAnim: number, superFunc: Function): any;
|
||||
onCreate(fragment: any, savedInstanceState: any, superFunc: Function): void;
|
||||
onCreateView(fragment: any, inflater: any, container: any, savedInstanceState: any, superFunc: Function): any;
|
||||
onSaveInstanceState(fragment: any, outState: any, superFunc: Function): void;
|
||||
onDestroyView(fragment: any, superFunc: Function): void;
|
||||
onDestroy(fragment: any, superFunc: Function): void;
|
||||
onPause(fragment: any, superFunc: Function): void;
|
||||
onResume(fragment: any, superFunc: Function): void;
|
||||
onStop(fragment: any, superFunc: Function): void;
|
||||
toStringOverride(fragment: any, superFunc: Function): string;
|
||||
}
|
||||
|
||||
function findPageForFragment(fragment: androidx.fragment.app.Fragment, frame: Frame) {
|
||||
const fragmentTag = fragment.getTag();
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`Finding page for ${fragmentTag}.`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
let entry: BackstackEntry;
|
||||
const current = frame._currentEntry;
|
||||
const executingContext = frame._executingContext;
|
||||
if (current && current.fragmentTag === fragmentTag) {
|
||||
entry = current;
|
||||
} else if (executingContext && executingContext.entry && executingContext.entry.fragmentTag === fragmentTag) {
|
||||
entry = executingContext.entry;
|
||||
}
|
||||
|
||||
let page: Page;
|
||||
if (entry) {
|
||||
entry.recreated = true;
|
||||
page = entry.resolvedPage;
|
||||
}
|
||||
|
||||
if (page) {
|
||||
const callbacks: FragmentCallbacksImplementation = fragment[CALLBACKS];
|
||||
callbacks.frame = frame;
|
||||
callbacks.entry = entry;
|
||||
entry.fragment = fragment;
|
||||
_updateTransitions(entry);
|
||||
} else {
|
||||
throw new Error(`Could not find a page for ${fragmentTag}.`);
|
||||
}
|
||||
}
|
||||
|
||||
export class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
|
||||
public frame: Frame;
|
||||
public entry: BackstackEntry;
|
||||
private backgroundBitmap: android.graphics.Bitmap = null;
|
||||
|
||||
@profile
|
||||
public onHiddenChanged(fragment: androidx.fragment.app.Fragment, hidden: boolean, superFunc: Function): void {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onHiddenChanged(${hidden})`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
superFunc.call(fragment, hidden);
|
||||
}
|
||||
|
||||
@profile
|
||||
public onCreateAnimator(fragment: androidx.fragment.app.Fragment, transit: number, enter: boolean, nextAnim: number, superFunc: Function): android.animation.Animator {
|
||||
let animator = null;
|
||||
const entry = <any>this.entry;
|
||||
|
||||
// Return enterAnimator only when new (no current entry) nested transition.
|
||||
if (enter && entry.isNestedDefaultTransition) {
|
||||
animator = entry.enterAnimator;
|
||||
entry.isNestedDefaultTransition = false;
|
||||
}
|
||||
|
||||
return animator || superFunc.call(fragment, transit, enter, nextAnim);
|
||||
}
|
||||
|
||||
@profile
|
||||
public onCreate(fragment: androidx.fragment.app.Fragment, savedInstanceState: android.os.Bundle, superFunc: Function): void {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onCreate(${savedInstanceState})`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
superFunc.call(fragment, savedInstanceState);
|
||||
// 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) {
|
||||
const args = fragment.getArguments();
|
||||
const frameId = args.getInt(FRAMEID);
|
||||
const frame = getFrameByNumberId(frameId);
|
||||
if (!frame) {
|
||||
throw new Error(`Cannot find Frame for ${fragment}`);
|
||||
}
|
||||
|
||||
findPageForFragment(fragment, frame);
|
||||
}
|
||||
}
|
||||
|
||||
@profile
|
||||
public onCreateView(fragment: androidx.fragment.app.Fragment, inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle, superFunc: Function): android.view.View {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onCreateView(inflater, container, ${savedInstanceState})`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
const entry = this.entry;
|
||||
if (!entry) {
|
||||
Trace.error(`${fragment}.onCreateView: entry is null or undefined`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const page = entry.resolvedPage;
|
||||
if (!page) {
|
||||
Trace.error(`${fragment}.onCreateView: entry has no resolvedPage`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const frame = this.frame;
|
||||
if (!frame) {
|
||||
Trace.error(`${fragment}.onCreateView: this.frame is null or undefined`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
frame._resolvedPage = page;
|
||||
|
||||
// @ts-ignore
|
||||
if (page.parent === frame) {
|
||||
(frame as Frame)._inheritStyles(page);
|
||||
|
||||
// If we are navigating to a page that was destroyed
|
||||
// reinitialize its UI.
|
||||
if (!page._context) {
|
||||
const context = (container && container.getContext()) || (inflater && inflater.getContext());
|
||||
page._setupUI(context);
|
||||
}
|
||||
|
||||
if ((frame as Frame).isLoaded && !page.isLoaded) {
|
||||
page.callLoaded();
|
||||
}
|
||||
} else {
|
||||
if (!page.parent) {
|
||||
if (!frame._styleScope) {
|
||||
// Make sure page will have styleScope even if parents don't.
|
||||
page._updateStyleScope();
|
||||
}
|
||||
|
||||
frame._addView(page);
|
||||
} else {
|
||||
throw new Error('Page is already shown on another frame.');
|
||||
}
|
||||
}
|
||||
|
||||
const savedState = entry.viewSavedState;
|
||||
if (savedState) {
|
||||
(<android.view.View>page.nativeViewProtected).restoreHierarchyState(savedState);
|
||||
entry.viewSavedState = null;
|
||||
}
|
||||
|
||||
// fixes 'java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first'.
|
||||
// on app resume in nested frame scenarios with support library version greater than 26.0.0
|
||||
// HACK: this whole code block shouldn't be necessary as the native view is supposedly removed from its parent
|
||||
// right after onDestroyView(...) is called but for some reason the fragment view (page) still thinks it has a
|
||||
// parent while its supposed parent believes it properly removed its children; in order to "force" the child to
|
||||
// lose its parent we temporarily add it to the parent, and then remove it (addViewInLayout doesn't trigger layout pass)
|
||||
const nativeView = page.nativeViewProtected;
|
||||
if (nativeView != null) {
|
||||
const parentView = nativeView.getParent();
|
||||
if (parentView instanceof android.view.ViewGroup) {
|
||||
if (parentView.getChildCount() === 0) {
|
||||
parentView.addViewInLayout(nativeView, -1, new org.nativescript.widgets.CommonLayoutParams());
|
||||
}
|
||||
|
||||
parentView.removeAllViews();
|
||||
}
|
||||
}
|
||||
|
||||
return page.nativeViewProtected;
|
||||
}
|
||||
|
||||
@profile
|
||||
public onSaveInstanceState(fragment: androidx.fragment.app.Fragment, outState: android.os.Bundle, superFunc: Function): void {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onSaveInstanceState(${outState})`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
superFunc.call(fragment, outState);
|
||||
}
|
||||
|
||||
@profile
|
||||
public onDestroyView(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
|
||||
try {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onDestroyView()`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
const hasRemovingParent = fragment.getRemovingParentFragment();
|
||||
|
||||
if (hasRemovingParent) {
|
||||
const nativeFrameView = this.frame.nativeViewProtected;
|
||||
if (nativeFrameView) {
|
||||
const bitmapDrawable = new android.graphics.drawable.BitmapDrawable((getNativeApp() as android.app.Application).getApplicationContext().getResources(), this.backgroundBitmap);
|
||||
this.frame._originalBackground = this.frame.backgroundColor || new Color('White');
|
||||
nativeFrameView.setBackgroundDrawable(bitmapDrawable);
|
||||
this.backgroundBitmap = null;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
superFunc.call(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
@profile
|
||||
public onDestroy(fragment: androidx.fragment.app.Fragment, superFunc: Function): void {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onDestroy()`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
superFunc.call(fragment);
|
||||
|
||||
const entry = this.entry;
|
||||
if (!entry) {
|
||||
Trace.error(`${fragment}.onDestroy: entry is null or undefined`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// [nested frames / fragments] see https://github.com/NativeScript/NativeScript/issues/6629
|
||||
// retaining reference to a destroyed fragment here somehow causes a cryptic
|
||||
// "IllegalStateException: Failure saving state: active fragment has cleared index: -1"
|
||||
// in a specific mixed parent / nested frame navigation scenario
|
||||
entry.fragment = null;
|
||||
|
||||
const page = entry.resolvedPage;
|
||||
if (!page) {
|
||||
// todo: check why this happens when using shared element transition!!!
|
||||
// commented out the Trace.error to prevent a crash (the app will still work interestingly)
|
||||
console.log(`${fragment}.onDestroy: entry has no resolvedPage`);
|
||||
// Trace.error(`${fragment}.onDestroy: entry has no resolvedPage`);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@profile
|
||||
public onPause(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
|
||||
try {
|
||||
// Get view as bitmap and set it as background. This is workaround for the disapearing nested fragments.
|
||||
// TODO: Consider removing it when update to androidx.fragment:1.2.0
|
||||
const hasRemovingParent = fragment.getRemovingParentFragment();
|
||||
|
||||
if (hasRemovingParent) {
|
||||
this.backgroundBitmap = this.loadBitmapFromView(this.frame.nativeViewProtected);
|
||||
}
|
||||
} finally {
|
||||
superFunc.call(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
@profile
|
||||
public onResume(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
|
||||
const frame = this.entry.resolvedPage.frame;
|
||||
// on some cases during the first navigation on nested frames the animation doesn't trigger
|
||||
// we depend on the animation (even None animation) to set the entry as the current entry
|
||||
// animation should start between start and resume, so if we have an executing navigation here it probably means the animation was skipped
|
||||
// so we manually set the entry
|
||||
// also, to be compatible with fragments 1.2.x we need this setTimeout as animations haven't run on onResume yet
|
||||
const weakRef = new WeakRef(this);
|
||||
setTimeout(() => {
|
||||
const owner = weakRef.get();
|
||||
if (!owner) {
|
||||
return;
|
||||
}
|
||||
if (frame._executingContext && !(<any>owner.entry).isAnimationRunning) {
|
||||
frame.setCurrent(owner.entry, frame._executingContext.navigationType);
|
||||
}
|
||||
}, 0);
|
||||
|
||||
superFunc.call(fragment);
|
||||
}
|
||||
|
||||
@profile
|
||||
public onStop(fragment: androidx.fragment.app.Fragment, superFunc: Function): void {
|
||||
superFunc.call(fragment);
|
||||
}
|
||||
|
||||
@profile
|
||||
public toStringOverride(fragment: androidx.fragment.app.Fragment, superFunc: Function): string {
|
||||
const entry = this.entry;
|
||||
if (entry) {
|
||||
return `${entry.fragmentTag}<${entry.resolvedPage}>`;
|
||||
} else {
|
||||
return 'NO ENTRY, ' + superFunc.call(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
private loadBitmapFromView(view: android.view.View): android.graphics.Bitmap {
|
||||
// Don't try to create bitmaps with no dimensions as this causes a crash
|
||||
// This might happen when showing and closing dialogs fast.
|
||||
if (!(view && view.getWidth() > 0 && view.getHeight() > 0)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Another way to get view bitmap. Test performance vs setDrawingCacheEnabled
|
||||
// const width = view.getWidth();
|
||||
// const height = view.getHeight();
|
||||
// const bitmap = android.graphics.Bitmap.createBitmap(width, height, android.graphics.Bitmap.Config.ARGB_8888);
|
||||
// const canvas = new android.graphics.Canvas(bitmap);
|
||||
// view.layout(0, 0, width, height);
|
||||
// view.draw(canvas);
|
||||
|
||||
// view.setDrawingCacheEnabled(true);
|
||||
// const drawCache = view.getDrawingCache();
|
||||
// const bitmap = android.graphics.Bitmap.createBitmap(drawCache);
|
||||
// view.setDrawingCacheEnabled(false);
|
||||
return org.nativescript.widgets.Utils.getBitmapFromView(view);
|
||||
}
|
||||
}
|
||||
|
||||
export function getFrameByNumberId(frameId: number): Frame {
|
||||
// Find the frame for this activity.
|
||||
for (let i = 0; i < framesCache.length; i++) {
|
||||
const aliveFrame = framesCache[i].get();
|
||||
if (aliveFrame && aliveFrame.frameId === frameId) {
|
||||
return aliveFrame.owner;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function setFragmentCallbacks(fragment: androidx.fragment.app.Fragment): void {
|
||||
fragment[CALLBACKS] = new FragmentCallbacksImplementation();
|
||||
}
|
@ -11,18 +11,16 @@ import { android as androidUtils } from '../../utils/native-helper';
|
||||
import type { ExpandedEntry } from './fragment.transitions.android';
|
||||
import { ensureFragmentClass, fragmentClass } from './fragment';
|
||||
import { getAppMainEntry } from '../../application/helpers-common';
|
||||
import { getNativeApp } from '../../application/helpers-common';
|
||||
import { Color } from '../../color';
|
||||
|
||||
import { AndroidActivityBackPressedEventData, AndroidActivityNewIntentEventData, AndroidActivityRequestPermissionsEventData, AndroidActivityResultEventData } from '../../application/application-interfaces';
|
||||
import { Application } from '../../application/application';
|
||||
import { isEmbedded, setEmbeddedView } from '../embedding';
|
||||
import { CALLBACKS, FRAMEID, framesCache, setFragmentCallbacks } from './frame-helper-for-android';
|
||||
|
||||
export * from './frame-common';
|
||||
export { setFragmentClass } from './fragment';
|
||||
|
||||
const INTENT_EXTRA = 'com.tns.activity';
|
||||
const FRAMEID = '_frameId';
|
||||
const CALLBACKS = '_callbacks';
|
||||
|
||||
const ownerSymbol = Symbol('_owner');
|
||||
|
||||
@ -648,7 +646,6 @@ function restoreTransitionState(entry: BackstackEntry, snapshot: TransitionState
|
||||
}
|
||||
|
||||
let framesCounter = 0;
|
||||
const framesCache = new Array<WeakRef<AndroidFrame>>();
|
||||
|
||||
class AndroidFrame extends Observable implements AndroidFrameDefinition {
|
||||
public rootViewGroup: android.view.ViewGroup;
|
||||
@ -760,18 +757,6 @@ function startActivity(activity: androidx.appcompat.app.AppCompatActivity, frame
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
|
||||
export function getFrameByNumberId(frameId: number): Frame {
|
||||
// Find the frame for this activity.
|
||||
for (let i = 0; i < framesCache.length; i++) {
|
||||
const aliveFrame = framesCache[i].get();
|
||||
if (aliveFrame && aliveFrame.frameId === frameId) {
|
||||
return aliveFrame.owner;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const activityRootViewsMap = new Map<number, WeakRef<View>>();
|
||||
const ROOT_VIEW_ID_EXTRA = 'com.tns.activity.rootViewId';
|
||||
|
||||
@ -1063,310 +1048,6 @@ export class ActivityCallbacksImplementation implements AndroidActivityCallbacks
|
||||
}
|
||||
}
|
||||
|
||||
export class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
|
||||
public frame: Frame;
|
||||
public entry: BackstackEntry;
|
||||
private backgroundBitmap: android.graphics.Bitmap = null;
|
||||
|
||||
@profile
|
||||
public onHiddenChanged(fragment: androidx.fragment.app.Fragment, hidden: boolean, superFunc: Function): void {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onHiddenChanged(${hidden})`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
superFunc.call(fragment, hidden);
|
||||
}
|
||||
|
||||
@profile
|
||||
public onCreateAnimator(fragment: androidx.fragment.app.Fragment, transit: number, enter: boolean, nextAnim: number, superFunc: Function): android.animation.Animator {
|
||||
let animator = null;
|
||||
const entry = <any>this.entry;
|
||||
|
||||
// Return enterAnimator only when new (no current entry) nested transition.
|
||||
if (enter && entry.isNestedDefaultTransition) {
|
||||
animator = entry.enterAnimator;
|
||||
entry.isNestedDefaultTransition = false;
|
||||
}
|
||||
|
||||
return animator || superFunc.call(fragment, transit, enter, nextAnim);
|
||||
}
|
||||
|
||||
@profile
|
||||
public onCreate(fragment: androidx.fragment.app.Fragment, savedInstanceState: android.os.Bundle, superFunc: Function): void {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onCreate(${savedInstanceState})`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
superFunc.call(fragment, savedInstanceState);
|
||||
// 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) {
|
||||
const args = fragment.getArguments();
|
||||
const frameId = args.getInt(FRAMEID);
|
||||
const frame = getFrameByNumberId(frameId);
|
||||
if (!frame) {
|
||||
throw new Error(`Cannot find Frame for ${fragment}`);
|
||||
}
|
||||
|
||||
findPageForFragment(fragment, frame);
|
||||
}
|
||||
}
|
||||
|
||||
@profile
|
||||
public onCreateView(fragment: androidx.fragment.app.Fragment, inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle, superFunc: Function): android.view.View {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onCreateView(inflater, container, ${savedInstanceState})`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
const entry = this.entry;
|
||||
if (!entry) {
|
||||
Trace.error(`${fragment}.onCreateView: entry is null or undefined`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const page = entry.resolvedPage;
|
||||
if (!page) {
|
||||
Trace.error(`${fragment}.onCreateView: entry has no resolvedPage`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const frame = this.frame;
|
||||
if (!frame) {
|
||||
Trace.error(`${fragment}.onCreateView: this.frame is null or undefined`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
frame._resolvedPage = page;
|
||||
|
||||
if (page.parent === frame) {
|
||||
frame._inheritStyles(page);
|
||||
|
||||
// If we are navigating to a page that was destroyed
|
||||
// reinitialize its UI.
|
||||
if (!page._context) {
|
||||
const context = (container && container.getContext()) || (inflater && inflater.getContext());
|
||||
page._setupUI(context);
|
||||
}
|
||||
|
||||
if (frame.isLoaded && !page.isLoaded) {
|
||||
page.callLoaded();
|
||||
}
|
||||
} else {
|
||||
if (!page.parent) {
|
||||
if (!frame._styleScope) {
|
||||
// Make sure page will have styleScope even if parents don't.
|
||||
page._updateStyleScope();
|
||||
}
|
||||
|
||||
frame._addView(page);
|
||||
} else {
|
||||
throw new Error('Page is already shown on another frame.');
|
||||
}
|
||||
}
|
||||
|
||||
const savedState = entry.viewSavedState;
|
||||
if (savedState) {
|
||||
(<android.view.View>page.nativeViewProtected).restoreHierarchyState(savedState);
|
||||
entry.viewSavedState = null;
|
||||
}
|
||||
|
||||
// fixes 'java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first'.
|
||||
// on app resume in nested frame scenarios with support library version greater than 26.0.0
|
||||
// HACK: this whole code block shouldn't be necessary as the native view is supposedly removed from its parent
|
||||
// right after onDestroyView(...) is called but for some reason the fragment view (page) still thinks it has a
|
||||
// parent while its supposed parent believes it properly removed its children; in order to "force" the child to
|
||||
// lose its parent we temporarily add it to the parent, and then remove it (addViewInLayout doesn't trigger layout pass)
|
||||
const nativeView = page.nativeViewProtected;
|
||||
if (nativeView != null) {
|
||||
const parentView = nativeView.getParent();
|
||||
if (parentView instanceof android.view.ViewGroup) {
|
||||
if (parentView.getChildCount() === 0) {
|
||||
parentView.addViewInLayout(nativeView, -1, new org.nativescript.widgets.CommonLayoutParams());
|
||||
}
|
||||
|
||||
parentView.removeAllViews();
|
||||
}
|
||||
}
|
||||
|
||||
return page.nativeViewProtected;
|
||||
}
|
||||
|
||||
@profile
|
||||
public onSaveInstanceState(fragment: androidx.fragment.app.Fragment, outState: android.os.Bundle, superFunc: Function): void {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onSaveInstanceState(${outState})`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
superFunc.call(fragment, outState);
|
||||
}
|
||||
|
||||
@profile
|
||||
public onDestroyView(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
|
||||
try {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onDestroyView()`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
const hasRemovingParent = fragment.getRemovingParentFragment();
|
||||
|
||||
if (hasRemovingParent) {
|
||||
const nativeFrameView = this.frame.nativeViewProtected;
|
||||
if (nativeFrameView) {
|
||||
const bitmapDrawable = new android.graphics.drawable.BitmapDrawable((getNativeApp() as android.app.Application).getApplicationContext().getResources(), this.backgroundBitmap);
|
||||
this.frame._originalBackground = this.frame.backgroundColor || new Color('White');
|
||||
nativeFrameView.setBackgroundDrawable(bitmapDrawable);
|
||||
this.backgroundBitmap = null;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
superFunc.call(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
@profile
|
||||
public onDestroy(fragment: androidx.fragment.app.Fragment, superFunc: Function): void {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${fragment}.onDestroy()`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
superFunc.call(fragment);
|
||||
|
||||
const entry = this.entry;
|
||||
if (!entry) {
|
||||
Trace.error(`${fragment}.onDestroy: entry is null or undefined`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// [nested frames / fragments] see https://github.com/NativeScript/NativeScript/issues/6629
|
||||
// retaining reference to a destroyed fragment here somehow causes a cryptic
|
||||
// "IllegalStateException: Failure saving state: active fragment has cleared index: -1"
|
||||
// in a specific mixed parent / nested frame navigation scenario
|
||||
entry.fragment = null;
|
||||
|
||||
const page = entry.resolvedPage;
|
||||
if (!page) {
|
||||
// todo: check why this happens when using shared element transition!!!
|
||||
// commented out the Trace.error to prevent a crash (the app will still work interestingly)
|
||||
console.log(`${fragment}.onDestroy: entry has no resolvedPage`);
|
||||
// Trace.error(`${fragment}.onDestroy: entry has no resolvedPage`);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@profile
|
||||
public onPause(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
|
||||
try {
|
||||
// Get view as bitmap and set it as background. This is workaround for the disapearing nested fragments.
|
||||
// TODO: Consider removing it when update to androidx.fragment:1.2.0
|
||||
const hasRemovingParent = fragment.getRemovingParentFragment();
|
||||
|
||||
if (hasRemovingParent) {
|
||||
this.backgroundBitmap = this.loadBitmapFromView(this.frame.nativeViewProtected);
|
||||
}
|
||||
} finally {
|
||||
superFunc.call(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
@profile
|
||||
public onResume(fragment: org.nativescript.widgets.FragmentBase, superFunc: Function): void {
|
||||
const frame = this.entry.resolvedPage.frame;
|
||||
// on some cases during the first navigation on nested frames the animation doesn't trigger
|
||||
// we depend on the animation (even None animation) to set the entry as the current entry
|
||||
// animation should start between start and resume, so if we have an executing navigation here it probably means the animation was skipped
|
||||
// so we manually set the entry
|
||||
// also, to be compatible with fragments 1.2.x we need this setTimeout as animations haven't run on onResume yet
|
||||
const weakRef = new WeakRef(this);
|
||||
setTimeout(() => {
|
||||
const owner = weakRef.get();
|
||||
if (!owner) {
|
||||
return;
|
||||
}
|
||||
if (frame._executingContext && !(<any>owner.entry).isAnimationRunning) {
|
||||
frame.setCurrent(owner.entry, frame._executingContext.navigationType);
|
||||
}
|
||||
}, 0);
|
||||
|
||||
superFunc.call(fragment);
|
||||
}
|
||||
|
||||
@profile
|
||||
public onStop(fragment: androidx.fragment.app.Fragment, superFunc: Function): void {
|
||||
superFunc.call(fragment);
|
||||
}
|
||||
|
||||
@profile
|
||||
public toStringOverride(fragment: androidx.fragment.app.Fragment, superFunc: Function): string {
|
||||
const entry = this.entry;
|
||||
if (entry) {
|
||||
return `${entry.fragmentTag}<${entry.resolvedPage}>`;
|
||||
} else {
|
||||
return 'NO ENTRY, ' + superFunc.call(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
private loadBitmapFromView(view: android.view.View): android.graphics.Bitmap {
|
||||
// Don't try to create bitmaps with no dimensions as this causes a crash
|
||||
// This might happen when showing and closing dialogs fast.
|
||||
if (!(view && view.getWidth() > 0 && view.getHeight() > 0)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Another way to get view bitmap. Test performance vs setDrawingCacheEnabled
|
||||
// const width = view.getWidth();
|
||||
// const height = view.getHeight();
|
||||
// const bitmap = android.graphics.Bitmap.createBitmap(width, height, android.graphics.Bitmap.Config.ARGB_8888);
|
||||
// const canvas = new android.graphics.Canvas(bitmap);
|
||||
// view.layout(0, 0, width, height);
|
||||
// view.draw(canvas);
|
||||
|
||||
// view.setDrawingCacheEnabled(true);
|
||||
// const drawCache = view.getDrawingCache();
|
||||
// const bitmap = android.graphics.Bitmap.createBitmap(drawCache);
|
||||
// view.setDrawingCacheEnabled(false);
|
||||
return org.nativescript.widgets.Utils.getBitmapFromView(view);
|
||||
}
|
||||
}
|
||||
|
||||
function findPageForFragment(fragment: androidx.fragment.app.Fragment, frame: Frame) {
|
||||
const fragmentTag = fragment.getTag();
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`Finding page for ${fragmentTag}.`, Trace.categories.NativeLifecycle);
|
||||
}
|
||||
|
||||
let entry: BackstackEntry;
|
||||
const current = frame._currentEntry;
|
||||
const executingContext = frame._executingContext;
|
||||
if (current && current.fragmentTag === fragmentTag) {
|
||||
entry = current;
|
||||
} else if (executingContext && executingContext.entry && executingContext.entry.fragmentTag === fragmentTag) {
|
||||
entry = executingContext.entry;
|
||||
}
|
||||
|
||||
let page: Page;
|
||||
if (entry) {
|
||||
entry.recreated = true;
|
||||
page = entry.resolvedPage;
|
||||
}
|
||||
|
||||
if (page) {
|
||||
const callbacks: FragmentCallbacksImplementation = fragment[CALLBACKS];
|
||||
callbacks.frame = frame;
|
||||
callbacks.entry = entry;
|
||||
entry.fragment = fragment;
|
||||
_updateTransitions(entry);
|
||||
} else {
|
||||
throw new Error(`Could not find a page for ${fragmentTag}.`);
|
||||
}
|
||||
}
|
||||
|
||||
export function setActivityCallbacks(activity: androidx.appcompat.app.AppCompatActivity): void {
|
||||
activity[CALLBACKS] = new ActivityCallbacksImplementation();
|
||||
}
|
||||
|
||||
export function setFragmentCallbacks(fragment: androidx.fragment.app.Fragment): void {
|
||||
fragment[CALLBACKS] = new FragmentCallbacksImplementation();
|
||||
}
|
||||
|
35
packages/core/ui/frame/index.d.ts
vendored
35
packages/core/ui/frame/index.d.ts
vendored
@ -1,8 +1,8 @@
|
||||
import { NavigationType, FrameBase } from './frame-common';
|
||||
import type { NavigationType, FrameBase } from './frame-common';
|
||||
import type { NavigatedData, Page } from '../page';
|
||||
import { Observable, EventData } from '../../data/observable';
|
||||
import { Property, View } from '../core/view';
|
||||
import { Transition } from '../transition';
|
||||
import type { Observable, EventData } from '../../data/observable';
|
||||
import type { Property, View } from '../core/view';
|
||||
import type { Transition } from '../transition';
|
||||
|
||||
export * from './frame-interfaces';
|
||||
|
||||
@ -521,6 +521,19 @@ export interface AndroidFrame extends Observable {
|
||||
* @param page The Page instance to search for.
|
||||
*/
|
||||
fragmentForPage(entry: BackstackEntry): any;
|
||||
|
||||
// common properties
|
||||
_resolvedPage?: Page;
|
||||
_currentEntry?: BackstackEntry;
|
||||
_executingContext?: NavigationContext;
|
||||
_inheritStyles?(page: Page): void;
|
||||
isLoaded?: boolean;
|
||||
_styleScope?: any;
|
||||
_addView?(view: View): void;
|
||||
nativeViewProtected?: any /* android.view.View */;
|
||||
_originalBackground?: any /* android.graphics.drawable.Drawable */;
|
||||
backgroundColor?: any;
|
||||
owner?: any;
|
||||
}
|
||||
|
||||
export interface AndroidActivityCallbacks {
|
||||
@ -539,20 +552,6 @@ export interface AndroidActivityCallbacks {
|
||||
onNewIntent(activity: any, intent: any, superSetIntentFunc: Function, superFunc: Function): void;
|
||||
}
|
||||
|
||||
export interface AndroidFragmentCallbacks {
|
||||
onHiddenChanged(fragment: any, hidden: boolean, superFunc: Function): void;
|
||||
onCreateAnimator(fragment: any, transit: number, enter: boolean, nextAnim: number, superFunc: Function): any;
|
||||
onCreate(fragment: any, savedInstanceState: any, superFunc: Function): void;
|
||||
onCreateView(fragment: any, inflater: any, container: any, savedInstanceState: any, superFunc: Function): any;
|
||||
onSaveInstanceState(fragment: any, outState: any, superFunc: Function): void;
|
||||
onDestroyView(fragment: any, superFunc: Function): void;
|
||||
onDestroy(fragment: any, superFunc: Function): void;
|
||||
onPause(fragment: any, superFunc: Function): void;
|
||||
onResume(fragment: any, superFunc: Function): void;
|
||||
onStop(fragment: any, superFunc: Function): void;
|
||||
toStringOverride(fragment: any, superFunc: Function): string;
|
||||
}
|
||||
|
||||
/* tslint:disable */
|
||||
/**
|
||||
* Represents the iOS-specific Frame object, aggregated within the common Frame one.
|
||||
|
@ -2,7 +2,7 @@
|
||||
// Only put platform-agnostic logic here.
|
||||
|
||||
import { CoreTypes } from '../../core-types';
|
||||
import { layout } from '../../utils';
|
||||
import { layout } from '../../utils/layout-helper';
|
||||
import { isCssWideKeyword } from '../core/properties/property-shared';
|
||||
|
||||
function equalsCommon(a: CoreTypes.LengthType, b: CoreTypes.LengthType): boolean;
|
||||
|
@ -4,6 +4,7 @@ import emojiRegex from 'emoji-regex';
|
||||
|
||||
export * from './mainthread-helper';
|
||||
export * from './macrotask-scheduler';
|
||||
export * from './utils-shared';
|
||||
|
||||
export const RESOURCE_PREFIX = 'res://';
|
||||
export const SYSTEM_PREFIX = 'sys://';
|
||||
@ -164,5 +165,3 @@ export function isEmoji(value: string): boolean {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Unicode_Property_Escapes
|
||||
return emojiRegex().test(value);
|
||||
}
|
||||
|
||||
export { getFileExtension } from './utils-shared';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Trace } from '../trace';
|
||||
import { getFileExtension } from './common';
|
||||
import { getFileExtension } from './utils-shared';
|
||||
import { SDK_VERSION } from './constants';
|
||||
import { android as AndroidUtils } from './native-helper';
|
||||
import { topmost } from '../ui/frame/frame-stack';
|
||||
|
296
packages/core/utils/native-helper-for-android.ts
Normal file
296
packages/core/utils/native-helper-for-android.ts
Normal file
@ -0,0 +1,296 @@
|
||||
import { numberHasDecimals, numberIs64Bit } from './types';
|
||||
import { getNativeApp } from '../application/helpers-common';
|
||||
import { androidGetCurrentActivity } from '../application/helpers';
|
||||
import { Trace } from '../trace';
|
||||
import { topmost } from '../ui/frame/frame-stack';
|
||||
|
||||
export function dataDeserialize(nativeData?: any) {
|
||||
if (nativeData === null || typeof nativeData !== 'object') {
|
||||
return nativeData;
|
||||
}
|
||||
let store;
|
||||
|
||||
switch (nativeData.getClass().getName()) {
|
||||
case 'java.lang.String': {
|
||||
return String(nativeData);
|
||||
}
|
||||
|
||||
case 'java.lang.Boolean': {
|
||||
return String(nativeData) === 'true';
|
||||
}
|
||||
|
||||
case 'java.lang.Float':
|
||||
case 'java.lang.Integer':
|
||||
case 'java.lang.Long':
|
||||
case 'java.lang.Double':
|
||||
case 'java.lang.Short': {
|
||||
return Number(nativeData);
|
||||
}
|
||||
|
||||
case 'org.json.JSONArray': {
|
||||
store = [];
|
||||
for (let j = 0; j < nativeData.length(); j++) {
|
||||
store[j] = dataDeserialize(nativeData.get(j));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'org.json.JSONObject': {
|
||||
store = {};
|
||||
const i = nativeData.keys();
|
||||
let key;
|
||||
while (i.hasNext()) {
|
||||
key = i.next();
|
||||
store[key] = dataDeserialize(nativeData.get(key));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'androidx.collection.SimpleArrayMap': {
|
||||
const count = nativeData.size();
|
||||
for (let l = 0; l < count; l++) {
|
||||
const key = nativeData.keyAt(l);
|
||||
store[key] = dataDeserialize(nativeData.get(key));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'androidx.collection.ArrayMap':
|
||||
case 'android.os.Bundle':
|
||||
case 'java.util.HashMap':
|
||||
case 'java.util.Map': {
|
||||
store = {};
|
||||
const keys = nativeData.keySet().toArray();
|
||||
for (let k = 0; k < keys.length; k++) {
|
||||
const key = keys[k];
|
||||
store[key] = dataDeserialize(nativeData.get(key));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
if (typeof nativeData === 'object' && nativeData instanceof java.util.List) {
|
||||
const array = [];
|
||||
const size = nativeData.size();
|
||||
for (let i = 0, n = size; i < n; i++) {
|
||||
array[i] = dataDeserialize(nativeData.get(i));
|
||||
}
|
||||
store = array;
|
||||
} else {
|
||||
store = null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
export function dataSerialize(data?: any, wrapPrimitives?: boolean) {
|
||||
let store;
|
||||
switch (typeof data) {
|
||||
case 'string':
|
||||
case 'boolean': {
|
||||
if (wrapPrimitives) {
|
||||
if (typeof data === 'string') {
|
||||
return new java.lang.String(data);
|
||||
}
|
||||
return new java.lang.Boolean(data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
case 'number': {
|
||||
const hasDecimals = numberHasDecimals(data);
|
||||
if (numberIs64Bit(data)) {
|
||||
if (hasDecimals) {
|
||||
return java.lang.Double.valueOf(data);
|
||||
} else {
|
||||
return java.lang.Long.valueOf(data);
|
||||
}
|
||||
} else {
|
||||
if (hasDecimals) {
|
||||
return java.lang.Float.valueOf(data);
|
||||
} else {
|
||||
return java.lang.Integer.valueOf(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case 'object': {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data instanceof Date) {
|
||||
return new java.util.Date(data.getTime());
|
||||
}
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
store = new java.util.ArrayList();
|
||||
data.forEach((item) => store.add(dataSerialize(item, wrapPrimitives)));
|
||||
return store;
|
||||
}
|
||||
|
||||
if (data.native) {
|
||||
return data.native;
|
||||
}
|
||||
|
||||
store = new java.util.HashMap();
|
||||
Object.keys(data).forEach((key) => store.put(key, dataSerialize(data[key], wrapPrimitives)));
|
||||
return store;
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function getApplicationContext() {
|
||||
return getApplication().getApplicationContext();
|
||||
}
|
||||
export function getCurrentActivity() {
|
||||
return androidGetCurrentActivity();
|
||||
}
|
||||
export function getApplication() {
|
||||
return getNativeApp() as android.app.Application;
|
||||
}
|
||||
export function getResources() {
|
||||
return getApplication().getResources();
|
||||
}
|
||||
let packageName: string;
|
||||
export function getPackageName() {
|
||||
if (!packageName) {
|
||||
packageName = getApplicationContext().getPackageName();
|
||||
}
|
||||
|
||||
return packageName;
|
||||
}
|
||||
|
||||
let inputMethodManager: android.view.inputmethod.InputMethodManager;
|
||||
export function getInputMethodManager(): android.view.inputmethod.InputMethodManager {
|
||||
if (!inputMethodManager) {
|
||||
inputMethodManager = <android.view.inputmethod.InputMethodManager>getApplicationContext().getSystemService(android.content.Context.INPUT_METHOD_SERVICE);
|
||||
}
|
||||
|
||||
return inputMethodManager;
|
||||
}
|
||||
|
||||
export function getWindow() {
|
||||
return getCurrentActivity()?.getWindow();
|
||||
}
|
||||
|
||||
export function showSoftInput(nativeView: android.view.View): void {
|
||||
const inputManager = getInputMethodManager();
|
||||
if (inputManager && nativeView instanceof android.view.View) {
|
||||
inputManager.showSoftInput(nativeView, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
}
|
||||
|
||||
export function dismissSoftInput(nativeView?: android.view.View): void {
|
||||
const inputManager = getInputMethodManager();
|
||||
let windowToken: android.os.IBinder;
|
||||
|
||||
if (nativeView instanceof android.view.View) {
|
||||
if (!nativeView.hasFocus()) {
|
||||
return;
|
||||
}
|
||||
windowToken = nativeView.getWindowToken();
|
||||
} else if (getCurrentActivity() instanceof androidx.appcompat.app.AppCompatActivity) {
|
||||
const modalDialog = (topmost()?._modalParent ?? (topmost()?.modal as any))?._dialogFragment?.getDialog();
|
||||
const window = (modalDialog ?? getCurrentActivity()).getWindow();
|
||||
const decorView = window.getDecorView();
|
||||
if (decorView) {
|
||||
windowToken = decorView.getWindowToken();
|
||||
decorView.requestFocus();
|
||||
} else {
|
||||
windowToken = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (inputManager && windowToken) {
|
||||
inputManager.hideSoftInputFromWindow(windowToken, 0);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace collections {
|
||||
export function stringArrayToStringSet(str: string[]): java.util.HashSet<string> {
|
||||
const hashSet = new java.util.HashSet<string>();
|
||||
if (str !== undefined) {
|
||||
for (const element in str) {
|
||||
hashSet.add('' + str[element]);
|
||||
}
|
||||
}
|
||||
|
||||
return hashSet;
|
||||
}
|
||||
|
||||
export function stringSetToStringArray(stringSet: any): string[] {
|
||||
const arr = [];
|
||||
if (stringSet !== undefined) {
|
||||
const it = stringSet.iterator();
|
||||
while (it.hasNext()) {
|
||||
const element = '' + it.next();
|
||||
arr.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace resources {
|
||||
let attr;
|
||||
const attrCache = new Map<string, number>();
|
||||
|
||||
export function getDrawableId(name) {
|
||||
return getId(':drawable/' + name);
|
||||
}
|
||||
|
||||
export function getStringId(name) {
|
||||
return getId(':string/' + name);
|
||||
}
|
||||
|
||||
export function getId(name: string): number {
|
||||
const resources = getResources();
|
||||
const packageName = getPackageName();
|
||||
const uri = packageName + name;
|
||||
|
||||
return resources.getIdentifier(uri, null, null);
|
||||
}
|
||||
export function getResource(name: string, type?: string): number {
|
||||
return getResources().getIdentifier(name, type, getPackageName());
|
||||
}
|
||||
export function getPaletteColor(name: string, context: android.content.Context): number {
|
||||
if (attrCache.has(name)) {
|
||||
return attrCache.get(name);
|
||||
}
|
||||
|
||||
let result = 0;
|
||||
try {
|
||||
if (!attr) {
|
||||
attr = java.lang.Class.forName('androidx.appcompat.R$attr');
|
||||
}
|
||||
|
||||
let colorID = 0;
|
||||
const field = attr.getField(name);
|
||||
if (field) {
|
||||
colorID = field.getInt(null);
|
||||
}
|
||||
|
||||
if (colorID) {
|
||||
const typedValue = new android.util.TypedValue();
|
||||
context.getTheme().resolveAttribute(colorID, typedValue, true);
|
||||
result = typedValue.data;
|
||||
}
|
||||
} catch (ex) {
|
||||
Trace.write('Cannot get pallete color: ' + name, Trace.categories.Error, Trace.messageType.error);
|
||||
}
|
||||
|
||||
attrCache.set(name, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export function isRealDevice(): boolean {
|
||||
const fingerprint = android.os.Build.FINGERPRINT;
|
||||
|
||||
return fingerprint != null && (fingerprint.indexOf('vbox') > -1 || fingerprint.indexOf('generic') > -1);
|
||||
}
|
@ -1,300 +1,7 @@
|
||||
import { platformCheck } from './platform-check';
|
||||
import { numberHasDecimals, numberIs64Bit } from './types';
|
||||
import { getNativeApp } from '../application/helpers-common';
|
||||
import { androidGetCurrentActivity } from '../application/helpers';
|
||||
import { Trace } from '../trace';
|
||||
import { topmost } from '../ui/frame/frame-stack';
|
||||
|
||||
export function dataDeserialize(nativeData?: any) {
|
||||
if (nativeData === null || typeof nativeData !== 'object') {
|
||||
return nativeData;
|
||||
}
|
||||
let store;
|
||||
|
||||
switch (nativeData.getClass().getName()) {
|
||||
case 'java.lang.String': {
|
||||
return String(nativeData);
|
||||
}
|
||||
|
||||
case 'java.lang.Boolean': {
|
||||
return String(nativeData) === 'true';
|
||||
}
|
||||
|
||||
case 'java.lang.Float':
|
||||
case 'java.lang.Integer':
|
||||
case 'java.lang.Long':
|
||||
case 'java.lang.Double':
|
||||
case 'java.lang.Short': {
|
||||
return Number(nativeData);
|
||||
}
|
||||
|
||||
case 'org.json.JSONArray': {
|
||||
store = [];
|
||||
for (let j = 0; j < nativeData.length(); j++) {
|
||||
store[j] = dataDeserialize(nativeData.get(j));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'org.json.JSONObject': {
|
||||
store = {};
|
||||
const i = nativeData.keys();
|
||||
let key;
|
||||
while (i.hasNext()) {
|
||||
key = i.next();
|
||||
store[key] = dataDeserialize(nativeData.get(key));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'androidx.collection.SimpleArrayMap': {
|
||||
const count = nativeData.size();
|
||||
for (let l = 0; l < count; l++) {
|
||||
const key = nativeData.keyAt(l);
|
||||
store[key] = dataDeserialize(nativeData.get(key));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'androidx.collection.ArrayMap':
|
||||
case 'android.os.Bundle':
|
||||
case 'java.util.HashMap':
|
||||
case 'java.util.Map': {
|
||||
store = {};
|
||||
const keys = nativeData.keySet().toArray();
|
||||
for (let k = 0; k < keys.length; k++) {
|
||||
const key = keys[k];
|
||||
store[key] = dataDeserialize(nativeData.get(key));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
if (typeof nativeData === 'object' && nativeData instanceof java.util.List) {
|
||||
const array = [];
|
||||
const size = nativeData.size();
|
||||
for (let i = 0, n = size; i < n; i++) {
|
||||
array[i] = dataDeserialize(nativeData.get(i));
|
||||
}
|
||||
store = array;
|
||||
} else {
|
||||
store = null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
export function dataSerialize(data?: any, wrapPrimitives?: boolean) {
|
||||
let store;
|
||||
switch (typeof data) {
|
||||
case 'string':
|
||||
case 'boolean': {
|
||||
if (wrapPrimitives) {
|
||||
if (typeof data === 'string') {
|
||||
return new java.lang.String(data);
|
||||
}
|
||||
return new java.lang.Boolean(data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
case 'number': {
|
||||
const hasDecimals = numberHasDecimals(data);
|
||||
if (numberIs64Bit(data)) {
|
||||
if (hasDecimals) {
|
||||
return java.lang.Double.valueOf(data);
|
||||
} else {
|
||||
return java.lang.Long.valueOf(data);
|
||||
}
|
||||
} else {
|
||||
if (hasDecimals) {
|
||||
return java.lang.Float.valueOf(data);
|
||||
} else {
|
||||
return java.lang.Integer.valueOf(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case 'object': {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data instanceof Date) {
|
||||
return new java.util.Date(data.getTime());
|
||||
}
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
store = new java.util.ArrayList();
|
||||
data.forEach((item) => store.add(dataSerialize(item, wrapPrimitives)));
|
||||
return store;
|
||||
}
|
||||
|
||||
if (data.native) {
|
||||
return data.native;
|
||||
}
|
||||
|
||||
store = new java.util.HashMap();
|
||||
Object.keys(data).forEach((key) => store.put(key, dataSerialize(data[key], wrapPrimitives)));
|
||||
return store;
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getApplicationContext() {
|
||||
return getApplication().getApplicationContext();
|
||||
}
|
||||
function getCurrentActivity() {
|
||||
return androidGetCurrentActivity();
|
||||
}
|
||||
function getApplication() {
|
||||
return getNativeApp() as globalAndroid.app.Application;
|
||||
}
|
||||
function getResources() {
|
||||
return getApplication().getResources();
|
||||
}
|
||||
let packageName: string;
|
||||
function getPackageName() {
|
||||
if (!packageName) {
|
||||
packageName = getApplicationContext().getPackageName();
|
||||
}
|
||||
|
||||
return packageName;
|
||||
}
|
||||
|
||||
let inputMethodManager: globalAndroid.view.inputmethod.InputMethodManager;
|
||||
function getInputMethodManager(): globalAndroid.view.inputmethod.InputMethodManager {
|
||||
if (!inputMethodManager) {
|
||||
inputMethodManager = <globalAndroid.view.inputmethod.InputMethodManager>getApplicationContext().getSystemService(globalAndroid.content.Context.INPUT_METHOD_SERVICE);
|
||||
}
|
||||
|
||||
return inputMethodManager;
|
||||
}
|
||||
|
||||
export function getWindow() {
|
||||
return getCurrentActivity()?.getWindow();
|
||||
}
|
||||
|
||||
function showSoftInput(nativeView: globalAndroid.view.View): void {
|
||||
const inputManager = getInputMethodManager();
|
||||
if (inputManager && nativeView instanceof globalAndroid.view.View) {
|
||||
inputManager.showSoftInput(nativeView, globalAndroid.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
}
|
||||
|
||||
function dismissSoftInput(nativeView?: globalAndroid.view.View): void {
|
||||
const inputManager = getInputMethodManager();
|
||||
let windowToken: globalAndroid.os.IBinder;
|
||||
|
||||
if (nativeView instanceof globalAndroid.view.View) {
|
||||
if (!nativeView.hasFocus()) {
|
||||
return;
|
||||
}
|
||||
windowToken = nativeView.getWindowToken();
|
||||
} else if (getCurrentActivity() instanceof androidx.appcompat.app.AppCompatActivity) {
|
||||
const modalDialog = (topmost()?._modalParent ?? (topmost()?.modal as any))?._dialogFragment?.getDialog();
|
||||
const window = (modalDialog ?? getCurrentActivity()).getWindow();
|
||||
const decorView = window.getDecorView();
|
||||
if (decorView) {
|
||||
windowToken = decorView.getWindowToken();
|
||||
decorView.requestFocus();
|
||||
} else {
|
||||
windowToken = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (inputManager && windowToken) {
|
||||
inputManager.hideSoftInputFromWindow(windowToken, 0);
|
||||
}
|
||||
}
|
||||
|
||||
namespace collections {
|
||||
export function stringArrayToStringSet(str: string[]): java.util.HashSet<string> {
|
||||
const hashSet = new java.util.HashSet<string>();
|
||||
if (str !== undefined) {
|
||||
for (const element in str) {
|
||||
hashSet.add('' + str[element]);
|
||||
}
|
||||
}
|
||||
|
||||
return hashSet;
|
||||
}
|
||||
|
||||
export function stringSetToStringArray(stringSet: any): string[] {
|
||||
const arr = [];
|
||||
if (stringSet !== undefined) {
|
||||
const it = stringSet.iterator();
|
||||
while (it.hasNext()) {
|
||||
const element = '' + it.next();
|
||||
arr.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
||||
namespace resources {
|
||||
let attr;
|
||||
const attrCache = new Map<string, number>();
|
||||
|
||||
export function getDrawableId(name) {
|
||||
return getId(':drawable/' + name);
|
||||
}
|
||||
|
||||
export function getStringId(name) {
|
||||
return getId(':string/' + name);
|
||||
}
|
||||
|
||||
export function getId(name: string): number {
|
||||
const resources = getResources();
|
||||
const packageName = getPackageName();
|
||||
const uri = packageName + name;
|
||||
|
||||
return resources.getIdentifier(uri, null, null);
|
||||
}
|
||||
export function getResource(name: string, type?: string): number {
|
||||
return getResources().getIdentifier(name, type, getPackageName());
|
||||
}
|
||||
export function getPaletteColor(name: string, context: globalAndroid.content.Context): number {
|
||||
if (attrCache.has(name)) {
|
||||
return attrCache.get(name);
|
||||
}
|
||||
|
||||
let result = 0;
|
||||
try {
|
||||
if (!attr) {
|
||||
attr = java.lang.Class.forName('androidx.appcompat.R$attr');
|
||||
}
|
||||
|
||||
let colorID = 0;
|
||||
const field = attr.getField(name);
|
||||
if (field) {
|
||||
colorID = field.getInt(null);
|
||||
}
|
||||
|
||||
if (colorID) {
|
||||
const typedValue = new globalAndroid.util.TypedValue();
|
||||
context.getTheme().resolveAttribute(colorID, typedValue, true);
|
||||
result = typedValue.data;
|
||||
}
|
||||
} catch (ex) {
|
||||
Trace.write('Cannot get pallete color: ' + name, Trace.categories.Error, Trace.messageType.error);
|
||||
}
|
||||
|
||||
attrCache.set(name, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export function isRealDevice(): boolean {
|
||||
const fingerprint = globalAndroid.os.Build.FINGERPRINT;
|
||||
|
||||
return fingerprint != null && (fingerprint.indexOf('vbox') > -1 || fingerprint.indexOf('generic') > -1);
|
||||
}
|
||||
// importing this helper as a separate file avoids "android" symbol clash with the global android object
|
||||
import { resources, getApplication, getCurrentActivity, getApplicationContext, getWindow, getResources, getPackageName, getInputMethodManager, showSoftInput, dismissSoftInput } from './native-helper-for-android';
|
||||
|
||||
export const android = {
|
||||
resources,
|
||||
|
@ -1,6 +1,3 @@
|
||||
// Shared helpers and types for utils, used by both native-helper and common.
|
||||
// Only put platform-agnostic logic here.
|
||||
|
||||
export function getFileExtension(path: string): string {
|
||||
if (!path) {
|
||||
return '';
|
||||
@ -8,5 +5,3 @@ export function getFileExtension(path: string): string {
|
||||
const index = path.lastIndexOf('.');
|
||||
return index !== -1 ? path.substring(index + 1) : '';
|
||||
}
|
||||
|
||||
// Add more shared helpers/types/constants as needed.
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as http from '../http';
|
||||
import * as types from '../utils/types';
|
||||
import type { HttpRequestOptions, HttpResponse } from '../http';
|
||||
import { request } from '../http';
|
||||
import { isString, isFunction } from '../utils/types';
|
||||
import { Trace } from '../trace';
|
||||
|
||||
namespace XMLHttpRequestResponseType {
|
||||
@ -24,7 +25,7 @@ export class XMLHttpRequest {
|
||||
public onloadstart: (...args: any[]) => void;
|
||||
public onprogress: (...args: any[]) => void;
|
||||
|
||||
private _options: http.HttpRequestOptions;
|
||||
private _options: HttpRequestOptions;
|
||||
private _readyState: number;
|
||||
private _status: number;
|
||||
private _response: any;
|
||||
@ -64,7 +65,7 @@ export class XMLHttpRequest {
|
||||
throw new Error("Failed to read the 'responseText' property from 'XMLHttpRequest': " + "The value is only accessible if the object's 'responseType' is '' or 'text' " + `(was '${this._responseType}').`);
|
||||
}
|
||||
|
||||
if (types.isFunction(this._responseTextReader)) {
|
||||
if (isFunction(this._responseTextReader)) {
|
||||
return this._responseTextReader();
|
||||
}
|
||||
|
||||
@ -103,7 +104,7 @@ export class XMLHttpRequest {
|
||||
this._readyState = this.UNSENT;
|
||||
}
|
||||
|
||||
private _loadResponse(r: http.HttpResponse) {
|
||||
private _loadResponse(r: HttpResponse) {
|
||||
this._status = r.statusCode;
|
||||
this._headers = r.headers;
|
||||
this._setReadyState(this.HEADERS_RECEIVED);
|
||||
@ -135,7 +136,7 @@ export class XMLHttpRequest {
|
||||
}
|
||||
|
||||
private emitEvent(eventName: string, ...args: Array<any>) {
|
||||
if (types.isFunction(this['on' + eventName])) {
|
||||
if (isFunction(this['on' + eventName])) {
|
||||
this['on' + eventName](...args);
|
||||
}
|
||||
|
||||
@ -188,15 +189,15 @@ export class XMLHttpRequest {
|
||||
}
|
||||
|
||||
public open(method: string, url: string, async?: boolean, user?: string, password?: string) {
|
||||
if (types.isString(method) && types.isString(url)) {
|
||||
if (isString(method) && isString(url)) {
|
||||
this._options = { url: url, method: method };
|
||||
this._options.headers = {};
|
||||
|
||||
if (types.isString(user)) {
|
||||
if (isString(user)) {
|
||||
this._options.headers['user'] = user;
|
||||
}
|
||||
|
||||
if (types.isString(password)) {
|
||||
if (isString(password)) {
|
||||
this._options.headers['password'] = password;
|
||||
}
|
||||
|
||||
@ -232,7 +233,7 @@ export class XMLHttpRequest {
|
||||
throw new Error("Failed to execute 'send' on 'XMLHttpRequest': " + "The object's state must be OPENED.");
|
||||
}
|
||||
|
||||
if (types.isString(data) && this._options.method !== 'GET') {
|
||||
if (isString(data) && this._options.method !== 'GET') {
|
||||
//The Android Java HTTP lib throws an exception if we provide a
|
||||
//a request body for GET requests, so we avoid doing that.
|
||||
//Browser implementations silently ignore it as well.
|
||||
@ -250,8 +251,7 @@ export class XMLHttpRequest {
|
||||
|
||||
this.emitEvent('loadstart');
|
||||
|
||||
http
|
||||
.request(this._options)
|
||||
request(this._options)
|
||||
.then((r) => {
|
||||
if (!this._errorFlag && this._sendFlag) {
|
||||
this._loadResponse(r);
|
||||
@ -269,7 +269,7 @@ export class XMLHttpRequest {
|
||||
throw new Error("Failed to execute 'setRequestHeader' on 'XMLHttpRequest': " + "The object's state must be OPENED.");
|
||||
}
|
||||
|
||||
if (types.isString(header) && types.isString(value)) {
|
||||
if (isString(header) && isString(value)) {
|
||||
this._options.headers[header] = value;
|
||||
}
|
||||
}
|
||||
@ -289,7 +289,7 @@ export class XMLHttpRequest {
|
||||
}
|
||||
|
||||
public getResponseHeader(header: string): string {
|
||||
if (types.isString(header) && this._readyState > 1 && this._headers && !this._errorFlag) {
|
||||
if (isString(header) && this._readyState > 1 && this._headers && !this._errorFlag) {
|
||||
header = header.toLowerCase();
|
||||
for (const i in this._headers) {
|
||||
if (i.toLowerCase() === header) {
|
||||
@ -559,7 +559,7 @@ export class FileReader {
|
||||
}
|
||||
|
||||
private emitEvent(eventName: string, ...args: Array<any>) {
|
||||
if (types.isFunction(this['on' + eventName])) {
|
||||
if (isFunction(this['on' + eventName])) {
|
||||
this['on' + eventName](...args);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user