UNPKG

react-native-gif-search

Version:

An easy-to-use, highly customizable react-native package for searching and selecting from a list of gifs and/or stickers using the Tenor and/or the Giphy API

600 lines (527 loc) 21 kB
import React, { PureComponent, useState } from 'react'; import { View, StyleSheet, Image, TouchableOpacity, TextInput, FlatList, BackHandler, ActivityIndicator, Dimensions, Keyboard, Text, } from 'react-native'; import Requests from './Requests'; const GIPHY_BASE_URL = 'https://api.giphy.com/v1/'; const TENOR_BASE_URL = 'https://api.tenor.com/v1/'; const providers = { TENOR: "tenor", GIPHY: "giphy", ALL: "all", } const gif_types = { GIF: "gif", STICKER: "sticker", ALL: "all", } const endpoints = { TRENDING: "trending", SEARCH: "search", } const giphyFormats = { low: "preview_gif", medium: "fixed_width", high: "downsized_large" } const tenorFormats = { low: "nanogif", medium: "tinygif", high: "mediumgif" } class GifSearch extends PureComponent { constructor(props) { super(props); this.gifsToLoad = 15; if (props.gifsToLoad != null) { this.gifsToLoad = props.gifsToLoad; } this.maxGifsToLoad = 60; if (props.maxGifsToLoad != null) { this.maxGifsToLoad = props.maxGifsToLoad; } this.placeholderTextColor = 'grey'; if (props.placeholderTextColor != null) { this.placeholderTextColor = props.placeholderTextColor; } this.giphyApiKey = ''; if (props.giphyApiKey != null) { this.giphyApiKey = props.giphyApiKey; } this.tenorApiKey = ''; if (props.tenorApiKey != null) { this.tenorApiKey = props.tenorApiKey; } this.loadingSpinnerColor = 'white'; if (props.loadingSpinnerColor != null) { this.loadingSpinnerColor = props.loadingSpinnerColor; } this.showScrollBar = true; if (props.showScrollBar != null) { this.showScrollBar = props.showScrollBar; } this.placeholderText = "Search GIFs"; if (props.placeholderText != null) { this.placeholderText = props.placeholderText; } this.stickersPlaceholderText = "Search Stickers"; if (props.stickersPlaceholderText != null) { this.stickersPlaceholderText = props.stickersPlaceholderText; } this.noGifsFoundText = "No GIFS found"; if (props.noGifsFoundText != null) { this.noGifsFoundText = props.noGifsFoundText; } this.horizontal = true; if (props.horizontal != null) { this.horizontal = props.horizontal; } this.numColumns = 1; if (props.numColumns != null) { this.numColumns = props.numColumns; this.horizontal = false; this.gifSize = Dimensions.get('window').width / this.numColumns - 15; } this.provider = providers.ALL; if (props.provider != null) { if (props.provider == providers.TENOR || props.provider == providers.GIPHY) { this.provider = props.provider; } } this.providerLogo = null; if (props.providerLogo != null) { this.providerLogo = props.providerLogo; } this.gifType = gif_types.GIF; var currentGifType = gif_types.GIF; if (props.gifType != null) { if (props.gifType == gif_types.STICKER) { this.gifType = props.gifType currentGifType = props.gifType; } if (props.gifType == gif_types.ALL) { this.gifType = props.gifType } } this.showGifsButtonText = "Gifs"; if (props.showGifsButtonText != null) { this.showGifsButtonText = props.showGifsButtonText; } this.showStickersButtonText = "Stickers"; if (props.showStickersButtonText != null) { this.showStickersButtonText = props.showStickersButtonText; } if (currentGifType == gif_types.STICKER) { this.provider = providers.GIPHY } this.previewGifQuality = "low"; if (props.previewGifQuality != null) { this.previewGifQuality = props.previewGifQuality; } this.selectedGifQuality = "medium"; if (props.selectedGifQuality != null) { this.selectedGifQuality = props.selectedGifQuality; } this.tenorGifPreview = tenorFormats[this.previewGifQuality]; this.tenorGifSelected = tenorFormats[this.selectedGifQuality]; this.tenorMediaFilter = this.calcTenorMediaFilter(this.tenorGifPreview, this.tenorGifSelected); this.giphyGifPreview = giphyFormats[this.previewGifQuality]; this.giphyGifSelected = giphyFormats[this.selectedGifQuality]; this.state = { gifs: [], offset: 0, next: 0, scrollOffset: 0, search_term: "", fetching: false, gifsOver: false, noGifsFound: false, currentGifType: currentGifType, currentProvider: this.provider, gifSize: this.gifSize, visible: props.visible != null ? props.visible : true, } } calcTenorMediaFilter = (tenorGifPreview, tenorGifSelected) => { if (tenorGifPreview == "mediumgif" || tenorGifSelected == "mediumgif") { return null; } if (tenorGifPreview == "nanogif" || tenorGifSelected == "nanogif") { return "basic"; } return "minimal"; } refreshGifs = () => { this.setState({fetching: true, gifsOver: false, noGifsFound: false}) if (this.state.search_term == ""){ this.showGifs(endpoints.TRENDING) } else { this.showGifs(endpoints.SEARCH) } } handleRequestResponses = (tenorResponseJSON, giphyResponseJSON) => { if (tenorResponseJSON == null) {tenorResponseJSON = {results:[]}} if (giphyResponseJSON == null) {giphyResponseJSON = {data:[]}} if (tenorResponseJSON.results.length > 0 || giphyResponseJSON.data.length > 0) { var tenor_gifs = this.addProviderAndTypeInfo(tenorResponseJSON.results, providers.TENOR) var giphy_gifs = this.addProviderAndTypeInfo(giphyResponseJSON.data, providers.GIPHY) var all_gifs = this.joinAndRemoveDuplicateIds(this.state.gifs, this.interleaveGifArrays(tenor_gifs, giphy_gifs)) this.setState({ gifs: all_gifs, offset: this.state.offset + this.gifsToLoad, next: tenorResponseJSON.next, fetching: false }) } else { this.setState({fetching: false, gifsOver: true, noGifsFound: this.state.gifs.length == 0}) } } handleRequestError = (error) => { this.setState({fetching: false, gifsOver: true}) if (this.props.onError) { this.props.onError(error) } } showGifs = (endpoint) => { const search_term = this.state.search_term; if (this.state.currentProvider == providers.ALL) { this.fetchTenorGifs(endpoint).then((tenorResponseJSON) => { this.fetchGiphyGifs(endpoint).then((giphyResponseJSON) => { if (search_term == this.state.search_term) { this.handleRequestResponses(tenorResponseJSON, giphyResponseJSON) } }).catch(this.handleRequestError) }).catch(this.handleRequestError) } else if (this.state.currentProvider == providers.TENOR) { this.fetchTenorGifs(endpoint).then((tenorResponseJSON) => { if (search_term == this.state.search_term) { this.handleRequestResponses(tenorResponseJSON, null) } }).catch(this.handleRequestError) } else if (this.state.currentProvider == providers.GIPHY) { this.fetchGiphyGifs(endpoint).then((giphyResponseJSON) => { if (search_term == this.state.search_term) { this.handleRequestResponses(null, giphyResponseJSON) } }).catch(this.handleRequestError) } } fetchTenorGifs = (endpoint) => { var limit = Math.min(this.gifsToLoad, this.maxGifsToLoad - this.state.offset) if (this.state.currentProvider == providers.ALL) { limit = Math.ceil(limit/2) } if (endpoint == endpoints.TRENDING) { return Requests.fetch("GET", TENOR_BASE_URL + "trending", { "key": this.tenorApiKey, "limit": limit, "locale": "el_GR", "contentfilter": "medium", ...this.tenorMediaFilter != null && {"media_filter": this.tenorMediaFilter}, ...this.state.next != 0 && {"pos": this.state.next}, ...this.props.tenorApiProps, }) } else if (endpoint == endpoints.SEARCH) { return Requests.fetch("GET", TENOR_BASE_URL + "search", { "key": this.tenorApiKey, "q": this.state.search_term, "limit": limit, "locale": "el_GR", "contentfilter": "medium", ...this.tenorMediaFilter != null && {"media_filter": this.tenorMediaFilter}, ...this.state.next != 0 && {"pos": this.state.next}, ...this.props.tenorApiProps, }) } } fetchGiphyGifs = (endpoint) => { var limit = Math.min(this.gifsToLoad, this.maxGifsToLoad - this.state.offset) if (this.state.currentProvider == providers.ALL) { limit = Math.floor(limit/2) } var base_url = GIPHY_BASE_URL; if (this.state.currentGifType == gif_types.GIF) { base_url += "gifs/" } else if (this.state.currentGifType == gif_types.STICKER) { base_url += "stickers/" } if (endpoint == endpoints.TRENDING) { return Requests.fetch("GET", base_url + "trending", { "api_key": this.giphyApiKey, "limit": limit, "rating": "pg", "offset": this.state.offset, ...this.props.giphyApiProps, }) } else if (endpoint == endpoints.SEARCH) { return Requests.fetch("GET", base_url + "search", { "api_key": this.giphyApiKey, "q": this.state.search_term, "limit": limit, "rating": "pg", "offset": this.state.offset, ...this.props.giphyApiProps, }) } } joinAndRemoveDuplicateIds = (old_array, new_array) => { var new_array_unique = new_array.filter((obj1, index1) => {return !new_array.some((obj2, index2) => obj1.id === obj2.id && index1 != index2)}); // remove duplicates new_array_unique = new_array_unique.filter(o1 => {return !old_array.some(o2 => o1.id === o2.id)}); // remove duplicates with previous gifs return [...old_array, ...new_array_unique] } addProviderAndTypeInfo = (gifs_array, provider) => { gifs_array.forEach((element) => { element.id = provider + " " + element.id; element.provider = provider; element.type = this.state.currentGifType; }); return gifs_array } interleaveGifArrays = (tenor_array, giphy_array) => { if (tenor_array.length == 0) {return giphy_array} if (giphy_array.length == 0) {return tenor_array} var result = []; var i, l = Math.min(tenor_array.length, giphy_array.length); for (i = 0; i < l; i++) { result.push(tenor_array[i], giphy_array[i]); } result.push(...tenor_array.slice(l), ...giphy_array.slice(l)); return result } onSearchTermChanged = (new_search_term) => { this.setState({search_term: new_search_term, offset: 0, gifs: [], tenorGifs: [], giphyGifs: [], next: 0}, () => { this.refreshGifs() }) } showGifsButtonPressed = () => { if (this.state.currentGifType != gif_types.GIF) { this.setState({currentGifType: gif_types.GIF, currentProvider: this.provider, offset: 0, gifs: [], tenorGifs: [], giphyGifs: [], next: 0}, () => { this.refreshGifs() }) } } showStickersButtonPressed = () => { if (this.state.currentGifType != gif_types.STICKER) { this.setState({currentGifType: gif_types.STICKER, currentProvider: providers.GIPHY, offset: 0, gifs: [], tenorGifs: [], giphyGifs: [], next: 0}, () => { this.refreshGifs() }) } } loadMoreGifs = () => { if (this.state.offset < this.maxGifsToLoad && !this.state.gifsOver) { this.refreshGifs() } } handleBackButtonClick = () => { var wasVisible = this.props.visible; this.props.onBackPressed() return wasVisible } componentDidMount() { this.refreshGifs() if (this.props.onBackPressed != null){ BackHandler.addEventListener('hardwareBackPress', this.handleBackButtonClick); } if (this.numColumns > 1) { Dimensions.addEventListener('change', () => { this.setState({gifSize: Dimensions.get('window').width / this.numColumns - 15}) }) } } componentWillUnmount() { if (this.props.onBackPressed != null){ BackHandler.removeEventListener('hardwareBackPress', this.handleBackButtonClick); } } render() { return ( this.props.visible == null || this.props.visible ? ( <View style={[this.styles.view, this.props.style]}> <View style={{flexDirection: 'row', alignSelf: 'stretch', alignItems: 'center', justifyContent: 'space-between' }}> <TextInput placeholder={this.state.currentGifType == gif_types.GIF ? (this.placeholderText) : (this.stickersPlaceholderText)} placeholderTextColor={this.placeholderTextColor} autoCapitalize={'none'} numberOfLines={1} style={[this.styles.textInput, this.props.textInputStyle]} onChangeText={this.onSearchTermChanged} value={this.state.search_term} {...this.props.textInputProps} /> {this.providerLogo != null ? ( <Image source={this.providerLogo} style={[this.styles.providerLogo, this.props.providerLogoStyle]} /> ) : (null) } </View> { this.gifType == gif_types.ALL ? ( <View style={{flexDirection:'row', alignItems:'center', marginTop: 5, marginBottom:15}}> <TouchableOpacity style={this.state.currentGifType != gif_types.GIF ? ([this.styles.gifTypeButton, this.props.showGifsButtonStyle]) : ([this.styles.gifTypeButton, this.props.showGifsButtonStyle, this.styles.gifTypeButtonSelected, this.props.showGifsButtonSelectedStyle])} onPress={this.showGifsButtonPressed} > <Text style={this.state.currentGifType != gif_types.GIF ? ([this.styles.gifTypeButtonText, this.props.showGifsButtonTextStyle]) : ([this.styles.gifTypeButtonText, this.props.showGifsButtonTextStyle, this.styles.gifTypeButtonSelectedText, this.props.showGifsButtonSelectedTextStyle])} > {this.showGifsButtonText} </Text> </TouchableOpacity> <TouchableOpacity style={this.state.currentGifType != gif_types.STICKER ? ([this.styles.gifTypeButton, this.styles.stickerTypeButton, this.props.showStickersButtonStyle]) : ([this.styles.gifTypeButton, this.styles.stickerTypeButton, this.props.showStickersButtonStyle, this.styles.gifTypeButtonSelected, this.props.showStickersButtonSelectedStyle])} onPress={this.showStickersButtonPressed} > <Text style={this.state.currentGifType != gif_types.STICKER ? ([this.styles.gifTypeButtonText, this.props.showStickersButtonTextStyle]) : ([this.styles.gifTypeButtonText, this.props.showStickersButtonTextStyle, this.styles.gifTypeButtonSelectedText, this.props.showStickersButtonSelectedTextStyle])} > {this.showStickersButtonText} </Text> </TouchableOpacity> </View> ) : (null) } <FlatList onEndReached={this.loadMoreGifs} onEndReachedThreshold={0.8} onScroll={this.handleScroll} keyboardShouldPersistTaps={"handled"} style={[this.styles.gifList, this.props.gifListStyle]} contentContainerStyle={{alignItems: 'center'}} data={this.state.gifs} horizontal={this.horizontal} numColumns={this.numColumns} showsHorizontalScrollIndicator={this.showScrollBar} showsVerticalScrollIndicator={this.showScrollBar} {...this.props.gifListProps} renderItem={({item, index}) => { var aspect_ratio = null; var gif_preview = null; var gif_better_quality = null; if (item.provider == providers.TENOR) { gif_preview = item.media[0][this.tenorGifPreview].url gif_better_quality = item.media[0][this.tenorGifSelected].url if (parseInt(item.media[0][this.tenorGifSelected].dims[1])) { aspect_ratio = parseInt(item.media[0][this.tenorGifSelected].dims[0])/parseInt(item.media[0][this.tenorGifSelected].dims[1]) } } else { gif_preview = item.images[this.giphyGifPreview].url gif_better_quality = item.images[this.giphyGifSelected].url if (parseInt(item.images[this.giphyGifSelected].height)) { aspect_ratio = parseInt(item.images[this.giphyGifSelected].width)/parseInt(item.images[this.giphyGifSelected].height) } } return ( <TouchableOpacity activeOpacity={0.7} onPress={() => {this.props.onGifSelected(gif_better_quality, item); Keyboard.dismiss()}} onLongPress={() => {this.props.onGifLongPress(gif_better_quality, item); Keyboard.dismiss()}} onLongPress={() => {if (this.props.onGifLongPress) {this.props.onGifLongPress(gif_better_quality, item)}}}> <Image resizeMode={'cover'} style={[this.styles.image, !this.horizontal && (index+1)%this.numColumns === 0 ? this.styles.lastColumnImage : {}, this.numColumns > 1 ? {width:this.state.gifSize, minHeight: this.state.gifSize, maxHeight:this.state.gifSize} : {aspectRatio: aspect_ratio ? aspect_ratio : 4/3, height: 150}, this.props.gifStyle]} source={{uri: gif_preview}} /> </TouchableOpacity> ) }} ListFooterComponent={( this.state.offset < this.maxGifsToLoad && !this.state.gifsOver? ( <View style={{ justifyContent:'center', width: 150, height: 150}}> <ActivityIndicator size="large" color={this.loadingSpinnerColor} /> </View> ) : (this.state.noGifsFound? ( <View style={{ justifyContent:'center', width: Dimensions.get('window').width*0.7}}> <Text style={[{textAlign: 'center', fontSize: 20, color: 'grey'}, this.props.noGifsFoundTextStyle]}>{this.noGifsFoundText}</Text> </View> ) : (null) ) )} /> </View> ) : (null) ); } styles = StyleSheet.create({ view: { flex: 1, alignItems: 'center', backgroundColor: 'black', padding: 10, }, textInput: { flex: 1, height: 50, fontSize: 20, marginHorizontal: 10, color: 'white' }, image: { borderWidth: 3, marginRight: 10, marginBottom: 10 }, lastColumnImage: { marginRight: 0, }, gifList: { height: 160, marginBottom: 10, }, gifTypeButton: { flex: 1, alignItems: "center", backgroundColor: "black", borderRadius: 15, padding: 6, marginRight: 2, borderColor: "black", borderWidth: 2 }, stickerTypeButton: { marginRight: 0, marginLeft: 2 }, gifTypeButtonSelected: { borderColor: "white", borderWidth: 2 }, gifTypeButtonText: { fontSize: 14, color: "#e3e3e3" }, gifTypeButtonSelectedText: { fontWeight: "bold", }, providerLogo: { width: '35%', height: 50, resizeMode: 'contain' } }); } export default GifSearch;