import { ItemEventData , KeyedTemplate , Observable , PercentLength , ProxyViewContainer , StackLayout , Template , Utils , View } from '@nativescript/core' ; import { ITEMDESELECTED , itemHeightProperty , ITEMLOADING , ITEMSELECTED , ItemsSource , ITEMTAP , itemTemplatesProperty , itemWidthProperty , LayoutTypeOptions , WearOsListViewBase } from './wear-os-listview-base' ; export class WearOsListView extends WearOsListViewBase { static itemLoadingEvent = ITEMLOADING ; static itemTapEvent = ITEMTAP ; nativeViewProtected : any ; listView : TNS_WearableRecyclerView ; circularScrollingEnabled : boolean = false ; useScalingScroll : boolean = false ; _itemWidth : any ; _itemHeight : any ; itemWidth : PercentLength ; itemHeight : PercentLength ; spanCount : number ; items : any [ ] | ItemsSource ; itemTemplate : string | Template ; _realizedItems = new Map < android . view . View , View > ( ) ; _random : java . util . Random ; _itemsSelected : any [ ] ; _staggeredMap : Map < number , number > ; private _androidViewId : number = - 1 ; constructor ( ) { super ( ) ; } createNativeView ( ) { this . _itemsSelected = [ ] ; this . _staggeredMap = new Map < number , number > ( ) ; this . _random = new java . util . Random ( ) ; this . listView = new TNS_WearableRecyclerView ( this . _context , new WeakRef < any > ( this ) ) ; return this . listView ; } initNativeView ( ) { super . initNativeView ( ) ; const that = new WeakRef ( this ) ; this . nativeViewProtected . setEnabled ( false ) ; if ( this . _androidViewId < 0 ) { this . _androidViewId = android . view . View . generateViewId ( ) ; } this . nativeViewProtected . setId ( this . _androidViewId ) ; this . listView . setEdgeItemsCenteringEnabled ( true ) ; const adapter = new TNS_WearOsListViewAdapter ( new WeakRef ( this ) ) ; adapter . owner = that ; adapter . setHasStableIds ( true ) ; ( this . listView as any ) . setAdapter ( adapter ) ; ( < any > this . listView ) . adapter = adapter ; const androidConfig = ( Utils . android . getApplicationContext ( ) as android . content . Context ) . getResources ( ) . getConfiguration ( ) ; const isCircleWatch = ( androidConfig as any ) . isScreenRound ( ) ; if ( isCircleWatch === true && this . useScalingScroll ) { const customScrollingLayoutCallback = new TNS_CustomScrollingLayoutCallback ( ) ; ( this . listView as any ) . setLayoutManager ( new androidx . wear . widget . WearableLinearLayoutManager ( this . _context , customScrollingLayoutCallback ) ) ; } else { ( this . listView as any ) . setLayoutManager ( new androidx . wear . widget . WearableLinearLayoutManager ( this . _context ) ) ; } if ( this . circularScrollingEnabled === true ) { this . listView . setCircularScrollingGestureEnabled ( true ) ; } const params = new androidx . recyclerview . widget . RecyclerView . LayoutParams ( - 1 , - 1 ) ; itemWidthProperty . coerce ( this ) ; itemHeightProperty . coerce ( this ) ; } disposeNativeView ( ) { const nativeView = this . nativeViewProtected ; nativeView . setAdapter ( null ) ; this . eachChildView ( view => { if ( view && view . parent ) { view . parent . _removeView ( view ) ; } return true ; } ) ; ( < any > nativeView ) . adapter . owner = null ; this . _clearRealizedCells ( ) ; super . disposeNativeView ( ) ; } onLoaded ( ) { super . onLoaded ( ) ; this . requestLayout ( ) ; } onLayout ( left , top , right , bottom ) { super . onLayout ( left , top , right , bottom ) ; this . refresh ( ) ; } refresh ( ) { const nativeView = this . listView as any ; if ( ! nativeView || ! nativeView . getAdapter ( ) ) { return ; } this . _realizedItems . forEach ( ( view , nativeView ) => { if ( ! ( view . bindingContext instanceof Observable ) ) { view . bindingContext = null ; } } ) ; nativeView . getAdapter ( ) . notifyDataSetChanged ( ) ; } scrollToIndex ( index ) { const nativeView = this . nativeViewProtected ; if ( nativeView ) { nativeView . setSelection ( index ) ; } } scrollToIndexAnimated ( index ) { const nativeView = this . nativeViewProtected ; if ( nativeView ) { nativeView . smoothScrollToPosition ( index ) ; } } get _childrenCount ( ) : number { return this . _realizedItems . size ; } eachChildView ( callback ) { this . _realizedItems . forEach ( ( view , nativeView ) => { if ( view . parent instanceof WearOsListView ) { callback ( view ) ; } else { if ( view . parent ) { callback ( < View > view . parent ) ; } } } ) ; } private _clearRealizedCells ( ) { this . _realizedItems . forEach ( ( view , nativeView ) => { if ( view . parent ) { if ( ! ( view . parent instanceof WearOsListView ) ) { this . _removeView ( view . parent ) ; } view . parent . _removeView ( view ) ; } } ) ; this . _realizedItems . clear ( ) ; this . _staggeredMap . clear ( ) ; } [ itemTemplatesProperty . getDefault ] ( ) { return null ; } [ itemTemplatesProperty . setNative ] ( value ) { this . _itemTemplatesInternal = new Array < KeyedTemplate > ( this . _defaultTemplate ) ; if ( value ) { this . _itemTemplatesInternal = this . _itemTemplatesInternal . concat ( value ) ; } ( this . listView as any ) . setAdapter ( new TNS_WearOsListViewAdapter ( new WeakRef ( this ) ) ) ; this . refresh ( ) ; } } @ NativeClass ( ) class TNS_CustomScrollingLayoutCallback extends androidx . wear . widget . WearableLinearLayoutManager . LayoutCallback { /** How much should we scale the icon at most. */ private static MAX_ICON_PROGRESS = 2 ; constructor ( ) { super ( ) ; } onLayoutFinished ( child , parent ) { const centerOffset = child . getHeight ( ) / 2.0 / parent . getHeight ( ) ; const yRelativeToCenterOffset = child . getY ( ) / parent . getHeight ( ) + centerOffset ; const progresstoCenter = Math . sin ( yRelativeToCenterOffset * Math . PI ) ; let mProgressToCenter = Math . abs ( 0.5 - yRelativeToCenterOffset ) ; mProgressToCenter = Math . min ( mProgressToCenter , TNS_CustomScrollingLayoutCallback . MAX_ICON_PROGRESS ) ; child . setScaleX ( 1 - mProgressToCenter ) ; child . setScaleY ( 1 - mProgressToCenter ) ; child . setX ( + ( 1 - progresstoCenter ) * 100 ) ; } } @ NativeClass ( ) class TNS_WearOsListViewAdapter extends androidx . recyclerview . widget . RecyclerView . Adapter < any > { owner : WeakRef < WearOsListView > ; constructor ( owner ) { super ( ) ; this . owner = owner ; return global . __native ( this ) ; } onCreateViewHolder ( parent , viewType ) { const owner = this . owner ? this . owner . get ( ) : null ; if ( ! owner ) { return null ; } const template = owner . _itemTemplatesInternal [ viewType ] ; let view = template . createView ( ) ; if ( view instanceof View && ! ( view instanceof ProxyViewContainer ) ) { owner . _addView ( view ) ; } else { const sp = new StackLayout ( ) ; sp . addChild ( view || owner . _getDefaultItemContent ( viewType ) ) ; owner . _addView ( sp ) ; view = sp ; } owner . _realizedItems . set ( view . nativeView , view ) ; return new TNS_WearOsListViewHolder ( new WeakRef ( view ) , new WeakRef ( owner ) ) ; } onBindViewHolder ( holder , index ) { const owner = this . owner ? this . owner . get ( ) : null ; if ( owner ) { let view = holder . view ; const args = < ItemEventData > { eventName : ITEMLOADING , object : owner , android : holder , ios : undefined , index , view : view } ; owner . notify ( args ) ; if ( args . view !== view ) { view = args . view ; ( holder . view as StackLayout ) . removeChildren ( ) ; ( holder . view as StackLayout ) . addChild ( args . view ) ; } if ( owner . layoutType === LayoutTypeOptions . STAGGERED ) { let random ; const max = Utils . layout . toDeviceIndependentPixels ( owner . _effectiveItemHeight ) ; const min = Utils . layout . toDeviceIndependentPixels ( owner . _effectiveItemHeight ) * ( 1 / 3 ) ; if ( min && max ) { if ( owner . _staggeredMap && owner . _staggeredMap . has ( index ) ) { random = owner . _staggeredMap . get ( index ) ; } else { random = ( owner . _random as java . util . Random ) . nextInt ( max - min + min ) + min ; if ( ! owner . _staggeredMap ) { owner . _staggeredMap = new Map < number , number > ( ) ; } owner . _staggeredMap . set ( index , random ) ; } view . height = random ; } } else { if ( owner . _itemHeight ) { view . height = Utils . layout . toDeviceIndependentPixels ( owner . _effectiveItemHeight ) ; } if ( owner . _itemWidth ) { view . width = Utils . layout . toDeviceIndependentPixels ( owner . _effectiveItemWidth ) ; } } owner . _prepareItem ( view , index ) ; } } getItemId ( i ) { const owner = this . owner ? this . owner . get ( ) : null ; let id = i ; if ( owner && owner . items ) { const item = ( owner as any ) . items . getItem ? ( owner as any ) . items . getItem ( i ) : owner . items [ i ] ; if ( item ) { id = owner . itemIdGenerator ( item , i , owner . items ) ; } } return long ( id ) ; } getItemCount ( ) { const owner = this . owner ? this . owner . get ( ) : null ; return owner && owner . items && owner . items . length ? owner . items . length : 0 ; } getItemViewType ( index ) { const owner = this . owner ? this . owner . get ( ) : null ; if ( owner ) { const template = owner . _getItemTemplate ( index ) ; return owner . _itemTemplatesInternal . indexOf ( template ) ; } return 0 ; } } @ NativeClass ( ) @ Interfaces ( [ android . view . View . OnClickListener , android . view . View . OnLongClickListener ] ) class TNS_WearOsListViewHolder extends androidx . recyclerview . widget . RecyclerView . ViewHolder { _selected : boolean = false ; constructor ( private owner , private list ) { super ( owner . get ( ) . nativeViewProtected ) ; const that = global . __native ( this ) ; owner . get ( ) . nativeViewProtected . setOnClickListener ( that ) ; owner . get ( ) . nativeViewProtected . setOnLongClickListener ( that ) ; return that ; } isSelected ( ) { return this . _selected ; } setIsSelected ( selected ) { this . _selected = selected ; } get view ( ) : View { return this . owner ? this . owner . get ( ) : null ; } onClick ( v ) { const listView = this . list . get ( ) ; const index = this . getAdapterPosition ( ) ; listView . notify < ItemEventData > ( { eventName : ITEMTAP , object : listView , index : index , view : this . view , android : v , ios : undefined } ) ; if ( listView . selectionBehavior !== 'Press' ) return ; const items = listView . items as any ; const item = items . getItem ? items . getItem ( index ) : items [ index ] ; if ( listView . multipleSelection ) { if ( this . isSelected ( ) ) { listView . _itemsSelected = listView . _itemsSelected . filter ( selected => { if ( selected !== item ) { return selected ; } } ) ; this . setIsSelected ( false ) ; listView . notify < ItemEventData > ( { eventName : ITEMDESELECTED , object : listView , index : index , view : this . view , android : v , ios : undefined } ) ; } else { this . setIsSelected ( true ) ; listView . _itemsSelected . push ( item ) ; listView . notify < ItemEventData > ( { eventName : ITEMSELECTED , object : listView , index : index , view : this . view , android : v , ios : undefined } ) ; } } else { if ( this . isSelected ( ) ) { listView . _itemsSelected . pop ( ) ; this . setIsSelected ( false ) ; listView . notify < ItemEventData > ( { eventName : ITEMDESELECTED , object : listView , index : index , view : this . view , android : v , ios : undefined } ) ; } else { this . setIsSelected ( true ) ; listView . _itemsSelected . push ( item ) ; listView . notify < ItemEventData > ( { eventName : ITEMSELECTED , object : listView , index : index , view : this . view , android : v , ios : undefined } ) ; } } } onLongClick ( v ) { const listView = this . list . get ( ) ; const index = this . getAdapterPosition ( ) ; if ( listView . selectionBehavior === 'LongPress' ) { const items = listView . items as any ; const item = items . getItem ? items . getItem ( index ) : items [ index ] ; if ( listView . multipleSelection ) { if ( this . isSelected ( ) ) { listView . _itemsSelected = listView . _itemsSelected . filter ( selected => { if ( selected !== item ) { return selected ; } } ) ; this . setIsSelected ( false ) ; listView . notify < ItemEventData > ( { eventName : ITEMDESELECTED , object : listView , index : index , view : this . view , android : v , ios : undefined } ) ; } else { this . setIsSelected ( true ) ; listView . _itemsSelected . push ( item ) ; listView . notify < ItemEventData > ( { eventName : ITEMSELECTED , object : listView , index : index , view : this . view , android : v , ios : undefined } ) ; } } else { if ( this . isSelected ( ) ) { listView . _itemsSelected . pop ( ) ; this . setIsSelected ( false ) ; listView . notify < ItemEventData > ( { eventName : ITEMDESELECTED , object : listView , index : index , view : this . view , android : v , ios : undefined } ) ; } else { this . setIsSelected ( true ) ; listView . _itemsSelected . push ( item ) ; listView . notify < ItemEventData > ( { eventName : ITEMSELECTED , object : listView , index : index , view : this . view , android : v , ios : undefined } ) ; } } } return true ; } } @ NativeClass ( ) class TNS_WearableRecyclerView extends androidx . wear . widget . WearableRecyclerView { constructor ( context , private owner ) { super ( context ) ; return global . __native ( this ) ; } onLayout ( changed , l , t , r , b ) { if ( changed ) { const owner = this . owner . get ( ) ; owner . onLayout ( l , t , r , b ) ; } super . onLayout ( changed , l , t , r , b ) ; } }