import { Application , IOSHelper , Page , Screen , Trace , Utils , View , fromObject } from '@nativescript/core' ; import { applyMixins } from '@nativescript-community/ui-material-core' ; import { BottomSheetOptions } from './bottomsheet' ; import { StateBottomSheet , ViewWithBottomSheetBase } from './bottomsheet-common' ; export { ViewWithBottomSheetBase } from './bottomsheet-common' ; const majorVersion = Utils . ios . MajorVersion ; declare class IMDCBottomSheetControllerDelegateImpl extends NSObject implements MDCBottomSheetControllerDelegate { static new ( ) ; _owner : WeakRef < ViewWithBottomSheet > ; } @ NativeClass class MDCBottomSheetControllerDelegateImpl extends NSObject { static ObjCProtocols = [ MDCBottomSheetControllerDelegate ] ; _owner : WeakRef < ViewWithBottomSheet > ; public static initWithOwner ( owner ) { const delegate = MDCBottomSheetControllerDelegateImpl . new ( ) as MDCBottomSheetControllerDelegateImpl ; delegate . _owner = new WeakRef ( owner ) ; return delegate ; } bottomSheetControllerDidChangeYOffsetYOffset ( controller , yOffset ) { const owner = this . _owner . get ( ) ; if ( owner && owner . _onChangeStateBottomSheetCallback ) { const presentationController = controller . presentationController as MDCBottomSheetPresentationController ; const heightScreen = Screen . mainScreen . heightDIPs ; const heightCollapsedSheet = presentationController . preferredSheetHeight || controller . preferredContentSize . height ; const window = UIApplication . sharedApplication . windows . firstObject ; const topPadding = window . safeAreaInsets . top ; const bottomPadding = window . safeAreaInsets . bottom ; const isCollapsed = heightScreen - yOffset - bottomPadding === heightCollapsedSheet ; const isExpanded = yOffset === topPadding ; if ( ! isCollapsed && ! isExpanded ) { let normalized = 0 ; if ( yOffset + bottomPadding > heightScreen - heightCollapsedSheet ) { normalized = ( heightScreen - yOffset - bottomPadding - 0 ) / ( heightCollapsedSheet - 0 ) - 1 ; } else { normalized = ( heightScreen - yOffset - heightCollapsedSheet ) / ( heightScreen - heightCollapsedSheet ) ; } owner . _onChangeStateBottomSheetCallback ( StateBottomSheet . DRAGGING , normalized ) ; } else { owner . _onChangeStateBottomSheetCallback ( isCollapsed ? StateBottomSheet . COLLAPSED : StateBottomSheet . EXPANDED ) ; } } } bottomSheetControllerDidDismissBottomSheet ( controller ) { const owner = this . _owner . get ( ) ; if ( owner ) { owner . _onDismissBottomSheetCallback && owner . _onDismissBottomSheetCallback ( ) ; if ( owner && owner . isLoaded ) { owner . callUnloaded ( ) ; } } } bottomSheetControllerStateChangedState ( controller , state ) { const owner = this . _owner . get ( ) ; if ( state === MDCSheetState . Closed ) { if ( owner ) { owner . _onDismissBottomSheetCallback && owner . _onDismissBottomSheetCallback ( ) ; owner . _onChangeStateBottomSheetCallback && owner . _onChangeStateBottomSheetCallback ( StateBottomSheet . CLOSED ) ; if ( owner && owner . isLoaded ) { owner . callUnloaded ( ) ; } } } else { if ( owner && owner . _onChangeStateBottomSheetCallback ) { owner . _onChangeStateBottomSheetCallback ( state === MDCSheetState . Extended ? StateBottomSheet . EXPANDED : StateBottomSheet . EXPANDED ) ; } } } } function initLayoutGuide ( controller ) { const rootView = controller . view ; const layoutGuide = UILayoutGuide . alloc ( ) . init ( ) ; rootView . addLayoutGuide ( layoutGuide ) ; NSLayoutConstraint . activateConstraints ( [ layoutGuide . topAnchor . constraintEqualToAnchor ( controller . topLayoutGuide . bottomAnchor ) , layoutGuide . bottomAnchor . constraintEqualToAnchor ( controller . bottomLayoutGuide . topAnchor ) , layoutGuide . leadingAnchor . constraintEqualToAnchor ( rootView . leadingAnchor ) , layoutGuide . trailingAnchor . constraintEqualToAnchor ( rootView . trailingAnchor ) ] as any ) ; return layoutGuide ; } function layoutView ( controller , owner ) { let layoutGuide = controller . view . safeAreaLayoutGuide ; if ( ! layoutGuide ) { Trace . write ( `safeAreaLayoutGuide during layout of ${ owner }. Creating fallback constraints, but layout might be wrong.` , Trace . categories . Layout , Trace . messageType . error ) ; layoutGuide = initLayoutGuide ( controller ) ; } const safeArea = layoutGuide . layoutFrame ; let position = IOSHelper . getPositionFromFrame ( safeArea ) ; const safeAreaSize = safeArea . size ; const hasChildViewControllers = controller . childViewControllers . count > 0 ; if ( hasChildViewControllers ) { const fullscreen = controller . view . frame ; position = IOSHelper . getPositionFromFrame ( fullscreen ) ; } const safeAreaWidth = Utils . layout . round ( Utils . layout . toDevicePixels ( safeAreaSize . width ) ) ; const safeAreaHeight = Utils . layout . round ( Utils . layout . toDevicePixels ( safeAreaSize . height ) ) ; const widthSpec = Utils . layout . makeMeasureSpec ( safeAreaWidth , Utils . layout . EXACTLY ) ; const heightSpec = Utils . layout . makeMeasureSpec ( safeAreaHeight , Utils . layout . UNSPECIFIED ) ; ( owner as any ) . _cachedFrame = null ; View . measureChild ( null , owner , widthSpec , heightSpec ) ; const marginTop = owner . effectiveMarginTop ; const marginBottom = owner . effectiveMarginBottom ; const marginLeft = owner . effectiveMarginLeft + position . left ; const marginRight = owner . effectiveMarginRight ; const top = marginTop + position . top ; const width = Screen . mainScreen . widthPixels ; const height = owner . getMeasuredHeight ( ) ; View . layoutChild ( null , owner , marginLeft , top , width - marginLeft - marginRight , position . top + height + marginBottom ) ; const effectiveWidth = width ; let effectiveHeight = height + top + marginBottom ; if ( controller . ignoreTopSafeArea || controller . ignoreBottomSafeArea ) { const frame = CGRectMake ( Utils . layout . toDeviceIndependentPixels ( marginLeft ) , Utils . layout . toDeviceIndependentPixels ( top ) , Utils . layout . toDeviceIndependentPixels ( width ) , Utils . layout . toDeviceIndependentPixels ( height ) ) ; const availableSpace = getAvailableSpaceFromParent ( owner , frame ) ; const startPos = IOSHelper . getPositionFromFrame ( frame ) ; const fullscreenPosition = IOSHelper . getPositionFromFrame ( availableSpace . fullscreen ) ; const safeAreaPosition = IOSHelper . getPositionFromFrame ( availableSpace . safeArea ) ; const adjustedPosition = startPos ; const orientation = UIDevice . currentDevice . orientation ; const isLandscape = Application . orientation ( ) === 'landscape' ; owner . iosIgnoreSafeArea = true ; if ( controller . ignoreTopSafeArea ) { const key = 'top' ; const oppositeKey = 'bottom' ; const delta = safeAreaPosition [ key ] - fullscreenPosition [ key ] ; effectiveHeight -= delta ; adjustedPosition [ oppositeKey ] -= delta ; adjustedPosition [ key ] -= delta ; } if ( controller . ignoreBottomSafeArea ) { const key = 'bottom' ; const delta = fullscreenPosition [ key ] - safeAreaPosition [ key ] ; effectiveHeight -= delta ; } if ( orientation === UIDeviceOrientation . LandscapeRight || orientation === UIDeviceOrientation . LandscapeLeft ) { const key = 'left' ; const oppositeKey = 'right' ; const delta = fullscreenPosition [ key ] - safeAreaPosition [ key ] ; adjustedPosition [ oppositeKey ] += Utils . layout . toDevicePixels ( delta ) ; } owner . nativeViewProtected . frame = CGRectMake ( Utils . layout . toDeviceIndependentPixels ( adjustedPosition . left ) , Utils . layout . toDeviceIndependentPixels ( adjustedPosition . top ) , Utils . layout . toDeviceIndependentPixels ( adjustedPosition . right - adjustedPosition . left ) , Utils . layout . toDeviceIndependentPixels ( adjustedPosition . bottom - adjustedPosition . top ) ) ; } controller . preferredContentSize = CGSizeMake ( Utils . layout . toDeviceIndependentPixels ( effectiveWidth ) , Utils . layout . toDeviceIndependentPixels ( effectiveHeight ) ) ; if ( owner . parent ) { owner . parent . _layoutParent ( ) ; } } function getAvailableSpaceFromParent ( view , frame ) { if ( ! view ) { return null ; } let scrollView = null ; let viewControllerView = null ; if ( view . viewController ) { viewControllerView = view . viewController . view ; } else { let parent = view . parent as View ; while ( parent && ! parent . viewController && ! ( parent . nativeViewProtected instanceof UIScrollView ) ) { parent = parent . parent as View ; } if ( parent . nativeViewProtected instanceof UIScrollView ) { scrollView = parent . nativeViewProtected ; } else if ( parent . viewController ) { viewControllerView = parent . viewController . view ; } } let fullscreen = null ; let safeArea = null ; if ( viewControllerView ) { safeArea = viewControllerView . safeAreaLayoutGuide . layoutFrame ; fullscreen = viewControllerView . frame ; } else if ( scrollView ) { const insets = scrollView . safeAreaInsets ; safeArea = CGRectMake ( insets . left , insets . top , scrollView . contentSize . width - insets . left - insets . right , scrollView . contentSize . height - insets . top - insets . bottom ) ; fullscreen = CGRectMake ( 0 , 0 , scrollView . contentSize . width , scrollView . contentSize . height ) ; } return { safeArea , fullscreen } ; } declare class IUILayoutViewController extends UIViewController { static new ( ) ; static alloc ( ) ; owner : WeakRef < View > ; nsAnimated : boolean ; ignoreBottomSafeArea : boolean ; ignoreTopSafeArea : boolean ; } @ NativeClass class UILayoutViewController extends UIViewController { owner : WeakRef < View > ; ignoreBottomSafeArea : boolean ; ignoreTopSafeArea : boolean ; nsAnimated : boolean ; public static initWithOwner ( owner ) { const delegate = UILayoutViewController . new ( ) as UILayoutViewController ; delegate . owner = new WeakRef ( owner ) ; return delegate ; } viewDidLoad ( ) { super . viewDidLoad ( ) ; const owner = this . owner . get ( ) ; if ( ! owner ) { return ; } if ( ! owner . parent ) { owner . callLoaded ( ) ; } this . edgesForExtendedLayout = UIRectEdge . All ; this . extendedLayoutIncludesOpaqueBars = true ; } viewWillLayoutSubviews ( ) { super . viewWillLayoutSubviews ( ) ; const owner = this . owner . get ( ) ; if ( owner ) { owner . iosOverflowSafeArea = true ; IOSHelper . updateConstraints ( this , owner ) ; } } viewDidLayoutSubviews ( ) { super . viewDidLayoutSubviews ( ) ; const owner = this . owner . get ( ) ; if ( owner ) { layoutView ( this , owner ) ; } } viewWillAppear ( animated ) { super . viewWillAppear ( animated ) ; const owner = this . owner . get ( ) ; if ( ! owner ) { return ; } IOSHelper . updateAutoAdjustScrollInsets ( this , owner ) ; } viewDidDisappear ( animated ) { super . viewDidDisappear ( animated ) ; } traitCollectionDidChange ( previousTraitCollection ) { super . traitCollectionDidChange ( previousTraitCollection ) ; if ( majorVersion >= 13 ) { const owner = this . owner . get ( ) ; if ( owner && this . traitCollection . hasDifferentColorAppearanceComparedToTraitCollection && this . traitCollection . hasDifferentColorAppearanceComparedToTraitCollection ( previousTraitCollection ) ) { owner . notify ( { eventName : 'traitCollectionColorAppearanceChanged' , object : owner } ) ; } } } } export class ViewWithBottomSheet extends ViewWithBottomSheetBase { bottomSheetControllerDelegate : IMDCBottomSheetControllerDelegateImpl ; bottomSheetController : MDCBottomSheetController ; protected _showNativeBottomSheet ( parent , options ) { options . context = options . context || { } ; const parentWithController = IOSHelper . getParentWithViewController ( parent ) ; if ( ! parentWithController ) { Trace . write ( `Could not find parent with viewController for ${ parent } while showing bottom sheet view.` , Trace . categories . ViewHierarchy , Trace . messageType . error ) ; return ; } const parentController = parentWithController . viewController ; if ( parentController . presentedViewController ) { Trace . write ( 'Parent is already presenting view controller. Close the current bottom sheet page before showing another one!' , Trace . categories . ViewHierarchy , Trace . messageType . error ) ; return ; } if ( ! parentController . view || ! parentController . view . window ) { Trace . write ( 'Parent page is not part of the window hierarchy.' , Trace . categories . ViewHierarchy , Trace . messageType . error ) ; return ; } this . _setupAsRootView ( { } ) ; this . _commonShowNativeBottomSheet ( parentWithController , options ) ; let controller = this . viewController ; if ( ! controller ) { const nativeView = this . ios || this . nativeViewProtected ; controller = UILayoutViewController . initWithOwner ( this ) ; if ( options . ignoreBottomSafeArea !== undefined ) { controller . ignoreBottomSafeArea = options . ignoreBottomSafeArea ; } else { controller . ignoreBottomSafeArea = false ; } if ( options . ignoreTopSafeArea !== undefined ) { controller . ignoreTopSafeArea = options . ignoreTopSafeArea ; } else { controller . ignoreTopSafeArea = true ; } if ( nativeView instanceof UIView ) { controller . view . addSubview ( nativeView ) ; } this . viewController = controller ; } this . _raiseShowingBottomSheetEvent ( ) ; const bottomSheet = ( this . bottomSheetController = MDCBottomSheetController . alloc ( ) . initWithContentViewController ( controller ) ) ; this . bottomSheetControllerDelegate = bottomSheet . delegate = MDCBottomSheetControllerDelegateImpl . initWithOwner ( this ) ; bottomSheet . isScrimAccessibilityElement = true ; bottomSheet . scrimAccessibilityLabel = 'close' ; bottomSheet . dismissOnBackgroundTap = options . dismissOnBackgroundTap !== false ; bottomSheet . dismissOnDraggingDownSheet = options . dismissOnDraggingDownSheet !== false ; const peekHeight = options . peekHeight ; if ( peekHeight ) { ( bottomSheet . presentationController as MDCBottomSheetPresentationController ) . preferredSheetHeight = peekHeight ; } ( bottomSheet . presentationController as MDCBottomSheetPresentationController ) . ignoreKeyboardHeight = options . ignoreKeyboardHeight !== false ; if ( options . trackingScrollView ) { const scrollView = this . getViewById ( options . trackingScrollView ) ; if ( scrollView && scrollView . nativeViewProtected instanceof UIScrollView ) { bottomSheet . trackingScrollView = scrollView . nativeViewProtected ; } } controller . nsAnimated = true ; parentController . presentViewControllerAnimatedCompletion ( bottomSheet , true , null ) ; if ( options . transparent === true ) { controller . view . backgroundColor = UIColor . clearColor ; controller . view [ 'elevation' ] = 0 ; } else if ( ! ( this instanceof Page ) ) { controller . view . backgroundColor = majorVersion <= 12 && ! UIColor . systemBackgroundColor ? UIColor . whiteColor : UIColor . systemBackgroundColor ; } const transitionCoordinator = bottomSheet . transitionCoordinator ; if ( transitionCoordinator ) { UIViewControllerTransitionCoordinator . prototype . animateAlongsideTransitionCompletion . call ( transitionCoordinator , null , ( ) => { this . bindingContext = fromObject ( options . context ) ; this . _raiseShownBottomSheetEvent ( ) ; } ) ; } else { this . bindingContext = fromObject ( options . context ) ; this . _raiseShownBottomSheetEvent ( ) ; } } _bottomSheetClosed ( ) { if ( this . bottomSheetController ) { this . bottomSheetController . delegate = null ; this . bottomSheetController = null ; } this . bottomSheetControllerDelegate = null ; } protected _hideNativeBottomSheet ( parent , whenClosedCallback ) { const parentWithController = IOSHelper . getParentWithViewController ( parent ) ; if ( ! parent || ! parentWithController ) { Trace . error ( 'Trying to hide bottom-sheet view but no parent with viewController specified.' ) ; return ; } const parentController = parentWithController . viewController ; const animated = this . viewController . nsAnimated ; parentController . dismissViewControllerAnimatedCompletion ( animated , whenClosedCallback ) ; } } let mixinInstalled = false ; export function overrideBottomSheet ( ) { applyMixins ( View , [ ViewWithBottomSheetBase , ViewWithBottomSheet ] ) ; } export function install ( ) { if ( ! mixinInstalled ) { mixinInstalled = true ; overrideBottomSheet ( ) ; } }