diff --git a/packages/core/accessibility/index.android.ts b/packages/core/accessibility/index.android.ts index cadd85e99..db36c9816 100644 --- a/packages/core/accessibility/index.android.ts +++ b/packages/core/accessibility/index.android.ts @@ -1,7 +1,7 @@ import { Application, ApplicationEventData } from '../application'; import { Trace } from '../trace'; import { SDK_VERSION } from '../utils/constants'; -import { resources } from '../utils/android'; +import { android as androidUtils } from '../utils'; import type { View } from '../ui/core/view'; import { GestureTypes } from '../ui/gestures'; import { notifyAccessibilityFocusState } from './accessibility-common'; @@ -168,7 +168,7 @@ function ensureNativeClasses() { } // Set resource id that can be used with test frameworks without polluting the content description. - const id = host.getTag(resources.getId(`:id/nativescript_accessibility_id`)); + const id = host.getTag(androidUtils.resources.getId(`:id/nativescript_accessibility_id`)); if (id != null) { info.setViewIdResourceName(id); } diff --git a/packages/core/file-system/file-system-access.ios.ts b/packages/core/file-system/file-system-access.ios.ts index 05925eb8b..295cf3ba4 100644 --- a/packages/core/file-system/file-system-access.ios.ts +++ b/packages/core/file-system/file-system-access.ios.ts @@ -1,5 +1,5 @@ import { encoding as textEncoding } from '../text'; -import { iOSNativeHelper } from '../utils'; +import { ios as iosUtils } from '../utils'; // TODO: Implement all the APIs receiving callback using async blocks // TODO: Check whether we need try/catch blocks for the iOS implementation @@ -257,7 +257,7 @@ export class FileSystemAccess { } public getCurrentAppPath(): string { - return iOSNativeHelper.getCurrentAppPath(); + return iosUtils.getCurrentAppPath(); } public copy = this.copySync.bind(this); @@ -700,7 +700,7 @@ export class FileSystemAccess { } public joinPaths(paths: string[]): string { - return iOSNativeHelper.joinPaths(...paths); + return iosUtils.joinPaths(...paths); } } diff --git a/packages/core/http/http-request/index.ios.ts b/packages/core/http/http-request/index.ios.ts index 27417b868..4e004d64c 100644 --- a/packages/core/http/http-request/index.ios.ts +++ b/packages/core/http/http-request/index.ios.ts @@ -4,7 +4,7 @@ import * as imageSourceModule from '../../image-source'; import * as fsModule from '../../file-system'; import { SDK_VERSION } from '../../utils/constants'; -import { isRealDevice } from '../../utils/ios'; +import { isRealDevice } from '../../utils'; import * as types from '../../utils/types'; import * as domainDebugger from '../../debugger'; import { getFilenameFromUrl } from './http-request-common'; diff --git a/packages/core/ui/action-bar/index.ios.ts b/packages/core/ui/action-bar/index.ios.ts index 6ed96382c..ebb3cc1c1 100644 --- a/packages/core/ui/action-bar/index.ios.ts +++ b/packages/core/ui/action-bar/index.ios.ts @@ -7,13 +7,12 @@ import { LinearGradient } from '../styling/linear-gradient'; import { colorProperty, backgroundInternalProperty, backgroundColorProperty, backgroundImageProperty } from '../styling/style-properties'; import { ios as iosViewUtils } from '../utils'; import { ImageSource } from '../../image-source'; -import { layout, iOSNativeHelper, isFontIconURI } from '../../utils'; +import { layout, isFontIconURI } from '../../utils'; import { SDK_VERSION } from '../../utils/constants'; import { accessibilityHintProperty, accessibilityLabelProperty, accessibilityLanguageProperty, accessibilityValueProperty } from '../../accessibility/accessibility-properties'; export * from './action-bar-common'; -const majorVersion = iOSNativeHelper.MajorVersion; const UNSPECIFIED = layout.makeMeasureSpec(0, layout.UNSPECIFIED); interface NSUINavigationBar extends UINavigationBar { @@ -271,7 +270,7 @@ export class ActionBar extends ActionBarBase { // show the one from the old page but the new page will still be visible (because we canceled EdgeBackSwipe gesutre) // Consider moving this to new method and call it from - navigationControllerDidShowViewControllerAnimated. const image = img ? img.imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal) : null; - if (majorVersion >= 15) { + if (SDK_VERSION >= 15) { const appearance = this._getAppearance(navigationBar); appearance.setBackIndicatorImageTransitionMaskImage(image, image); this._updateAppearance(navigationBar, appearance); @@ -378,7 +377,7 @@ export class ActionBar extends ActionBarBase { } if (color) { const titleTextColor = NSDictionary.dictionaryWithObjectForKey(color.ios, NSForegroundColorAttributeName); - if (majorVersion >= 15) { + if (SDK_VERSION >= 15) { const appearance = this._getAppearance(navBar); appearance.titleTextAttributes = titleTextColor; } @@ -398,7 +397,7 @@ export class ActionBar extends ActionBarBase { } const nativeColor = color instanceof Color ? color.ios : color; - if (__VISIONOS__ || majorVersion >= 15) { + if (__VISIONOS__ || SDK_VERSION >= 15) { const appearance = this._getAppearance(navBar); // appearance.configureWithOpaqueBackground(); appearance.backgroundColor = nativeColor; @@ -416,7 +415,7 @@ export class ActionBar extends ActionBarBase { let color: UIColor; - if (__VISIONOS__ || majorVersion >= 15) { + if (__VISIONOS__ || SDK_VERSION >= 15) { const appearance = this._getAppearance(navBar); color = appearance.backgroundColor; } else { @@ -432,7 +431,7 @@ export class ActionBar extends ActionBarBase { return; } - if (__VISIONOS__ || majorVersion >= 15) { + if (__VISIONOS__ || SDK_VERSION >= 15) { const appearance = this._getAppearance(navBar); // appearance.configureWithOpaqueBackground(); appearance.backgroundImage = image; @@ -456,7 +455,7 @@ export class ActionBar extends ActionBarBase { let image: UIImage; - if (__VISIONOS__ || majorVersion >= 15) { + if (__VISIONOS__ || SDK_VERSION >= 15) { const appearance = this._getAppearance(navBar); image = appearance.backgroundImage; } else { @@ -517,7 +516,7 @@ export class ActionBar extends ActionBarBase { private updateFlatness(navBar: UINavigationBar) { if (this.flat) { - if (majorVersion >= 15) { + if (SDK_VERSION >= 15) { const appearance = this._getAppearance(navBar); appearance.shadowColor = UIColor.clearColor; this._updateAppearance(navBar, appearance); @@ -530,7 +529,7 @@ export class ActionBar extends ActionBarBase { navBar.translucent = false; } } else { - if (majorVersion >= 15) { + if (SDK_VERSION >= 15) { if (navBar.standardAppearance) { // Not flat and never been set do nothing. const appearance = navBar.standardAppearance; @@ -581,7 +580,7 @@ export class ActionBar extends ActionBarBase { public onLayout(left: number, top: number, right: number, bottom: number) { const titleView = this.titleView; if (titleView) { - if (majorVersion > 10) { + if (SDK_VERSION > 10) { // On iOS 11 titleView is wrapped in another view that is centered with constraints. View.layoutChild(this, titleView, 0, 0, titleView.getMeasuredWidth(), titleView.getMeasuredHeight()); } else { diff --git a/packages/core/ui/activity-indicator/index.ios.ts b/packages/core/ui/activity-indicator/index.ios.ts index a8847e4d2..44dd9f6e6 100644 --- a/packages/core/ui/activity-indicator/index.ios.ts +++ b/packages/core/ui/activity-indicator/index.ios.ts @@ -1,13 +1,11 @@ import { ActivityIndicatorBase, busyProperty, iosIndicatorViewStyleProperty } from './activity-indicator-common'; import { colorProperty } from '../styling/style-properties'; import { Color } from '../../color'; -import { iOSNativeHelper } from '../../utils'; +import { SDK_VERSION } from '../../utils/constants'; import { IOSIndicatorViewStyle } from '.'; export * from './activity-indicator-common'; -const majorVersion = iOSNativeHelper.MajorVersion; - export class ActivityIndicator extends ActivityIndicatorBase { nativeViewProtected: UIActivityIndicatorView; @@ -29,10 +27,10 @@ export class ActivityIndicator extends ActivityIndicatorBase { switch (value) { case 'large': - viewStyle = majorVersion > 12 ? UIActivityIndicatorViewStyle.Large : UIActivityIndicatorViewStyle.WhiteLarge; + viewStyle = SDK_VERSION > 12 ? UIActivityIndicatorViewStyle.Large : UIActivityIndicatorViewStyle.WhiteLarge; break; default: - viewStyle = majorVersion > 12 ? UIActivityIndicatorViewStyle.Medium : UIActivityIndicatorViewStyle.Gray; + viewStyle = SDK_VERSION > 12 ? UIActivityIndicatorViewStyle.Medium : UIActivityIndicatorViewStyle.Gray; break; } diff --git a/packages/core/ui/core/view/view-helper/index.ios.ts b/packages/core/ui/core/view/view-helper/index.ios.ts index 90648d1d3..4c3c0f33c 100644 --- a/packages/core/ui/core/view/view-helper/index.ios.ts +++ b/packages/core/ui/core/view/view-helper/index.ios.ts @@ -41,7 +41,7 @@ class UILayoutViewController extends UIViewController { super.viewDidLayoutSubviews(); const owner = this.owner?.deref(); if (owner) { - if (iOSUtils.MajorVersion >= 11) { + if (SDK_VERSION >= 11) { // Handle nested UILayoutViewController safe area application. // Currently, UILayoutViewController can be nested only in a TabView. // The TabView itself is handled by the OS, so we check the TabView's parent (usually a Page, but can be a Layout). @@ -116,7 +116,7 @@ class UILayoutViewController extends UIViewController { public traitCollectionDidChange(previousTraitCollection: UITraitCollection): void { super.traitCollectionDidChange(previousTraitCollection); - if (iOSUtils.MajorVersion >= 13) { + if (SDK_VERSION >= 13) { const owner = this.owner?.deref(); if (owner && this.traitCollection.hasDifferentColorAppearanceComparedToTraitCollection && this.traitCollection.hasDifferentColorAppearanceComparedToTraitCollection(previousTraitCollection)) { owner.notify({ diff --git a/packages/core/ui/frame/index.android.ts b/packages/core/ui/frame/index.android.ts index 1bf7569a1..e97facb56 100644 --- a/packages/core/ui/frame/index.android.ts +++ b/packages/core/ui/frame/index.android.ts @@ -137,7 +137,7 @@ export class Frame extends FrameBase { // _onAttachedToWindow called from OS again after it was detach // still happens with androidx.fragment:1.3.2 const activity = androidUtils.getCurrentActivity(); - const lifecycleState = activity?.getLifecycle?.()?.getCurrentState() || androidx.lifecycle.Lifecycle.State.CREATED; + const lifecycleState = (activity as androidx.fragment.app.FragmentActivity)?.getLifecycle?.()?.getCurrentState() || androidx.lifecycle.Lifecycle.State.CREATED; if ((this._manager && this._manager.isDestroyed()) || !lifecycleState.isAtLeast(androidx.lifecycle.Lifecycle.State.CREATED)) { return; } diff --git a/packages/core/ui/frame/index.ios.ts b/packages/core/ui/frame/index.ios.ts index 33787b214..78411fec2 100644 --- a/packages/core/ui/frame/index.ios.ts +++ b/packages/core/ui/frame/index.ios.ts @@ -5,7 +5,8 @@ import { Page } from '../page'; import { View } from '../core/view'; import { IOSHelper } from '../core/view/view-helper'; import { profile } from '../../profiling'; -import { CORE_ANIMATION_DEFAULTS, ios as iOSUtils, layout } from '../../utils'; +import { CORE_ANIMATION_DEFAULTS, layout } from '../../utils'; +import { SDK_VERSION } from '../../utils/constants'; import { Trace } from '../../trace'; import type { PageTransition } from '../transition/page-transition'; import { SlideTransition } from '../transition/slide-transition'; @@ -14,8 +15,6 @@ import { SharedTransition } from '../transition/shared-transition'; export * from './frame-common'; -const majorVersion = iOSUtils.MajorVersion; - const ENTRY = '_entry'; const DELEGATE = '_delegate'; const NAV_DEPTH = '_navDepth'; @@ -144,7 +143,7 @@ export class Frame extends FrameBase { backstackEntry[NAV_DEPTH] = navDepth; viewController[ENTRY] = backstackEntry; - if (!animated && majorVersion > 10) { + if (!animated && SDK_VERSION > 10) { // Reset back button title before pushing view controller to prevent // displaying default 'back' title (when NavigaitonButton custom title is set). const barButtonItem = UIBarButtonItem.alloc().initWithTitleStyleTargetAction('', UIBarButtonItemStyle.Plain, null, null); @@ -633,7 +632,7 @@ class UINavigationControllerImpl extends UINavigationController { public traitCollectionDidChange(previousTraitCollection: UITraitCollection): void { super.traitCollectionDidChange(previousTraitCollection); - if (majorVersion >= 13) { + if (SDK_VERSION >= 13) { const owner = this._owner?.deref?.(); if (owner && this.traitCollection.hasDifferentColorAppearanceComparedToTraitCollection && this.traitCollection.hasDifferentColorAppearanceComparedToTraitCollection(previousTraitCollection)) { owner.notify({ diff --git a/packages/core/ui/html-view/index.ios.ts b/packages/core/ui/html-view/index.ios.ts index 730e1a0c8..536152745 100644 --- a/packages/core/ui/html-view/index.ios.ts +++ b/packages/core/ui/html-view/index.ios.ts @@ -3,12 +3,11 @@ import { Font } from '../styling/font'; import { colorProperty, fontInternalProperty } from '../styling/style-properties'; import { HtmlViewBase, htmlProperty, selectableProperty, linkColorProperty } from './html-view-common'; import { View } from '../core/view'; -import { iOSNativeHelper, layout } from '../../utils'; +import { layout } from '../../utils'; +import { SDK_VERSION } from '../../utils/constants'; export * from './html-view-common'; -const majorVersion = iOSNativeHelper.MajorVersion; - export class HtmlView extends HtmlViewBase { nativeViewProtected: UITextView; @@ -89,7 +88,7 @@ export class HtmlView extends HtmlViewBase { this.nativeViewProtected.attributedText = NSAttributedString.alloc().initWithDataOptionsDocumentAttributesError(nsData, attributes, null); - if (!this.style.color && majorVersion >= 13 && UIColor.labelColor) { + if (!this.style.color && SDK_VERSION >= 13 && UIColor.labelColor) { this.nativeViewProtected.textColor = UIColor.labelColor; } } diff --git a/packages/core/ui/scroll-view/index.ios.ts b/packages/core/ui/scroll-view/index.ios.ts index 984339695..16617e7cc 100644 --- a/packages/core/ui/scroll-view/index.ios.ts +++ b/packages/core/ui/scroll-view/index.ios.ts @@ -1,12 +1,11 @@ import { ScrollEventData } from '.'; import { ScrollViewBase, scrollBarIndicatorVisibleProperty, isScrollEnabledProperty } from './scroll-view-common'; -import { iOSNativeHelper, layout } from '../../utils'; +import { layout } from '../../utils'; +import { SDK_VERSION } from '../../utils/constants'; import { View } from '../core/view'; export * from './scroll-view-common'; -const majorVersion = iOSNativeHelper.MajorVersion; - @NativeClass class UIScrollViewDelegateImpl extends NSObject implements UIScrollViewDelegate { private _owner: WeakRef; @@ -174,7 +173,7 @@ export class ScrollView extends ScrollViewBase { let width = right - left - insets.right - insets.left; let height = bottom - top - insets.bottom - insets.top; - if (majorVersion > 10) { + if (SDK_VERSION > 10) { // Disable automatic adjustment of scroll view insets // Consider exposing this as property with all 4 modes // https://developer.apple.com/documentation/uikit/uiscrollview/contentinsetadjustmentbehavior diff --git a/packages/core/ui/switch/index.ios.ts b/packages/core/ui/switch/index.ios.ts index 242b9aabd..c88fcb489 100644 --- a/packages/core/ui/switch/index.ios.ts +++ b/packages/core/ui/switch/index.ios.ts @@ -1,12 +1,11 @@ import { SwitchBase, checkedProperty, offBackgroundColorProperty } from './switch-common'; import { colorProperty, backgroundColorProperty, backgroundInternalProperty } from '../styling/style-properties'; import { Color } from '../../color'; -import { iOSNativeHelper, layout } from '../../utils'; +import { layout } from '../../utils'; +import { SDK_VERSION } from '../../utils/constants'; export * from './switch-common'; -const majorVersion = iOSNativeHelper.MajorVersion; - @NativeClass class SwitchChangeHandlerImpl extends NSObject { private _owner: WeakRef; @@ -75,7 +74,7 @@ export class Switch extends SwitchBase { // only add :checked pseudo handling on supported iOS versions // ios <13 works but causes glitchy animations when toggling // so we decided to keep the old behavior on older versions. - if (majorVersion >= 13) { + if (SDK_VERSION >= 13) { super._onCheckedPropertyChanged(newValue); if (this.offBackgroundColor) { @@ -130,7 +129,7 @@ export class Switch extends SwitchBase { return this.nativeViewProtected.onTintColor; } [backgroundColorProperty.setNative](value: UIColor | Color) { - if (majorVersion >= 13) { + if (SDK_VERSION >= 13) { if (!this.offBackgroundColor || this.checked) { this.setNativeBackgroundColor(value); } @@ -151,7 +150,7 @@ export class Switch extends SwitchBase { return this.nativeViewProtected.backgroundColor; } [offBackgroundColorProperty.setNative](value: Color | UIColor) { - if (majorVersion >= 13) { + if (SDK_VERSION >= 13) { if (!this.checked) { this.setNativeBackgroundColor(value); } diff --git a/packages/core/ui/tab-view/index.android.ts b/packages/core/ui/tab-view/index.android.ts index cc8405311..3b33d4a11 100644 --- a/packages/core/ui/tab-view/index.android.ts +++ b/packages/core/ui/tab-view/index.android.ts @@ -8,7 +8,7 @@ import { ImageSource } from '../../image-source'; import { Trace } from '../../trace'; import { Color } from '../../color'; import { fontSizeProperty, fontInternalProperty } from '../styling/style-properties'; -import { RESOURCE_PREFIX, ad, layout } from '../../utils'; +import { RESOURCE_PREFIX, android as androidUtils, layout } from '../../utils'; import { Frame } from '../frame'; import { Application } from '../../application'; import { AndroidHelper } from '../core/view'; @@ -293,7 +293,7 @@ function createTabItemSpec(item: TabViewItem): org.nativescript.widgets.TabItemS if (item.iconSource) { if (item.iconSource.indexOf(RESOURCE_PREFIX) === 0) { - result.iconId = ad.resources.getDrawableId(item.iconSource.substr(RESOURCE_PREFIX.length)); + result.iconId = androidUtils.resources.getDrawableId(item.iconSource.substr(RESOURCE_PREFIX.length)); if (result.iconId === 0) { traceMissingIcon(item.iconSource); } @@ -315,7 +315,7 @@ let defaultAccentColor: number = undefined; function getDefaultAccentColor(context: android.content.Context): number { if (defaultAccentColor === undefined) { //Fallback color: https://developer.android.com/samples/SlidingTabsColors/src/com.example.android.common/view/SlidingTabStrip.html - defaultAccentColor = ad.resources.getPaletteColor(ACCENT_COLOR, context) || 0xff33b5e5; + defaultAccentColor = androidUtils.resources.getPaletteColor(ACCENT_COLOR, context) || 0xff33b5e5; } return defaultAccentColor; @@ -484,7 +484,7 @@ export class TabView extends TabViewBase { const viewPager = new org.nativescript.widgets.TabViewPager(context); const tabLayout = new org.nativescript.widgets.TabLayout(context); const lp = new org.nativescript.widgets.CommonLayoutParams(); - const primaryColor = ad.resources.getPaletteColor(PRIMARY_COLOR, context); + const primaryColor = androidUtils.resources.getPaletteColor(PRIMARY_COLOR, context); let accentColor = getDefaultAccentColor(context); lp.row = 1; @@ -494,7 +494,7 @@ export class TabView extends TabViewBase { JSON.stringify([ { value: 1, type: 0 /* org.nativescript.widgets.GridUnitType.auto */ }, { value: 1, type: 2 /* org.nativescript.widgets.GridUnitType.star */ }, - ]) + ]), ); viewPager.setLayoutParams(lp); @@ -506,7 +506,7 @@ export class TabView extends TabViewBase { JSON.stringify([ { value: 1, type: 2 /* org.nativescript.widgets.GridUnitType.star */ }, { value: 1, type: 0 /* org.nativescript.widgets.GridUnitType.auto */ }, - ]) + ]), ); tabLayout.setLayoutParams(lp); viewPager.setSwipePageEnabled(false); diff --git a/packages/core/ui/text-base/index.ios.ts b/packages/core/ui/text-base/index.ios.ts index 89a3b3319..c47969977 100644 --- a/packages/core/ui/text-base/index.ios.ts +++ b/packages/core/ui/text-base/index.ios.ts @@ -12,14 +12,12 @@ import { Span } from './span'; import { colorProperty, fontInternalProperty, fontScaleInternalProperty, Length } from '../styling/style-properties'; import { StrokeCSSValues } from '../styling/css-stroke'; import { isString, isNullOrUndefined } from '../../utils/types'; -import { iOSNativeHelper, layout } from '../../utils'; -import { Trace } from '../../trace'; +import { layout } from '../../utils'; +import { SDK_VERSION } from '../../utils/constants'; import { CoreTypes } from '../../core-types'; export * from './text-base-common'; -const majorVersion = iOSNativeHelper.MajorVersion; - @NativeClass class UILabelClickHandlerImpl extends NSObject { private _owner: WeakRef; @@ -350,7 +348,7 @@ export class TextBase extends TextBaseCommon { const text = getTransformedText(isNullOrUndefined(this.text) ? '' : `${this.text}`, this.textTransform); this.nativeTextViewProtected.nativeScriptSetTextDecorationAndTransformTextDecorationLetterSpacingLineHeight(text, this.style.textDecoration || '', letterSpacing, lineHeight); - if (!this.style?.color && majorVersion >= 13 && UIColor.labelColor) { + if (!this.style?.color && SDK_VERSION >= 13 && UIColor.labelColor) { this._setColor(UIColor.labelColor); } } diff --git a/packages/core/utils/android/index.ts b/packages/core/utils/android/index.ts deleted file mode 100644 index fc32ddbf1..000000000 --- a/packages/core/utils/android/index.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { Application } from '../../application'; -import { Trace } from '../../trace'; -import { topmost } from '../../ui/frame/frame-stack'; - -let application: android.app.Application; -let applicationContext: android.content.Context; -let contextResources: android.content.res.Resources; -let packageName: string; - -export function getApplicationContext() { - if (!applicationContext) { - applicationContext = getApplication().getApplicationContext(); - } - - return applicationContext; -} -export function getCurrentActivity() { - if (!Application) { - return null; - } - return Application.android.foregroundActivity || Application.android.startActivity; -} -export function getApplication() { - if (!application) { - application = Application.android.getNativeApplication(); - } - - return application; -} -export function getResources() { - if (!contextResources) { - contextResources = getApplication().getResources(); - } - - return contextResources; -} -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 = getApplicationContext().getSystemService(android.content.Context.INPUT_METHOD_SERVICE); - } - - return inputMethodManager; -} - -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 { - const hashSet = new java.util.HashSet(); - 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(); - - 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 getPalleteColor(name: string, context: android.content.Context): number { - return getPaletteColor(name, context); - } - 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); -} diff --git a/packages/core/utils/index.android.ts b/packages/core/utils/index.android.ts index bb0826e44..e2e3b7100 100644 --- a/packages/core/utils/index.android.ts +++ b/packages/core/utils/index.android.ts @@ -185,10 +185,6 @@ Please ensure you have your manifest correctly configured with the FileProvider. } } -export function isRealDevice(): boolean { - return AndroidUtils.isRealDevice(); -} - export function dismissSoftInput(nativeView?: any): void { AndroidUtils.dismissSoftInput(nativeView); } diff --git a/packages/core/utils/index.ios.ts b/packages/core/utils/index.ios.ts index 891247f67..7314e22c8 100644 --- a/packages/core/utils/index.ios.ts +++ b/packages/core/utils/index.ios.ts @@ -1,5 +1,5 @@ import { Trace } from '../trace'; -import { ios as iOSUtils } from './native-helper'; +import { ios as iOSUtils, isRealDevice } from './native-helper'; export { clearInterval, clearTimeout, setInterval, setTimeout } from '../timer'; export * from './common'; @@ -10,11 +10,12 @@ export * from './macrotask-scheduler'; export * from './mainthread-helper'; export * from './native-helper'; export * from './types'; +export * from './native-helper'; export function openFile(filePath: string): boolean { try { const appPath = iOSUtils.getCurrentAppPath(); - const path = iOSUtils.isRealDevice() ? filePath.replace('~', appPath) : filePath; + const path = isRealDevice() ? filePath.replace('~', appPath) : filePath; const controller = UIDocumentInteractionController.interactionControllerWithURL(NSURL.fileURLWithPath(path)); controller.delegate = iOSUtils.createUIDocumentInteractionControllerDelegate(); @@ -69,10 +70,6 @@ export function openUrlAsync(location: string): Promise { }); } -export function isRealDevice(): boolean { - return iOSUtils.isRealDevice(); -} - export const ad = 0; export function dismissSoftInput(nativeView?: UIView): void { diff --git a/packages/core/utils/ios/index.ts b/packages/core/utils/ios/index.ts deleted file mode 100644 index df13b1ea9..000000000 --- a/packages/core/utils/ios/index.ts +++ /dev/null @@ -1,264 +0,0 @@ -import { Color } from '../../color'; -import { Trace } from '../../trace'; -import { CORE_ANIMATION_DEFAULTS, getDurationWithDampingFromSpring } from '../common'; -import { SDK_VERSION } from '../constants'; - -declare let UIImagePickerControllerSourceType: any; - -const radToDeg = Math.PI / 180; - -function isOrientationLandscape(orientation: number) { - return orientation === UIDeviceOrientation.LandscapeLeft /* 3 */ || orientation === UIDeviceOrientation.LandscapeRight /* 4 */; -} - -function openFileAtRootModule(filePath: string): boolean { - try { - const appPath = getCurrentAppPath(); - const path = isRealDevice() ? filePath.replace('~', appPath) : filePath; - - const controller = UIDocumentInteractionController.interactionControllerWithURL(NSURL.fileURLWithPath(path)); - controller.delegate = createUIDocumentInteractionControllerDelegate(); - - return controller.presentPreviewAnimated(true); - } catch (e) { - Trace.write('Error in openFile', Trace.categories.Error, Trace.messageType.error); - } - - return false; -} -// TODO: remove for NativeScript 9.0 -export function getter(_this: any, property: T | { (): T }): T { - console.log('utils.ios.getter() is deprecated; use the respective native property instead'); - if (typeof property === 'function') { - return (<{ (): T }>property).call(_this); - } else { - return property; - } -} - -export namespace collections { - export function jsArrayToNSArray(str: T[]): NSArray { - return NSArray.arrayWithArray(str); - } - - export function nsArrayToJSArray(a: NSArray): Array { - const arr = []; - if (a !== undefined) { - const count = a.count; - for (let i = 0; i < count; i++) { - arr.push(a.objectAtIndex(i)); - } - } - - return arr; - } -} - -export function getRootViewController(): UIViewController { - const win = getWindow(); - let vc = win && win.rootViewController; - while (vc && vc.presentedViewController) { - vc = vc.presentedViewController; - } - return vc; -} - -export function getWindow(): UIWindow { - let window: UIWindow; - if (SDK_VERSION >= 15 && typeof NativeScriptViewFactory !== 'undefined') { - // UIWindowScene.keyWindow is only available 15+ - window = NativeScriptViewFactory.getKeyWindow(); - } - if (window) { - return window; - } - const app = UIApplication.sharedApplication; - if (!app) { - return; - } - return app.keyWindow || (app.windows && app.windows.count > 0 && app.windows.objectAtIndex(0)); -} - -export function getMainScreen(): UIScreen { - const window = getWindow(); - return window ? window.screen : UIScreen.mainScreen; -} - -export function setWindowBackgroundColor(value: string) { - const win = getWindow(); - if (win) { - const bgColor = new Color(value); - win.backgroundColor = bgColor.ios; - const rootVc = getRootViewController(); - if (rootVc?.view) { - rootVc.view.backgroundColor = bgColor.ios; - } - } -} - -export function isLandscape(): boolean { - console.log('utils.ios.isLandscape() is deprecated; use application.orientation instead'); - - const deviceOrientation = UIDevice.currentDevice.orientation; - const statusBarOrientation = UIApplication.sharedApplication.statusBarOrientation; - - const isDeviceOrientationLandscape = isOrientationLandscape(deviceOrientation); - const isStatusBarOrientationLandscape = isOrientationLandscape(statusBarOrientation); - - return isDeviceOrientationLandscape || isStatusBarOrientationLandscape; -} - -/** - * @deprecated use Utils.SDK_VERSION instead which is a float of the {major}.{minor} verison - */ -export const MajorVersion = NSString.stringWithString(UIDevice.currentDevice.systemVersion).intValue; - -export function openFile(filePath: string): boolean { - console.log('utils.ios.openFile() is deprecated; use utils.openFile() instead'); - - return openFileAtRootModule(filePath); -} - -export function getCurrentAppPath(): string { - const currentDir = __dirname; - const tnsModulesIndex = currentDir.indexOf('/tns_modules'); - - // Module not hosted in ~/tns_modules when bundled. Use current dir. - let appPath = currentDir; - if (tnsModulesIndex !== -1) { - // Strip part after tns_modules to obtain app root - appPath = currentDir.substring(0, tnsModulesIndex); - } - - return appPath; -} - -export function joinPaths(...paths: string[]): string { - if (!paths || paths.length === 0) { - return ''; - } - - return NSString.stringWithString(NSString.pathWithComponents(paths)).stringByStandardizingPath; -} - -export function getVisibleViewController(rootViewController: UIViewController): UIViewController { - let viewController = rootViewController; - - while (viewController && viewController.presentedViewController) { - viewController = viewController.presentedViewController; - } - return viewController; -} - -export function applyRotateTransform(transform: CATransform3D, x: number, y: number, z: number): CATransform3D { - if (x) { - transform = CATransform3DRotate(transform, x * radToDeg, 1, 0, 0); - } - - if (y) { - transform = CATransform3DRotate(transform, y * radToDeg, 0, 1, 0); - } - - if (z) { - transform = CATransform3DRotate(transform, z * radToDeg, 0, 0, 1); - } - - return transform; -} - -export function createUIDocumentInteractionControllerDelegate(): NSObject { - @NativeClass - class UIDocumentInteractionControllerDelegateImpl extends NSObject implements UIDocumentInteractionControllerDelegate { - public static ObjCProtocols = [UIDocumentInteractionControllerDelegate]; - - public getViewController(): UIViewController { - return getWindow().rootViewController; - } - - public documentInteractionControllerViewControllerForPreview(controller: UIDocumentInteractionController) { - return this.getViewController(); - } - - public documentInteractionControllerViewForPreview(controller: UIDocumentInteractionController) { - return this.getViewController().view; - } - - public documentInteractionControllerRectForPreview(controller: UIDocumentInteractionController): CGRect { - return this.getViewController().view.frame; - } - } - return new UIDocumentInteractionControllerDelegateImpl(); -} - -export function isRealDevice() { - try { - if (NSProcessInfo.processInfo.environment.valueForKey('SIMULATOR_DEVICE_NAME')) { - return false; - } - return true; - } catch (e) { - return true; - } -} - -export function printCGRect(rect: CGRect) { - if (rect) { - return `CGRect(${rect.origin.x} ${rect.origin.y} ${rect.size.width} ${rect.size.height})`; - } -} - -export function snapshotView(view: UIView, scale: number): UIImage { - if (view instanceof UIImageView) { - return view.image; - } - // console.log('snapshotView view.frame:', printRect(view.frame)); - const originalOpacity = view.layer.opacity; - view.layer.opacity = originalOpacity > 0 ? originalOpacity : 1; - UIGraphicsBeginImageContextWithOptions(CGSizeMake(view.frame.size.width, view.frame.size.height), false, scale); - view.layer.renderInContext(UIGraphicsGetCurrentContext()); - const image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - setTimeout(() => { - // ensure set back properly on next tick - view.layer.opacity = originalOpacity; - }); - return image; -} - -export function copyLayerProperties(view: UIView, toView: UIView, customProperties?: { view?: Array; layer?: Array }) { - const viewPropertiesToMatch: Array = customProperties?.view || ['backgroundColor']; - const layerPropertiesToMatch: Array = customProperties?.layer || ['cornerRadius', 'borderWidth', 'borderColor']; - - viewPropertiesToMatch.forEach((property) => { - if (view[property] !== toView[property]) { - // console.log('| -- matching view property:', property); - view[property as any] = toView[property]; - } - }); - - layerPropertiesToMatch.forEach((property) => { - if (view.layer[property] !== toView.layer[property]) { - // console.log('| -- matching layer property:', property); - view.layer[property as any] = toView.layer[property]; - } - }); -} - -export function animateWithSpring(options?: { tension?: number; friction?: number; mass?: number; delay?: number; velocity?: number; animateOptions?: UIViewAnimationOptions; animations?: () => void; completion?: (finished?: boolean) => void }) { - // for convenience, default spring settings are provided - const opt = { - ...CORE_ANIMATION_DEFAULTS.spring, - delay: 0, - animateOptions: null, - animations: null, - completion: null, - ...(options || {}), - }; - const { duration, damping } = getDurationWithDampingFromSpring(opt); - - if (duration === 0) { - UIView.animateWithDurationAnimationsCompletion(0, opt.animations, opt.completion); - return; - } - UIView.animateWithDurationDelayUsingSpringWithDampingInitialSpringVelocityOptionsAnimationsCompletion(duration, opt.delay, damping, opt.velocity, opt.animateOptions, opt.animations, opt.completion); -} diff --git a/packages/core/utils/layout-helper/index.ios.ts b/packages/core/utils/layout-helper/index.ios.ts index ea39616e0..1d0f7105b 100644 --- a/packages/core/utils/layout-helper/index.ios.ts +++ b/packages/core/utils/layout-helper/index.ios.ts @@ -1,5 +1,5 @@ import * as layoutCommon from './layout-helper-common'; -import { getMainScreen } from '../ios'; +import { ios as iosUtils } from '../native-helper'; export namespace layout { // cache the MeasureSpec constants here, to prevent extensive marshaling calls to and from Objective C @@ -33,15 +33,15 @@ export namespace layout { } export function getDisplayDensity(): number { - return getMainScreen().scale; + return iosUtils.getMainScreen().scale; } export function toDevicePixels(value: number): number { - return value * getMainScreen().scale; + return value * iosUtils.getMainScreen().scale; } export function toDeviceIndependentPixels(value: number): number { - return value / getMainScreen().scale; + return value / iosUtils.getMainScreen().scale; } export function round(value: number) { diff --git a/packages/core/utils/native-helper.android.ts b/packages/core/utils/native-helper.android.ts index de702cb1d..f6e9c8b59 100644 --- a/packages/core/utils/native-helper.android.ts +++ b/packages/core/utils/native-helper.android.ts @@ -1,6 +1,8 @@ -import * as AndroidUtils from './android'; import { platformCheck } from './platform-check'; import { numberHasDecimals, numberIs64Bit } from './types'; +import { Application } from '../application'; +import { Trace } from '../trace'; +import { topmost } from '../ui/frame/frame-stack'; export function dataDeserialize(nativeData?: any) { if (nativeData === null || typeof nativeData !== 'object') { @@ -140,12 +142,190 @@ export function dataSerialize(data?: any, wrapPrimitives?: boolean) { } } -export import android = AndroidUtils; +let application: android.app.Application; +let applicationContext: android.content.Context; +let contextResources: android.content.res.Resources; +let packageName: string; + +function getApplicationContext() { + if (!applicationContext) { + applicationContext = getApplication().getApplicationContext(); + } + + return applicationContext; +} +function getCurrentActivity() { + if (!Application) { + return null; + } + return Application.android.foregroundActivity || Application.android.startActivity; +} +function getApplication() { + if (!application) { + application = Application.android.getNativeApplication(); + } + + return application; +} +function getResources() { + if (!contextResources) { + contextResources = getApplication().getResources(); + } + + return contextResources; +} +function getPackageName() { + if (!packageName) { + packageName = getApplicationContext().getPackageName(); + } + + return packageName; +} + +let inputMethodManager: android.view.inputmethod.InputMethodManager; +function getInputMethodManager(): android.view.inputmethod.InputMethodManager { + if (!inputMethodManager) { + inputMethodManager = getApplicationContext().getSystemService(android.content.Context.INPUT_METHOD_SERVICE); + } + + return inputMethodManager; +} + +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); + } +} + +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); + } +} + +namespace collections { + export function stringArrayToStringSet(str: string[]): java.util.HashSet { + const hashSet = new java.util.HashSet(); + 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(); + + 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); +} + +export const androidUtils = { + resources, + getApplication, + getCurrentActivity, + getApplicationContext, + getResources, + getPackageName, + getInputMethodManager, + showSoftInput, + dismissSoftInput, +}; /** * @deprecated Use `Utils.android` instead. */ -export import ad = AndroidUtils; +export const ad = androidUtils; // these don't exist on Android.Stub them to empty functions. export const iOSNativeHelper = platformCheck('Utils.iOSNativeHelper'); diff --git a/packages/core/utils/native-helper.d.ts b/packages/core/utils/native-helper.d.ts index c89d7d26c..3b6311fb5 100644 --- a/packages/core/utils/native-helper.d.ts +++ b/packages/core/utils/native-helper.d.ts @@ -9,6 +9,11 @@ export function dataSerialize(data?: any, wrapPrimitives?: boolean): any; */ export function dataDeserialize(nativeData?: any): any; +/** + * Checks whether the application is running on real device and not on emulator. + */ +export function isRealDevice(): boolean; + // /** // * Module with android specific utilities. // */ @@ -94,24 +99,8 @@ export function dataDeserialize(nativeData?: any): any; // * @param type - (Optional) type // */ // export function getResource(name: string, type?: string): number; - -// /** -// * [Obsolete - please use getPaletteColor] Gets a color from current theme. -// * @param name - Name of the color -// */ -// export function getPalleteColor(); - -// /** -// * Gets a color from the current theme. -// * @param name - Name of the color resource. -// */ -// export function getPaletteColor(name: string, context: any /* android.content.Context */): number; // } -// /** -// * Checks whether the application is running on real device and not on emulator. -// */ -// export function isRealDevice(): boolean; // } // /** @@ -187,19 +176,6 @@ export function dataDeserialize(nativeData?: any): any; // */ // export function openFile(filePath: string): boolean; -// /** -// * Joins an array of file paths. -// * @param paths An array of paths. -// * Returns the joined path. -// */ -// export function joinPaths(...paths: string[]): string; - -// /** -// * Gets the root folder for the current application. This Folder is private for the application and not accessible from Users/External apps. -// * iOS - this folder is read-only and contains the app and all its resources. -// */ -// export function getCurrentAppPath(): string; - // /** // * Gets the currently visible(topmost) UIViewController. // * @param rootViewController The root UIViewController instance to start searching from (normally window.rootViewController). @@ -207,20 +183,6 @@ export function dataDeserialize(nativeData?: any): any; // */ // export function getVisibleViewController(rootViewController: any /* UIViewController*/): any; /* UIViewController*/ -// /** -// * -// * @param transform Applies a rotation transform over X,Y and Z axis -// * @param x Rotation over X axis in degrees -// * @param y Rotation over Y axis in degrees -// * @param z Rotation over Z axis in degrees -// */ -// export function applyRotateTransform(transform: any /* CATransform3D*/, x: number, y: number, z: number): any; /* CATransform3D*/ - -// /** -// * Create a UIDocumentInteractionControllerDelegate implementation for use with UIDocumentInteractionController -// */ -// export function createUIDocumentInteractionControllerDelegate(): any; - // /** // * Checks whether the application is running on real device and not on simulator. // */ @@ -248,33 +210,93 @@ export function dataDeserialize(nativeData?: any): any; // */ // export function copyLayerProperties(view: UIView, toView: UIView, customProperties?: { view?: Array /* Array */; layer?: Array /* Array */ }): void; -// /** -// * Animate views with a configurable spring effect -// * @param options various animation settings for the spring -// * - tension: number -// * - friction: number -// * - mass: number -// * - delay: number -// * - velocity: number -// * - animateOptions: UIViewAnimationOptions -// * - animations: () => void, Callback containing the property changes you want animated -// * - completion: (finished: boolean) => void, Callback when animation is finished -// */ -// export function animateWithSpring(options?: { tension?: number; friction?: number; mass?: number; delay?: number; velocity?: number; animateOptions?: UIViewAnimationOptions; animations?: () => void; completion?: (finished?: boolean) => void }); // } -import * as AndroidUtils from './android'; -export import android = AndroidUtils; +export const android: { + resources: { + getDrawableId: (name) => number; + getStringId: (name) => number; + getId: (name: string) => number; + getResource: (name: string, type?: string) => number; + /** + * Gets a color from the current theme. + * @param name - Name of the color resource. + * @param context - Context to resolve the color. + */ + getPaletteColor: (name: string, context: android.content.Context) => number; + }; + getApplication: () => android.app.Application; + getCurrentActivity: () => androidx.appcompat.app.AppCompatActivity | android.app.Activity | null; + getApplicationContext: () => android.content.Context; + getResources: () => android.content.res.Resources; + getPackageName: () => string; + getInputMethodManager: () => android.view.inputmethod.InputMethodManager; + showSoftInput: (nativeView: android.view.View) => void; + dismissSoftInput: (nativeView?: android.view.View) => void; +}; /** * @deprecated use Utils.android instead. */ -export import ad = AndroidUtils; +export const ad = android; -import * as iOSUtils from './ios'; -export import ios = iOSUtils; +export const ios: { + collections: { + jsArrayToNSArray(str: T[]): NSArray; + nsArrayToJSArray(a: NSArray): Array; + }; + /** + * Create a UIDocumentInteractionControllerDelegate implementation for use with UIDocumentInteractionController + */ + createUIDocumentInteractionControllerDelegate: () => NSObject; + /** + * Gets the root folder for the current application. This Folder is private for the application and not accessible from Users/External apps. + * iOS - this folder is read-only and contains the app and all its resources. + */ + getCurrentAppPath: () => string; + getRootViewController: () => UIViewController; + getVisibleViewController: (rootViewController: UIViewController) => UIViewController; + getWindow: () => UIWindow; + getMainScreen: () => UIScreen; + setWindowBackgroundColor: (value: string) => void; + isLandscape: () => boolean; + snapshotView: (view: UIView, scale: number) => UIImage; + /** + * Applies a rotation transform over X,Y and Z axis + * @param transform Applies a rotation transform over X,Y and Z axis + * @param x Rotation over X axis in degrees + * @param y Rotation over Y axis in degrees + * @param z Rotation over Z axis in degrees + */ + applyRotateTransform: (transform: CATransform3D, x: number, y: number, z: number) => CATransform3D; + printCGRect: (rect: CGRect) => void; + copyLayerProperties: (view: UIView, toView: UIView, customProperties?: { view?: Array; layer?: Array }) => void; + /** + * Animate views with a configurable spring effect + * @param options various animation settings for the spring + * - tension: number + * - friction: number + * - mass: number + * - delay: number + * - velocity: number + * - animateOptions: UIViewAnimationOptions + * - animations: () => void, Callback containing the property changes you want animated + * - completion: (finished: boolean) => void, Callback when animation is finished + */ + animateWithSpring: (options?: { tension?: number; friction?: number; mass?: number; delay?: number; velocity?: number; animateOptions?: UIViewAnimationOptions; animations?: () => void; completion?: (finished?: boolean) => void }) => void; + /** + * Joins an array of file paths. + * @param paths An array of paths. + * Returns the joined path. + */ + joinPaths: (...paths: string[]) => string; + /** + * @deprecated use Utils.SDK_VERSION instead which is a float of the {major}.{minor} verison + */ + MajorVersion: number; +}; /** * @deprecated use Utils.ios instead. */ -export import iOSNativeHelper = iOSUtils; +export const iOSNativeHelper = ios; diff --git a/packages/core/utils/native-helper.ios.ts b/packages/core/utils/native-helper.ios.ts index 6bc925552..bf2ba049a 100644 --- a/packages/core/utils/native-helper.ios.ts +++ b/packages/core/utils/native-helper.ios.ts @@ -1,6 +1,9 @@ -import * as iOSUtils from './ios'; import { platformCheck } from './platform-check'; import { getClass, isNullOrUndefined, numberHasDecimals, numberIs64Bit } from './types'; +import { Color } from '../color'; +import { Trace } from '../trace'; +import { CORE_ANIMATION_DEFAULTS, getDurationWithDampingFromSpring } from './common'; +import { SDK_VERSION } from './constants'; export function dataDeserialize(nativeData?: any) { if (isNullOrUndefined(nativeData)) { @@ -88,13 +91,290 @@ export function dataSerialize(data: any, wrapPrimitives: boolean = false) { } } +function getCurrentAppPath(): string { + const currentDir = __dirname; + const tnsModulesIndex = currentDir.indexOf('/tns_modules'); + + // Module not hosted in ~/tns_modules when bundled. Use current dir. + let appPath = currentDir; + if (tnsModulesIndex !== -1) { + // Strip part after tns_modules to obtain app root + appPath = currentDir.substring(0, tnsModulesIndex); + } + + return appPath; +} + +function joinPaths(...paths: string[]): string { + if (!paths || paths.length === 0) { + return ''; + } + + return NSString.stringWithString(NSString.pathWithComponents(paths)).stringByStandardizingPath; +} + +declare let UIImagePickerControllerSourceType: any; + +const radToDeg = Math.PI / 180; + +function isOrientationLandscape(orientation: number) { + return orientation === UIDeviceOrientation.LandscapeLeft /* 3 */ || orientation === UIDeviceOrientation.LandscapeRight /* 4 */; +} + +function openFileAtRootModule(filePath: string): boolean { + try { + const appPath = getCurrentAppPath(); + const path = isRealDevice() ? filePath.replace('~', appPath) : filePath; + + const controller = UIDocumentInteractionController.interactionControllerWithURL(NSURL.fileURLWithPath(path)); + controller.delegate = createUIDocumentInteractionControllerDelegate(); + + return controller.presentPreviewAnimated(true); + } catch (e) { + Trace.write('Error in openFile', Trace.categories.Error, Trace.messageType.error); + } + + return false; +} +// TODO: remove for NativeScript 9.0 +export function getter(_this: any, property: T | { (): T }): T { + console.log('utils.ios.getter() is deprecated; use the respective native property instead'); + if (typeof property === 'function') { + return (<{ (): T }>property).call(_this); + } else { + return property; + } +} + +namespace collections { + export function jsArrayToNSArray(str: T[]): NSArray { + return NSArray.arrayWithArray(str); + } + + export function nsArrayToJSArray(a: NSArray): Array { + const arr = []; + if (a !== undefined) { + const count = a.count; + for (let i = 0; i < count; i++) { + arr.push(a.objectAtIndex(i)); + } + } + + return arr; + } +} + +function getRootViewController(): UIViewController { + const win = getWindow(); + let vc = win && win.rootViewController; + while (vc && vc.presentedViewController) { + vc = vc.presentedViewController; + } + return vc; +} + +function getWindow(): UIWindow { + let window: UIWindow; + if (SDK_VERSION >= 15 && typeof NativeScriptViewFactory !== 'undefined') { + // UIWindowScene.keyWindow is only available 15+ + window = NativeScriptViewFactory.getKeyWindow(); + } + if (window) { + return window; + } + const app = UIApplication.sharedApplication; + if (!app) { + return; + } + return app.keyWindow || (app.windows && app.windows.count > 0 && app.windows.objectAtIndex(0)); +} + +function getMainScreen(): UIScreen { + const window = getWindow(); + return window ? window.screen : UIScreen.mainScreen; +} + +function setWindowBackgroundColor(value: string) { + const win = getWindow(); + if (win) { + const bgColor = new Color(value); + win.backgroundColor = bgColor.ios; + const rootVc = getRootViewController(); + if (rootVc?.view) { + rootVc.view.backgroundColor = bgColor.ios; + } + } +} + +function isLandscape(): boolean { + console.log('utils.ios.isLandscape() is deprecated; use application.orientation instead'); + + const deviceOrientation = UIDevice.currentDevice.orientation; + const statusBarOrientation = UIApplication.sharedApplication.statusBarOrientation; + + const isDeviceOrientationLandscape = isOrientationLandscape(deviceOrientation); + const isStatusBarOrientationLandscape = isOrientationLandscape(statusBarOrientation); + + return isDeviceOrientationLandscape || isStatusBarOrientationLandscape; +} + +/** + * @deprecated use Utils.SDK_VERSION instead which is a float of the {major}.{minor} verison + */ +const MajorVersion = NSString.stringWithString(UIDevice.currentDevice.systemVersion).intValue; + +export function openFile(filePath: string): boolean { + console.log('utils.ios.openFile() is deprecated; use utils.openFile() instead'); + + return openFileAtRootModule(filePath); +} + +function getVisibleViewController(rootViewController: UIViewController): UIViewController { + let viewController = rootViewController; + + while (viewController && viewController.presentedViewController) { + viewController = viewController.presentedViewController; + } + return viewController; +} + +function applyRotateTransform(transform: CATransform3D, x: number, y: number, z: number): CATransform3D { + if (x) { + transform = CATransform3DRotate(transform, x * radToDeg, 1, 0, 0); + } + + if (y) { + transform = CATransform3DRotate(transform, y * radToDeg, 0, 1, 0); + } + + if (z) { + transform = CATransform3DRotate(transform, z * radToDeg, 0, 0, 1); + } + + return transform; +} + +function createUIDocumentInteractionControllerDelegate(): NSObject { + @NativeClass + class UIDocumentInteractionControllerDelegateImpl extends NSObject implements UIDocumentInteractionControllerDelegate { + public static ObjCProtocols = [UIDocumentInteractionControllerDelegate]; + + public getViewController(): UIViewController { + return getWindow().rootViewController; + } + + public documentInteractionControllerViewControllerForPreview(controller: UIDocumentInteractionController) { + return this.getViewController(); + } + + public documentInteractionControllerViewForPreview(controller: UIDocumentInteractionController) { + return this.getViewController().view; + } + + public documentInteractionControllerRectForPreview(controller: UIDocumentInteractionController): CGRect { + return this.getViewController().view.frame; + } + } + return new UIDocumentInteractionControllerDelegateImpl(); +} + +export function isRealDevice() { + try { + if (NSProcessInfo.processInfo.environment.valueForKey('SIMULATOR_DEVICE_NAME')) { + return false; + } + return true; + } catch (e) { + return true; + } +} + +function printCGRect(rect: CGRect) { + if (rect) { + return `CGRect(${rect.origin.x} ${rect.origin.y} ${rect.size.width} ${rect.size.height})`; + } +} + +function snapshotView(view: UIView, scale: number): UIImage { + if (view instanceof UIImageView) { + return view.image; + } + // console.log('snapshotView view.frame:', printRect(view.frame)); + const originalOpacity = view.layer.opacity; + view.layer.opacity = originalOpacity > 0 ? originalOpacity : 1; + UIGraphicsBeginImageContextWithOptions(CGSizeMake(view.frame.size.width, view.frame.size.height), false, scale); + view.layer.renderInContext(UIGraphicsGetCurrentContext()); + const image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + setTimeout(() => { + // ensure set back properly on next tick + view.layer.opacity = originalOpacity; + }); + return image; +} + +function copyLayerProperties(view: UIView, toView: UIView, customProperties?: { view?: Array; layer?: Array }) { + const viewPropertiesToMatch: Array = customProperties?.view || ['backgroundColor']; + const layerPropertiesToMatch: Array = customProperties?.layer || ['cornerRadius', 'borderWidth', 'borderColor']; + + viewPropertiesToMatch.forEach((property) => { + if (view[property] !== toView[property]) { + // console.log('| -- matching view property:', property); + view[property as any] = toView[property]; + } + }); + + layerPropertiesToMatch.forEach((property) => { + if (view.layer[property] !== toView.layer[property]) { + // console.log('| -- matching layer property:', property); + view.layer[property as any] = toView.layer[property]; + } + }); +} + +function animateWithSpring(options?: { tension?: number; friction?: number; mass?: number; delay?: number; velocity?: number; animateOptions?: UIViewAnimationOptions; animations?: () => void; completion?: (finished?: boolean) => void }) { + // for convenience, default spring settings are provided + const opt = { + ...CORE_ANIMATION_DEFAULTS.spring, + delay: 0, + animateOptions: null, + animations: null, + completion: null, + ...(options || {}), + }; + const { duration, damping } = getDurationWithDampingFromSpring(opt); + + if (duration === 0) { + UIView.animateWithDurationAnimationsCompletion(0, opt.animations, opt.completion); + return; + } + UIView.animateWithDurationDelayUsingSpringWithDampingInitialSpringVelocityOptionsAnimationsCompletion(duration, opt.delay, damping, opt.velocity, opt.animateOptions, opt.animations, opt.completion); +} + // these don't exist on iOS. Stub them to empty functions. export const ad = platformCheck('Utils.ad'); export const android = platformCheck('Utils.android'); -export import ios = iOSUtils; +export const ios = { + collections, + createUIDocumentInteractionControllerDelegate, + getCurrentAppPath, + getRootViewController, + getVisibleViewController, + getWindow, + getMainScreen, + setWindowBackgroundColor, + isLandscape, + applyRotateTransform, + snapshotView, + joinPaths, + printCGRect, + copyLayerProperties, + animateWithSpring, + MajorVersion, +}; /** * @deprecated Use `Utils.ios` instead. */ -export import iOSNativeHelper = iOSUtils; +export const iOSNativeHelper = ios;