// Global Imports import * as React from "react"; import { Modal, View, FlatList, KeyboardAvoidingView, NativeSyntheticEvent, NativeScrollEvent, Platform, SafeAreaView, TouchableOpacity, BackHandler, } from "react-native"; // Local Imports import { AlphabetComponent, ListItemComponent, SearchComponent, ScrollToTopComponent, SelectBoxComponent, } from "@Components"; import { IModalProps, IModalListInDto, IModalState } from "@Interfaces"; import { ModalStyles, CommonStyle } from "@Styles"; import { generateAlphabet, getFilteredData, getIndex } from "@Helpers"; export class ModalComponent extends React.PureComponent< IModalProps, IModalState > { private flatListRef = null; private backHandler = null; private numToRender: number = 20; public state: IModalState = { modalVisible: false, searchText: "", stickyBottomButton: false, selectedAlpha: null, selectedObject: {} as IModalListInDto, }; public static defaultProps = { showToTopButton: true, modalAnimationType: "slide", showAlphabeticalIndex: false, searchInputTextColor: "#252525", autoGenerateAlphabeticalIndex: false, sortingLanguage: "tr", removeClippedSubviews: false, selectPlaceholderText: "Choose one...", searchPlaceholderText: "Search...", autoSort: false, items: [], disabled: false, requireSelection: false, }; private viewabilityConfig: { minimumViewTime: number; waitForInteraction: boolean; viewAreaCoveragePercentThreshold: number; }; constructor(props: IModalProps) { super(props); this._onViewableItemsChanged = this._onViewableItemsChanged.bind(this); this.viewabilityConfig = { minimumViewTime: 500, waitForInteraction: true, viewAreaCoveragePercentThreshold: 95, }; this.backHandler = BackHandler.addEventListener( "hardwareBackPress", () => { this._onClose(); return true; } ); } private _clearComponent(): void { this.setState({ stickyBottomButton: false, searchText: "", selectedAlpha: null, }); } public clearComponent(): void { this._clearComponent(); } public componentDidMount(): void { const { autoGenerateAlphabeticalIndex, alphabeticalIndexChars, items, sortingLanguage, } = this.props; if (autoGenerateAlphabeticalIndex) { this.setState({ alphabeticalIndexChars: generateAlphabet( items, sortingLanguage ), }); } else if (alphabeticalIndexChars) { this.setState({ alphabeticalIndexChars, }); } } private _openModal(): void { const { items, autoGenerateAlphabeticalIndex, disabled, sortingLanguage, } = this.props; if (autoGenerateAlphabeticalIndex) { this.setState({ alphabeticalIndexChars: generateAlphabet( items, sortingLanguage ), }); } if (items.length > 0 && !disabled) { this.setState({ modalVisible: true, }); } } public openModal(): void { this._openModal(); } public render(): JSX.Element { const { autoSort, modalAnimationType, onClosed, showAlphabeticalIndex, searchInputTextColor, keyExtractor, showToTopButton, onEndReached, removeClippedSubviews, FlatListProps, selectPlaceholderText, searchPlaceholderText, SearchInputProps, selected, disabled, items, requireSelection, renderSelectView, ModalProps, backButtonDisabled, renderSearch, } = this.props; const { modalVisible, alphabeticalIndexChars, stickyBottomButton, selectedAlpha, selectedObject, searchText, } = this.state; return ( onClosed} {...ModalProps} > {renderSearch ? ( renderSearch( this.onClose.bind(this), this.onBackRequest.bind(this) ) ) : ( this.setText(text)} backButtonDisabled={backButtonDisabled} {...SearchInputProps} /> )} (this.flatListRef = ref)} keyExtractor={ keyExtractor ? keyExtractor : (item, index) => index.toString() } data={getFilteredData( items, autoSort, searchText )} renderItem={({ item, index }) => this.renderItem(item, index) } onScroll={ showToTopButton && this.onScrolling.bind(this) } initialNumToRender={this.numToRender} keyboardShouldPersistTaps={"always"} keyboardDismissMode={"interactive"} onEndReached={onEndReached} maxToRenderPerBatch={20} legacyImplementation={false} updateCellsBatchingPeriod={50} removeClippedSubviews={ removeClippedSubviews } viewabilityConfig={this.viewabilityConfig} getItemLayout={(_, index) => ({ length: CommonStyle.BTN_HEIGHT, offset: CommonStyle.BTN_HEIGHT * index, index, })} onViewableItemsChanged={ this._onViewableItemsChanged } {...FlatListProps} /> this.setAlphabet(alphabet) } alphabets={alphabeticalIndexChars} selectedAlpha={selectedAlpha} /> ); } private _onViewableItemsChanged({ viewableItems }): void { if (viewableItems && viewableItems[0]) { const firstLetter = viewableItems[0].item.Name.charAt(0); this.setState({ selectedAlpha: firstLetter, }); } } private _onClose(): void { const { onClosed, onSelected, requireSelection, selected } = this.props; const { modalVisible, selectedObject } = this.state; if ( requireSelection && selectedObject && ![selectedObject.Id] && selected && ![selected.Id] ) return; if (!requireSelection) { onSelected({} as IModalListInDto); } this.setState({ selectedObject: {} as IModalListInDto, modalVisible: !modalVisible, }); this.clearComponent(); this.backHandler && this.backHandler.remove(); if (onClosed) { onClosed(); } } public onClose(): void { this._onClose(); } private _onBackRequest(): void { const { onBackButtonPressed } = this.props; const { modalVisible } = this.state; this.setState({ modalVisible: !modalVisible, }); this.clearComponent(); if (onBackButtonPressed) { onBackButtonPressed(); } } public onBackRequest(): void { this._onBackRequest(); } private _scrollToUp(): void { if (this.flatListRef) { this.setState( { selectedAlpha: null, }, () => { this.flatListRef.scrollToOffset({ animated: true, offset: 0, }); } ); } } public scrollToUp(): void { this._scrollToUp(); } private _onScrolling(e: NativeSyntheticEvent): void { const { contentOffset } = e.nativeEvent; if (contentOffset.y > 100) { this.setState({ stickyBottomButton: true, }); } else { this.setState({ stickyBottomButton: false, }); } } public onScrolling(e: NativeSyntheticEvent): void { this._onScrolling(e); } private _renderItem(item: IModalListInDto, index: number): JSX.Element { const { selected, renderListItem } = this.props; return ( (renderListItem && ( this.onSelectMethod(item)} > {renderListItem(selected, item)} )) || ( ) ); } public renderItem(item: IModalListInDto, index: number): JSX.Element { return this._renderItem(item, index); } private _setText(text: string): void { this.setState({ searchText: text, }); } public setText(text: string): void { this._setText(text); } private _onSelectMethod(key: IModalListInDto): IModalListInDto | void { const { onSelected } = this.props; this.setState({ modalVisible: false, selectedObject: key as IModalListInDto, }); this.clearComponent(); if (key && ![key.Id]) { return onSelected({} as IModalListInDto); } return onSelected(key); } public onSelectMethod(key: IModalListInDto): IModalListInDto | void { return this._onSelectMethod(key); } private _setAlphabet(alphabet: string): void { this.setState( { selectedAlpha: alphabet, }, () => { const list = getFilteredData( this.props.items, this.props.autoSort, this.state.searchText ); const findIndex = getIndex( alphabet, this.props.items, this.props.autoSort, this.state.searchText ); if ( findIndex >= 0 && findIndex <= list.length - this.numToRender / 2 ) { setTimeout(() => { this.flatListRef.scrollToIndex({ animated: true, index: findIndex, viewPosition: 0, }); }, 100); } else { this.flatListRef.scrollToEnd(); } } ); } public setAlphabet(alphabet: string): void { this._setAlphabet(alphabet); } }