mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-18 13:51:27 +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');
|
var file = documents.getFile('Test.txt');
|
||||||
// Getting the file name "Test.txt".
|
// Getting the file name "Test.txt".
|
||||||
var fileName = file.name;
|
var fileName = file.name;
|
||||||
// Getting the file extension ".txt".
|
// Getting the file extension "txt".
|
||||||
var fileExtension = file.extension;
|
var fileExtension = file.extension;
|
||||||
// >> (hide)
|
// >> (hide)
|
||||||
TKUnit.assert(fileName === 'Test.txt', 'Wrong file name.');
|
TKUnit.assert(fileName === 'Test.txt', 'Wrong file name.');
|
||||||
TKUnit.assert(fileExtension === '.txt', 'Wrong extension.');
|
TKUnit.assert(fileExtension === 'txt', 'Wrong extension.');
|
||||||
file.remove();
|
file.remove();
|
||||||
// << (hide)
|
// << (hide)
|
||||||
// << file-system-extension
|
// << file-system-extension
|
||||||
@ -633,7 +633,7 @@ export function test_FSEntity_Properties() {
|
|||||||
var documents = fs.knownFolders.documents();
|
var documents = fs.knownFolders.documents();
|
||||||
var file = documents.getFile('Test_File.txt');
|
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.isLocked === false, 'FileEntity.isLocked not working.');
|
||||||
TKUnit.assert(file.lastModified instanceof Date, 'FileEntity.lastModified not working.');
|
TKUnit.assert(file.lastModified instanceof Date, 'FileEntity.lastModified not working.');
|
||||||
TKUnit.assert(file.size === 0, 'FileEntity.size not working.');
|
TKUnit.assert(file.size === 0, 'FileEntity.size not working.');
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import * as textModule from '../text';
|
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 { SDK_VERSION } from '../utils/constants';
|
||||||
|
|
||||||
import type { IFileSystemAccess } from './file-system-access';
|
import type { IFileSystemAccess } from './file-system-access';
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { encoding as textEncoding } from '../text';
|
import { encoding as textEncoding } from '../text';
|
||||||
import { ios as iosUtils } from '../utils';
|
import { ios as iosUtils } from '../utils';
|
||||||
|
import { getFileExtension } from '../utils/utils-shared';
|
||||||
|
|
||||||
// TODO: Implement all the APIs receiving callback using async blocks
|
// TODO: Implement all the APIs receiving callback using async blocks
|
||||||
// TODO: Check whether we need try/catch blocks for the iOS implementation
|
// TODO: Check whether we need try/catch blocks for the iOS implementation
|
||||||
@ -611,24 +612,8 @@ export class FileSystemAccess {
|
|||||||
return url.path;
|
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 {
|
public getFileExtension(path: string): string {
|
||||||
// TODO [For Panata]: The definitions currently specify "any" as a return value of this method
|
return getFileExtension(path);
|
||||||
//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 '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private deleteEntity(path: string, onError?: (error: any) => any) {
|
private deleteEntity(path: string, onError?: (error: any) => any) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// imported for definition purposes only
|
// imported for definition purposes only
|
||||||
import * as httpModule from '../../http';
|
import type { Headers, HttpResponse, HttpRequestOptions } from '../../http';
|
||||||
import { ImageSource } from '../../image-source';
|
import { ImageSource } from '../../image-source';
|
||||||
import { Screen } from '../../platform';
|
import { Screen } from '../../platform/screen';
|
||||||
import { File } from '../../file-system';
|
import { File } from '../../file-system';
|
||||||
import { HttpResponseEncoding } from '../http-interfaces';
|
import { HttpResponseEncoding } from '../http-interfaces';
|
||||||
import { getFilenameFromUrl } from './http-request-common';
|
import { getFilenameFromUrl } from './http-request-common';
|
||||||
@ -51,7 +51,7 @@ function onRequestComplete(requestId: number, result: org.nativescript.widgets.A
|
|||||||
}
|
}
|
||||||
|
|
||||||
// read the headers
|
// read the headers
|
||||||
const headers: httpModule.Headers = {};
|
const headers: Headers = {};
|
||||||
if (result.headers) {
|
if (result.headers) {
|
||||||
const jHeaders = result.headers;
|
const jHeaders = result.headers;
|
||||||
const length = jHeaders.size();
|
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') {
|
if (typeof options.url !== 'string') {
|
||||||
throw new Error('Http request must provide a valid url.');
|
throw new Error('Http request must provide a valid url.');
|
||||||
}
|
}
|
||||||
@ -224,13 +224,13 @@ function buildJavaOptions(options: httpModule.HttpRequestOptions) {
|
|||||||
return javaOptions;
|
return javaOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function request(options: httpModule.HttpRequestOptions): Promise<httpModule.HttpResponse> {
|
export function request(options: HttpRequestOptions): Promise<HttpResponse> {
|
||||||
if (options === undefined || options === null) {
|
if (options === undefined || options === null) {
|
||||||
// TODO: Shouldn't we throw an error here - defensive programming
|
// TODO: Shouldn't we throw an error here - defensive programming
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise<httpModule.HttpResponse>((resolve, reject) => {
|
return new Promise<HttpResponse>((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
// initialize the options
|
// initialize the options
|
||||||
const javaOptions = buildJavaOptions(options);
|
const javaOptions = buildJavaOptions(options);
|
||||||
@ -289,7 +289,7 @@ function decodeResponse(raw: any, encoding?: HttpResponseEncoding) {
|
|||||||
return raw.toString(charsetName);
|
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]) {
|
if (!headers[key]) {
|
||||||
headers[key] = value;
|
headers[key] = value;
|
||||||
} else if (Array.isArray(headers[key])) {
|
} else if (Array.isArray(headers[key])) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { SDK_VERSION } from '../../utils/constants';
|
import { SDK_VERSION } from '../../utils/constants';
|
||||||
import { isRealDevice } from '../../utils';
|
import { isRealDevice } from '../../utils/native-helper';
|
||||||
import * as types from '../../utils/types';
|
import * as types from '../../utils/types';
|
||||||
import * as domainDebugger from '../../debugger';
|
import * as domainDebugger from '../../debugger';
|
||||||
import { getFilenameFromUrl } from './http-request-common';
|
import { getFilenameFromUrl } from './http-request-common';
|
||||||
|
@ -63,7 +63,7 @@
|
|||||||
// module.exports.EasySAXParser = EasySAXParser;
|
// module.exports.EasySAXParser = EasySAXParser;
|
||||||
// };
|
// };
|
||||||
|
|
||||||
export function EasySAXParser() {
|
function EasySAXParser() {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
if (!this) return null;
|
if (!this) return null;
|
||||||
@ -780,3 +780,5 @@ EasySAXParser.prototype.parse = function(xml) {
|
|||||||
j += 1;
|
j += 1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export { EasySAXParser };
|
@ -1,5 +1,5 @@
|
|||||||
import { EventData, Observable } from '../data/observable';
|
import { EventData, Observable } from '../data/observable';
|
||||||
import { Screen } from '../platform';
|
import { Screen } from '../platform/screen';
|
||||||
import { getApplicationProperties, toggleApplicationEventListeners } from '../application/helpers-common';
|
import { getApplicationProperties, toggleApplicationEventListeners } from '../application/helpers-common';
|
||||||
import type { ApplicationEventData } from '../application/application-interfaces';
|
import type { ApplicationEventData } from '../application/application-interfaces';
|
||||||
import { matchQuery, MediaQueryType } from '../css-mediaquery';
|
import { matchQuery, MediaQueryType } from '../css-mediaquery';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { isEmbedded, getEmbeddedView } from '../embedding';
|
import { isEmbedded, getEmbeddedView } from '../embedding';
|
||||||
import { setFragmentCallbacks } from '.';
|
import { setFragmentCallbacks } from './frame-helper-for-android';
|
||||||
|
|
||||||
declare const com: any;
|
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 type { ExpandedEntry } from './fragment.transitions.android';
|
||||||
import { ensureFragmentClass, fragmentClass } from './fragment';
|
import { ensureFragmentClass, fragmentClass } from './fragment';
|
||||||
import { getAppMainEntry } from '../../application/helpers-common';
|
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 { AndroidActivityBackPressedEventData, AndroidActivityNewIntentEventData, AndroidActivityRequestPermissionsEventData, AndroidActivityResultEventData } from '../../application/application-interfaces';
|
||||||
import { Application } from '../../application/application';
|
import { Application } from '../../application/application';
|
||||||
import { isEmbedded, setEmbeddedView } from '../embedding';
|
import { isEmbedded, setEmbeddedView } from '../embedding';
|
||||||
|
import { CALLBACKS, FRAMEID, framesCache, setFragmentCallbacks } from './frame-helper-for-android';
|
||||||
|
|
||||||
export * from './frame-common';
|
export * from './frame-common';
|
||||||
export { setFragmentClass } from './fragment';
|
export { setFragmentClass } from './fragment';
|
||||||
|
|
||||||
const INTENT_EXTRA = 'com.tns.activity';
|
const INTENT_EXTRA = 'com.tns.activity';
|
||||||
const FRAMEID = '_frameId';
|
|
||||||
const CALLBACKS = '_callbacks';
|
|
||||||
|
|
||||||
const ownerSymbol = Symbol('_owner');
|
const ownerSymbol = Symbol('_owner');
|
||||||
|
|
||||||
@ -648,7 +646,6 @@ function restoreTransitionState(entry: BackstackEntry, snapshot: TransitionState
|
|||||||
}
|
}
|
||||||
|
|
||||||
let framesCounter = 0;
|
let framesCounter = 0;
|
||||||
const framesCache = new Array<WeakRef<AndroidFrame>>();
|
|
||||||
|
|
||||||
class AndroidFrame extends Observable implements AndroidFrameDefinition {
|
class AndroidFrame extends Observable implements AndroidFrameDefinition {
|
||||||
public rootViewGroup: android.view.ViewGroup;
|
public rootViewGroup: android.view.ViewGroup;
|
||||||
@ -760,18 +757,6 @@ function startActivity(activity: androidx.appcompat.app.AppCompatActivity, frame
|
|||||||
activity.startActivity(intent);
|
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 activityRootViewsMap = new Map<number, WeakRef<View>>();
|
||||||
const ROOT_VIEW_ID_EXTRA = 'com.tns.activity.rootViewId';
|
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 {
|
export function setActivityCallbacks(activity: androidx.appcompat.app.AppCompatActivity): void {
|
||||||
activity[CALLBACKS] = new ActivityCallbacksImplementation();
|
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 type { NavigatedData, Page } from '../page';
|
||||||
import { Observable, EventData } from '../../data/observable';
|
import type { Observable, EventData } from '../../data/observable';
|
||||||
import { Property, View } from '../core/view';
|
import type { Property, View } from '../core/view';
|
||||||
import { Transition } from '../transition';
|
import type { Transition } from '../transition';
|
||||||
|
|
||||||
export * from './frame-interfaces';
|
export * from './frame-interfaces';
|
||||||
|
|
||||||
@ -521,6 +521,19 @@ export interface AndroidFrame extends Observable {
|
|||||||
* @param page The Page instance to search for.
|
* @param page The Page instance to search for.
|
||||||
*/
|
*/
|
||||||
fragmentForPage(entry: BackstackEntry): any;
|
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 {
|
export interface AndroidActivityCallbacks {
|
||||||
@ -539,20 +552,6 @@ export interface AndroidActivityCallbacks {
|
|||||||
onNewIntent(activity: any, intent: any, superSetIntentFunc: Function, superFunc: Function): void;
|
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 */
|
/* tslint:disable */
|
||||||
/**
|
/**
|
||||||
* Represents the iOS-specific Frame object, aggregated within the common Frame one.
|
* Represents the iOS-specific Frame object, aggregated within the common Frame one.
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// Only put platform-agnostic logic here.
|
// Only put platform-agnostic logic here.
|
||||||
|
|
||||||
import { CoreTypes } from '../../core-types';
|
import { CoreTypes } from '../../core-types';
|
||||||
import { layout } from '../../utils';
|
import { layout } from '../../utils/layout-helper';
|
||||||
import { isCssWideKeyword } from '../core/properties/property-shared';
|
import { isCssWideKeyword } from '../core/properties/property-shared';
|
||||||
|
|
||||||
function equalsCommon(a: CoreTypes.LengthType, b: CoreTypes.LengthType): boolean;
|
function equalsCommon(a: CoreTypes.LengthType, b: CoreTypes.LengthType): boolean;
|
||||||
|
@ -4,6 +4,7 @@ import emojiRegex from 'emoji-regex';
|
|||||||
|
|
||||||
export * from './mainthread-helper';
|
export * from './mainthread-helper';
|
||||||
export * from './macrotask-scheduler';
|
export * from './macrotask-scheduler';
|
||||||
|
export * from './utils-shared';
|
||||||
|
|
||||||
export const RESOURCE_PREFIX = 'res://';
|
export const RESOURCE_PREFIX = 'res://';
|
||||||
export const SYSTEM_PREFIX = 'sys://';
|
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
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Unicode_Property_Escapes
|
||||||
return emojiRegex().test(value);
|
return emojiRegex().test(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getFileExtension } from './utils-shared';
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Trace } from '../trace';
|
import { Trace } from '../trace';
|
||||||
import { getFileExtension } from './common';
|
import { getFileExtension } from './utils-shared';
|
||||||
import { SDK_VERSION } from './constants';
|
import { SDK_VERSION } from './constants';
|
||||||
import { android as AndroidUtils } from './native-helper';
|
import { android as AndroidUtils } from './native-helper';
|
||||||
import { topmost } from '../ui/frame/frame-stack';
|
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 { 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) {
|
// importing this helper as a separate file avoids "android" symbol clash with the global android object
|
||||||
if (nativeData === null || typeof nativeData !== 'object') {
|
import { resources, getApplication, getCurrentActivity, getApplicationContext, getWindow, getResources, getPackageName, getInputMethodManager, showSoftInput, dismissSoftInput } from './native-helper-for-android';
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const android = {
|
export const android = {
|
||||||
resources,
|
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 {
|
export function getFileExtension(path: string): string {
|
||||||
if (!path) {
|
if (!path) {
|
||||||
return '';
|
return '';
|
||||||
@ -8,5 +5,3 @@ export function getFileExtension(path: string): string {
|
|||||||
const index = path.lastIndexOf('.');
|
const index = path.lastIndexOf('.');
|
||||||
return index !== -1 ? path.substring(index + 1) : '';
|
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 type { HttpRequestOptions, HttpResponse } from '../http';
|
||||||
import * as types from '../utils/types';
|
import { request } from '../http';
|
||||||
|
import { isString, isFunction } from '../utils/types';
|
||||||
import { Trace } from '../trace';
|
import { Trace } from '../trace';
|
||||||
|
|
||||||
namespace XMLHttpRequestResponseType {
|
namespace XMLHttpRequestResponseType {
|
||||||
@ -24,7 +25,7 @@ export class XMLHttpRequest {
|
|||||||
public onloadstart: (...args: any[]) => void;
|
public onloadstart: (...args: any[]) => void;
|
||||||
public onprogress: (...args: any[]) => void;
|
public onprogress: (...args: any[]) => void;
|
||||||
|
|
||||||
private _options: http.HttpRequestOptions;
|
private _options: HttpRequestOptions;
|
||||||
private _readyState: number;
|
private _readyState: number;
|
||||||
private _status: number;
|
private _status: number;
|
||||||
private _response: any;
|
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}').`);
|
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();
|
return this._responseTextReader();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +104,7 @@ export class XMLHttpRequest {
|
|||||||
this._readyState = this.UNSENT;
|
this._readyState = this.UNSENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _loadResponse(r: http.HttpResponse) {
|
private _loadResponse(r: HttpResponse) {
|
||||||
this._status = r.statusCode;
|
this._status = r.statusCode;
|
||||||
this._headers = r.headers;
|
this._headers = r.headers;
|
||||||
this._setReadyState(this.HEADERS_RECEIVED);
|
this._setReadyState(this.HEADERS_RECEIVED);
|
||||||
@ -135,7 +136,7 @@ export class XMLHttpRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private emitEvent(eventName: string, ...args: Array<any>) {
|
private emitEvent(eventName: string, ...args: Array<any>) {
|
||||||
if (types.isFunction(this['on' + eventName])) {
|
if (isFunction(this['on' + eventName])) {
|
||||||
this['on' + eventName](...args);
|
this['on' + eventName](...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,15 +189,15 @@ export class XMLHttpRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public open(method: string, url: string, async?: boolean, user?: string, password?: string) {
|
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 = { url: url, method: method };
|
||||||
this._options.headers = {};
|
this._options.headers = {};
|
||||||
|
|
||||||
if (types.isString(user)) {
|
if (isString(user)) {
|
||||||
this._options.headers['user'] = user;
|
this._options.headers['user'] = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (types.isString(password)) {
|
if (isString(password)) {
|
||||||
this._options.headers['password'] = 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.");
|
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
|
//The Android Java HTTP lib throws an exception if we provide a
|
||||||
//a request body for GET requests, so we avoid doing that.
|
//a request body for GET requests, so we avoid doing that.
|
||||||
//Browser implementations silently ignore it as well.
|
//Browser implementations silently ignore it as well.
|
||||||
@ -250,8 +251,7 @@ export class XMLHttpRequest {
|
|||||||
|
|
||||||
this.emitEvent('loadstart');
|
this.emitEvent('loadstart');
|
||||||
|
|
||||||
http
|
request(this._options)
|
||||||
.request(this._options)
|
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
if (!this._errorFlag && this._sendFlag) {
|
if (!this._errorFlag && this._sendFlag) {
|
||||||
this._loadResponse(r);
|
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.");
|
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;
|
this._options.headers[header] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -289,7 +289,7 @@ export class XMLHttpRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getResponseHeader(header: string): string {
|
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();
|
header = header.toLowerCase();
|
||||||
for (const i in this._headers) {
|
for (const i in this._headers) {
|
||||||
if (i.toLowerCase() === header) {
|
if (i.toLowerCase() === header) {
|
||||||
@ -559,7 +559,7 @@ export class FileReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private emitEvent(eventName: string, ...args: Array<any>) {
|
private emitEvent(eventName: string, ...args: Array<any>) {
|
||||||
if (types.isFunction(this['on' + eventName])) {
|
if (isFunction(this['on' + eventName])) {
|
||||||
this['on' + eventName](...args);
|
this['on' + eventName](...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user