/* global alert */ import React, { Component } from 'react'; import { Linking, StyleSheet, View, Text, FlatList, TouchableHighlight, TouchableOpacity, Keyboard, KeyboardAvoidingView, Platform, Image, TextInput, ScrollView, } from 'react-native'; import { BlueNavigationStyle, BlueLoading, BlueCard, SafeBlueArea } from '../../BlueComponents'; import PropTypes from 'prop-types'; import { HodlHodlApi } from '../../class/hodl-hodl-api'; import Modal from 'react-native-modal'; import { Icon } from 'react-native-elements'; const CURRENCY_CODE_ANY = '_any'; const METHOD_ANY = '_any'; const styles = StyleSheet.create({ grayDropdownText: { fontSize: 17, fontWeight: "600", color: '#9AA0AA', }, modalContent: { backgroundColor: '#FFFFFF', padding: 22, justifyContent: 'center', alignItems: 'center', borderTopLeftRadius: 16, borderTopRightRadius: 16, borderColor: 'rgba(0, 0, 0, 0.1)', minHeight: 400, height: 400, }, modalContentShort: { backgroundColor: '#FFFFFF', padding: 22, justifyContent: 'center', alignItems: 'center', borderTopLeftRadius: 16, borderTopRightRadius: 16, borderColor: 'rgba(0, 0, 0, 0.1)', minHeight: 200, height: 200, }, bottomModal: { justifyContent: 'flex-end', margin: 0, }, Title: { fontWeight: 'bold', fontSize: 34, color: '#0c2550', }, BottomLine: { position: 'absolute', top: -10, left: 0, fontSize: 10, color: '#0c2550', }, grayDropdownTextContainer: { backgroundColor: '#EEF0F4', borderRadius: 20, width: 100, height: 35, top: 3, paddingLeft: 2, paddingBottom: 6, paddingTop: 6, paddingRight: 0, justifyContent: 'center', alignItems: 'center', flex: 0.65, flexDirection: 'row', }, grayTextContainerContainer: { backgroundColor: '#EEF0F4', borderRadius: 20, height: 44, justifyContent: 'center', alignItems: 'center', marginTop: 15, }, grayTextContainer: { width: '100%', alignItems: 'center', flex: 1, flexDirection: 'row', }, blueText: { color: '#2f5fb3', fontSize: 18, fontWeight: "600", }, allOffersText: { fontSize: 12, color: '#9AA0AA', position: 'absolute', top: 0, left: 15, }, locationText: { top: 0, left: 5, color: '#0c2550', fontSize: 20, fontWeight: '500', }, nicknameText: { color: '#0c2550', fontSize: 18, fontWeight: '600', }, blueTextContainer: { backgroundColor: '#CCDDF9', borderRadius: 20, width: 110, flex: 1, flexDirection: 'row', height: 36, paddingLeft: 8, justifyContent: 'center', alignItems: 'center', right: 4, position: 'absolute', }, searchInputContainer: { flexDirection: 'row', borderColor: '#EEF0F4', borderBottomColor: '#EEF0F4', borderWidth: 1.0, borderBottomWidth: 0.5, backgroundColor: '#EEF0F4', minHeight: 48, height: 48, marginHorizontal: 20, alignItems: 'center', marginVertical: 8, borderRadius: 26, width: '100%', }, }); const HodlApi = new HodlHodlApi(); export default class HodlHodl extends Component { static navigationOptions = ({ navigation }) => ({ ...BlueNavigationStyle(), title: '', }); constructor(props) { super(props); /** @type {AbstractWallet} */ let wallet = props.navigation.state.params.wallet; this.state = { isLoading: true, isChooseSideModalVisible: false, isChooseCountryModalVisible: false, isFiltersModalVisible: false, isChooseCurrencyVisible: false, isChooseMethodVisible: false, currency: false, // means no currency filtering is enabled by default method: false, // means no payment method filtering is enabled by default side: HodlHodlApi.FILTERS_SIDE_VALUE_SELL, // means 'show me sell offers as Im buying' wallet, offers: [], countries: [], // list of hodlhodl supported countries. filled later via api currencies: [], // list of hodlhodl supported currencies. filled later via api methods: [], // list of hodlhodl payment methods. filled later via api country: HodlHodlApi.FILTERS_COUNTRY_VALUE_GLOBAL, // country currently selected by user to display orders on screen. this is country code myCountryCode: HodlHodlApi.FILTERS_COUNTRY_VALUE_GLOBAL, // current user's country. filled later, via geoip api }; } /** * Fetch offers and set those offers into state * * @returns {Promise} */ async fetchOffers() { let pagination = { [HodlHodlApi.PAGINATION_LIMIT]: 200, }; let filters = { [HodlHodlApi.FILTERS_COUNTRY]: this.state.country, [HodlHodlApi.FILTERS_SIDE]: this.state.side, [HodlHodlApi.FILTERS_ASSET_CODE]: HodlHodlApi.FILTERS_ASSET_CODE_VALUE_BTC, [HodlHodlApi.FILTERS_INCLUDE_GLOBAL]: this.state.country === HodlHodlApi.FILTERS_COUNTRY_VALUE_GLOBAL, [HodlHodlApi.FILTERS_ONLY_WORKING_NOW]: true, // so there wont be any offers which user tries to open website says 'offer not found' }; if (this.state.currency) { filters[HodlHodlApi.FILTERS_CURRENCY_CODE] = this.state.currency; } if (this.state.method) { filters[HodlHodlApi.FILTERS_PAYMENT_METHOD_ID] = this.state.method; } let sort = { [HodlHodlApi.SORT_BY]: HodlHodlApi.SORT_BY_VALUE_PRICE, [HodlHodlApi.SORT_DIRECTION]: HodlHodlApi.SORT_DIRECTION_VALUE_ASC, }; const offers = await HodlApi.getOffers(pagination, filters, sort); this.setState({ offers, }); } async fetchMyCountry() { let myCountryCode = await HodlApi.getMyCountryCode(); this.setState({ myCountryCode, country: myCountryCode, // we start with orders from current country }); } /** * fetches all countries from API and sets them to state * * @returns {Promise} **/ async fetchListOfCountries() { let countries = await HodlApi.getCountries(); this.setState({ countries }); } /** * fetches all currencies from API and sets them to state * * @returns {Promise} **/ async fetchListOfCurrencies() { let currencies = await HodlApi.getCurrencies(); this.setState({ currencies }); } /** * fetches all payment methods from API and sets them to state * * @returns {Promise} **/ async fetchListOfMethods() { let methods = await HodlApi.getPaymentMethods(this.state.country || HodlHodlApi.FILTERS_COUNTRY_VALUE_GLOBAL); this.setState({ methods }); } async componentDidMount() { console.log('wallets/hodlHodl - componentDidMount'); try { await this.fetchMyCountry(); await this.fetchOffers(); } catch (Error) { alert(Error.message); return; } this.setState({ isLoading: false, }); this.fetchListOfCountries(); this.fetchListOfCurrencies(); this.fetchListOfMethods(); } async _refresh() { this.setState( { isLoading: true, }, async () => { await this.fetchOffers(); this.setState({ isLoading: false, }); }, ); } _onPress(item) { Linking.openURL('https://hodlhodl.com/offers/' + item.id); } _onCountryPress(item) { this.setState( { country: item.code, method: false, // invalidate currently selected payment method, as it is probably not valid for the new country currency: false, // invalidate currently selected currency, as it is probably not valid for the new country isChooseCountryModalVisible: false, isLoading: true, }, async () => { await this.fetchOffers(); this.setState({ isLoading: false, }); this.fetchListOfMethods(); // once selected country changed we fetch payment methods for this country }, ); } _onCurrencyPress(item) { this.setState( { currency: item.code === CURRENCY_CODE_ANY ? false : item.code, isLoading: true, isChooseCurrencyVisible: false, }, async () => { await this.fetchOffers(); this.setState({ isLoading: false, }); }, ); } _onMethodPress(item) { this.setState( { method: item.id === METHOD_ANY ? false : item.id, isLoading: true, isChooseMethodVisible: false, }, async () => { await this.fetchOffers(); this.setState({ isLoading: false, }); }, ); } _onSidePress(item) { this.setState( { isChooseSideModalVisible: false, isLoading: true, side: item.code, }, async () => { await this.fetchOffers(); this.setState({ isLoading: false, }); }, ); } getItemText(item) { let { title, description } = item; title = title || ''; let ret = title; if (description) { if (description.startsWith(title)) title = ''; ret = title + '\n' + description .split('\n') .slice(0, 2) .join('\n'); } if (ret.length >= 200) ret = ret.substr(0, 200) + '...'; return ret; } getMethodName(id) { for (let m of this.state.methods) { if (m.id === id) return m.name; } return ''; } getItemPrice(item) { let price = item.price.toString(); if (price.length > 8) price = Math.round(item.price).toString(); switch (item.currency_code) { case 'USD': return '$ ' + price; case 'GBP': return '£ ' + price; case 'RUB': return '₽ ' + price; case 'EUR': return '€ ' + price; case 'UAH': return '₴ ' + price; default: return price + (price.length >= 9 ? '' : ' ' + item.currency_code); // too lengthy prices dont render currency code } } getNativeCountryName() { if (this.state.country === this.state.myCountryCode) return 'Near me'; for (let c of this.state.countries) { if (c.code === this.state.country) return c.native_name; } return 'Global offers'; } renderChooseSideModal = () => { return ( { Keyboard.dismiss(); this.setState({ isChooseSideModalVisible: false }); }} > } data={[ { code: HodlHodlApi.FILTERS_SIDE_VALUE_SELL, name: "I'm buying bitcoin" }, { code: HodlHodlApi.FILTERS_SIDE_VALUE_BUY, name: "I'm selling bitcoin" }, ]} keyExtractor={(item, index) => item.code} renderItem={({ item, index, separators }) => ( this._onSidePress(item)} > {item.name} )} /> ); }; renderFiltersModal = () => { return ( { if (this.state.openNextModal) { const openNextModal = this.state.openNextModal; this.setState({ openNextModal: false, [openNextModal]: true, }); } }} onBackdropPress={() => { Keyboard.dismiss(); this.setState({ isFiltersModalVisible: false }); }} > } data={[ { code: 'currency', native_name: 'Currency' }, { code: 'method', native_name: 'Payment method' }, ]} keyExtractor={(item, index) => item.code} renderItem={({ item, index, separators }) => ( { if (item.code === 'currency') this.setState({ isFiltersModalVisible: false, openNextModal: 'isChooseCurrencyVisible' }); if (item.code === 'method') this.setState({ isFiltersModalVisible: false, openNextModal: 'isChooseMethodVisible' }); }} > {item.native_name} {item.code === 'currency' && ( {this.state.currency ? this.state.currency + ' ❯' : 'Detail ❯'} )} {item.code === 'method' && ( {' '} {this.state.method ? this.getMethodName(this.state.method) + ' ❯' : 'Detail ❯'} )} )} /> ); }; renderChooseContryModal = () => { let countries2render = []; // first, we include in the list current country for (let country of this.state.countries) { if (country.code === this.state.country) { countries2render.push(country); } } // next, we include option for user to set GLOBAL for offers countries2render.push({ code: HodlHodlApi.FILTERS_COUNTRY_VALUE_GLOBAL, name: 'Global offers', native_name: 'Global offers', }); // lastly, we include other countries for (let country of this.state.countries) { if (country.code !== this.state.country) { // except currently selected one if (this.state.countrySearchInput) { // if user typed something in search box we apply that filter if ( country.name.toLocaleLowerCase().includes(this.state.countrySearchInput.toLocaleLowerCase()) || country.native_name.toLocaleLowerCase().includes(this.state.countrySearchInput.toLocaleLowerCase()) ) { countries2render.push(country); } } else { // otherwise just put the country in the list countries2render.push(country); } } } return ( { Keyboard.dismiss(); this.setState({ isChooseCountryModalVisible: false }); }} > this.setState({ countrySearchInput: text })} placeholder={'Search..'} placeholderTextColor="#9AA0AA" value={this.state.countrySearchInput || ''} numberOfLines={1} style={{ fontSize: 17, flex: 1, marginHorizontal: 8, minHeight: 33, paddingLeft: 6, paddingRight: 6 }} /> } data={countries2render} keyExtractor={(item, index) => item.code} renderItem={({ item, index, separators }) => ( this._onCountryPress(item)} onShowUnderlay={separators.highlight} onHideUnderlay={separators.unhighlight} > {item.native_name} )} /> ); }; renderChooseCurrencyModal = () => { let currencies2render = []; // first, option to choose any currency currencies2render.push({ code: CURRENCY_CODE_ANY, name: 'Any', }); // lastly, we include other countries for (let curr of this.state.currencies) { if (this.state.currencySearchInput) { // if user typed something in search box we apply that filter if ( curr.name.toLocaleLowerCase().includes(this.state.currencySearchInput.toLocaleLowerCase()) || curr.code.toLocaleLowerCase().includes(this.state.currencySearchInput.toLocaleLowerCase()) ) { currencies2render.push(curr); } } else { // otherwise just put the country in the list currencies2render.push(curr); } } return ( { Keyboard.dismiss(); this.setState({ isChooseCurrencyVisible: false }); }} > this.setState({ currencySearchInput: text })} placeholder={'Search..'} placeholderTextColor="#9AA0AA" value={this.state.currencySearchInput || ''} numberOfLines={1} style={{ flex: 1, marginHorizontal: 8, minHeight: 33, paddingLeft: 6, paddingRight: 6 }} /> } data={currencies2render} keyExtractor={(item, index) => item.code} renderItem={({ item, index, separators }) => ( this._onCurrencyPress(item)} onShowUnderlay={separators.highlight} onHideUnderlay={separators.unhighlight} > {item.name} {item.code !== CURRENCY_CODE_ANY && '[' + item.code + ']'} )} /> ); }; renderChooseMethodModal = () => { let methods2render = []; // first, option to choose any currency methods2render.push({ id: METHOD_ANY, name: 'Any', }); // lastly, we include other countries for (let curr of this.state.methods) { if (this.state.methodSearchInput) { // if user typed something in search box we apply that filter if ( curr.name.toLocaleLowerCase().includes(this.state.methodSearchInput.toLocaleLowerCase()) || curr.type.toLocaleLowerCase().includes(this.state.methodSearchInput.toLocaleLowerCase()) ) { methods2render.push(curr); } } else { // otherwise just put the country in the list methods2render.push(curr); } } return ( { Keyboard.dismiss(); this.setState({ isChooseMethodVisible: false }); }} > this.setState({ methodSearchInput: text })} placeholder={'Search..'} placeholderTextColor="#9AA0AA" value={this.state.methodSearchInput || ''} numberOfLines={1} style={{ flex: 1, marginHorizontal: 8, minHeight: 33, paddingLeft: 6, paddingRight: 6 }} /> } data={methods2render} keyExtractor={(item, index) => item.id} renderItem={({ item, index, separators }) => ( this._onMethodPress(item)} onShowUnderlay={separators.highlight} onHideUnderlay={separators.unhighlight} > {item.name} {item.id !== METHOD_ANY && '[' + item.type + ']'} )} /> ); }; render() { return ( Powered by HodlHodl® Local Trader { this.setState({ isChooseSideModalVisible: true }); }} > {this.state.side === HodlHodlApi.FILTERS_SIDE_VALUE_SELL ? 'Buying' : 'Selling'} {/* All offers */} this.setState({ isChooseCountryModalVisible: true }) /* this.changeCountry() */}> {this.getNativeCountryName()} { this.setState({ isFiltersModalVisible: true }); }} > Filters {(this.state.isLoading && ) || ( this._refresh()} refreshing={this.state.isLoading} contentContainerStyle={{ flex: 1, justifyContent: 'center', paddingHorizontal: 0 }} style={{ marginTop: 24, flex: 1 }} ItemSeparatorComponent={() => } data={this.state.offers} ListEmptyComponent={() => No offers. Try to change "Near me" to Global offers!} renderItem={({ item, index, separators }) => ( this._onPress(item)} onShowUnderlay={separators.highlight} onHideUnderlay={separators.unhighlight} > {item.trader.login} {item.trader.trades_count > 0 ? Math.round(item.trader.rating * 100) + '%' : 'No rating'} {this.getItemText(item)} {this.getItemPrice(item)} Min/Max: {item.min_amount.replace('.00', '')} - {item.max_amount.replace('.00', '')} {item.currency_code} )} /> )} {this.renderChooseSideModal()} {this.renderChooseContryModal()} {this.renderFiltersModal()} {this.renderChooseCurrencyModal()} {this.renderChooseMethodModal()} ); } } HodlHodl.propTypes = { navigation: PropTypes.shape({ goBack: PropTypes.func, state: PropTypes.shape({ params: PropTypes.shape({ wallet: PropTypes.object, }), }), }), };