diff --git a/app/components/HistoryTileBox.js b/app/components/HistoryTileBox.js index 2d298d2..052d1e2 100644 --- a/app/components/HistoryTileBox.js +++ b/app/components/HistoryTileBox.js @@ -1,551 +1,562 @@ import React, { Component} from 'react'; import autoBind from 'auto-bind'; import PropTypes from 'prop-types'; import { SafeAreaView, ScrollView, View, FlatList, Text } from 'react-native'; import HistoryCard from './HistoryCard'; import utils from '../utils'; import DigestAuthRequest from 'digest-auth-request'; import uuid from 'react-native-uuid'; import moment from 'moment'; import momenttz from 'moment-timezone'; import styles from '../assets/styles/blink/_HistoryTileBox.scss'; class HistoryTileBox extends Component { constructor(props) { super(props); autoBind(this); this.state = { serverHistory: this.props.serverHistory, localHistory: this.props.localHistory, accountId: this.props.account ? this.props.account.id : '', password: this.props.password, targetUri: this.props.targetUri, favoriteUris: this.props.favoriteUris, blockedUris: this.props.blockedUris, isRefreshing: false, myInvitedParties: this.props.myInvitedParties } const echoTest = { remoteParty: '4444@sylk.link', displayName: 'Echo test', type: 'contact', label: 'Call to test microphone', id: uuid.v4(), tags: ['test'] }; this.echoTest = Object.assign({}, echoTest); const videoTest = { remoteParty: '3333@sylk.link', displayName: 'Video test', type: 'contact', label: 'Call to test video', id: uuid.v4(), tags: ['test'] }; this.videoTest = Object.assign({}, videoTest); this.ended = false; - } componentDidMount() { //this.getServerHistory(); + this.ended = false; } componentWillUnmount() { this.ended = true; } setTargetUri(uri, contact) { //console.log('Set target uri uri in history list', uri); this.props.setTargetUri(uri, contact); } deleteHistoryEntry(uri) { this.props.deleteHistoryEntry(uri); this.props.setTargetUri(uri); } setFavoriteUri(uri) { return this.props.setFavoriteUri(uri); } saveInvitedParties(room, uris) { + if (this.ended) { + return; + } + this.props.saveInvitedParties(room, uris); let myInvitedParties = this.state.myInvitedParties; if (myInvitedParties && myInvitedParties.hasOwnProperty(room)) { myInvitedParties[room] = uris; this.setState({myInvitedParties: myInvitedParties}); } } setBlockedUri(uri) { return this.props.setBlockedUri(uri); } renderItem(item) { let invitedParties = []; let uri = item.item.remoteParty; let username = uri.split('@')[0]; if (this.state.myInvitedParties && this.state.myInvitedParties.hasOwnProperty(username)) { invitedParties = this.state.myInvitedParties[username]; } return( ); } findObjectByKey(array, key, value) { for (var i = 0; i < array.length; i++) { if (array[i][key] === value) { return array[i]; } } return null; } //getDerivedStateFromProps(nextProps, state) { UNSAFE_componentWillReceiveProps(nextProps) { if (this.ended) { return; } if (nextProps.myInvitedParties !== this.state.myInvitedParties) { this.setState({myInvitedParties: nextProps.myInvitedParties}); } const { refreshHistory } = this.props; if (nextProps.refreshHistory !== refreshHistory) { this.getServerHistory(); } } getLocalHistory() { let history = this.state.localHistory; history.sort((a, b) => (a.startTime < b.startTime) ? 1 : -1) let known = []; let uri; history = history.filter((elem) => { uri = elem.remoteParty.toLowerCase(); if (known.indexOf(uri) <= -1) { elem.type = 'history'; if (!elem.tags) { elem.tags = []; } if (elem.tags.indexOf('history') === -1) { elem.tags.push('history'); } if (elem.tags.indexOf('local') === -1) { elem.tags.push('local'); } known.push(uri); return elem; } }); return history; } getFavoriteContacts() { let favoriteContacts = []; let displayName; let label; let conference; let contacts= this.props.contacts contacts = contacts.concat(this.videoTest); contacts = contacts.concat(this.echoTest); this.state.favoriteUris.forEach((uri) => { uri = uri.toLowerCase(); const contact_obj = this.findObjectByKey(contacts, 'remoteParty', uri); displayName = contact_obj ? contact_obj.displayName : uri; label = contact_obj ? contact_obj.label: null; conference = false; let tags = ['favorite']; const history_obj = this.findObjectByKey(this.state.serverHistory, 'remoteParty', uri); const startTime = history_obj? history_obj.startTime : null; const stopTime = history_obj? history_obj.stopTime : null; const duration = history_obj? history_obj.duration : 0; let media = history_obj? history_obj.media : ['audio']; tags.push('history'); if (uri.indexOf('@videoconference.') > -1) { displayName = uri.split('@')[0]; uri = uri.split('@')[0] + '@' + this.props.config.defaultConferenceDomain; conference = true; media = ['audio', 'video', 'chat']; } const item = { remoteParty: uri, displayName: displayName, conference: conference, media: media, type: 'contact', startTime: startTime, startTime: startTime, duration: duration, label: label, id: uuid.v4(), tags: tags }; favoriteContacts.push(item); }); return favoriteContacts; } getBlockedContacts() { let blockedContacts = []; let contact_obj; let displayName; let label; let contacts= this.props.contacts contacts = contacts.concat(this.videoTest); contacts = contacts.concat(this.echoTest); this.state.blockedUris.forEach((uri) => { contact_obj = this.findObjectByKey(contacts, 'remoteParty', uri); displayName = contact_obj ? contact_obj.displayName : uri; label = contact_obj ? contact_obj.label: null; const item = { remoteParty: uri.toLowerCase(), displayName: displayName, conference: false, type: 'contact', label: label, id: uuid.v4(), tags: ['blocked'] }; blockedContacts.push(item); }); return blockedContacts; } getServerHistory() { utils.timestampedLog('Requesting call history from server'); + if (this.ended) { + return; + } + let history = []; let localTime; let hasMissedCalls = false; this.setState({isRefreshing: true}); let getServerCallHistory = new DigestAuthRequest( 'GET', `${this.props.config.serverCallHistoryUrl}?action=get_history&realm=${this.state.accountId.split('@')[1]}`, this.state.accountId.split('@')[0], this.state.password ); // Disable logging getServerCallHistory.loggingOn = false; getServerCallHistory.request((data) => { if (data.success !== undefined && data.success === false) { console.log('Error getting call history from server', data.error_message); return; } if (data.received) { data.received.map(elem => {elem.direction = 'received'; return elem}); history = history.concat(data.received); } if (data.placed) { data.placed.map(elem => {elem.direction = 'placed'; return elem}); history = history.concat(data.placed); } history.sort((a, b) => (a.startTime < b.startTime) ? 1 : -1) if (history) { const known = []; history = history.filter((elem) => { elem.conference = false; if (!elem.tags) { elem.tags = []; } if (elem.remoteParty.indexOf('@conference.') > -1) { return null; } elem.remoteParty = elem.remoteParty.toLowerCase(); let username = elem.remoteParty.split('@')[0]; let isPhoneNumber = username.match(/^(\+|0)(\d+)$/); let contact_obj; if (this.props.contacts) { if (isPhoneNumber) { contact_obj = this.findObjectByKey(this.props.contacts, 'remoteParty', username); } else { contact_obj = this.findObjectByKey(this.props.contacts, 'remoteParty', elem.remoteParty); } } if (contact_obj) { elem.displayName = contact_obj.displayName; elem.photo = contact_obj.photo; if (isPhoneNumber) { elem.remoteParty = username; } // TODO update icon here } else { elem.photo = null; } if (elem.remoteParty.indexOf('@videoconference.') > -1) { elem.displayName = elem.remoteParty.split('@')[0]; elem.remoteParty = elem.remoteParty.split('@')[0] + '@' + this.props.config.defaultConferenceDomain; elem.conference = true; elem.media = ['audio', 'video', 'chat']; } if (elem.remoteParty === this.state.accountId) { elem.displayName = this.props.myDisplayName || 'Myself'; } elem.type = 'history'; elem.id = uuid.v4(); if (elem.tags.indexOf('history') === -1) { elem.tags.push('history'); } elem.label = elem.direction; if (!elem.displayName) { elem.displayName = elem.remoteParty; } if (!elem.media || !Array.isArray(elem.media)) { elem.media = ['audio']; } if (elem.remoteParty.indexOf('3333@') > -1) { // see Call.js as well if we change this elem.displayName = 'Video Test'; } if (elem.remoteParty.indexOf('4444@') > -1) { // see Call.js as well if we change this elem.displayName = 'Echo Test'; } if (elem.timezone !== undefined) { localTime = momenttz.tz(elem.startTime, elem.timezone).toDate(); elem.startTime = moment(localTime).format('YYYY-MM-DD HH:mm:ss'); localTime = momenttz.tz(elem.stopTime, elem.timezone).toDate(); elem.stopTime = moment(localTime).format('YYYY-MM-DD HH:mm:ss'); } if (known.indexOf(elem.remoteParty) <= -1) { known.push(elem.remoteParty); if (elem.direction === 'received' && elem.duration === 0) { elem.tags.push('missed'); hasMissedCalls = true; } return elem; } }); this.props.cacheHistory(history); + if (this.ended) { + return; + } this.setState({serverHistory: history, isRefreshing: false}); this.props.setMissedCalls(hasMissedCalls); } }, (errorCode) => { console.log('Error getting call history from server', errorCode); }); } render() { if (!this.state.accountId) { return null; } let history = []; let searchExtraItems = []; let items = []; if (this.props.filter === 'favorite') { let favoriteContacts = this.getFavoriteContacts(); items = favoriteContacts.filter(historyItem => historyItem.remoteParty.startsWith(this.props.targetUri)); } else if (this.props.filter === 'blocked') { let blockedContacts = this.getBlockedContacts(); items = blockedContacts.filter(historyItem => historyItem.remoteParty.startsWith(this.props.targetUri)); } else if (this.props.filter === 'missed') { history = this.state.serverHistory; items = history.filter(historyItem => historyItem.remoteParty.startsWith(this.props.targetUri) && historyItem.tags.indexOf('missed') > -1); } else { history = this.getLocalHistory(); history = history.concat(this.state.serverHistory); searchExtraItems = this.props.contacts; searchExtraItems.concat(this.videoTest); searchExtraItems.concat(this.echoTest); items = history.filter(historyItem => historyItem.remoteParty.startsWith(this.props.targetUri)); let matchedContacts = []; if (this.props.targetUri && this.props.targetUri.length > 2 && !this.props.selectedContact) { matchedContacts = searchExtraItems.filter(contact => (contact.remoteParty.toLowerCase().indexOf(this.props.targetUri) > -1 || contact.displayName.toLowerCase().indexOf(this.props.targetUri) > -1)); } else if (this.props.selectedContact && this.props.selectedContact.type === 'contact') { matchedContacts.push(this.props.selectedContact); } items = items.concat(matchedContacts); } const known = []; items = items.filter((elem) => { if (known.indexOf(elem.remoteParty) <= -1) { known.push(elem.remoteParty); return elem; } }); items.sort((a, b) => (a.startTime < b.startTime) ? 1 : -1) if (!this.props.targetUri && !this.props.filter) { if (!this.findObjectByKey(items, 'remoteParty', this.echoTest.remoteParty)) { items.push(this.echoTest); } if (!this.findObjectByKey(items, 'remoteParty', this.videoTest.remoteParty)) { items.push(this.videoTest); } } items.forEach((item) => { item.showActions = false; if (!item.tags) { item.tags = []; } if (this.state.favoriteUris.indexOf(item.remoteParty) > -1 && item.tags.indexOf('favorite') === -1) { item.tags.push('favorite'); } if (this.state.blockedUris.indexOf(item.remoteParty) > -1 && item.tags.indexOf('blocked') === -1) { item.tags.push('blocked'); } let idx = item.tags.indexOf('blocked'); if (this.state.blockedUris.indexOf(item.remoteParty) === -1 && idx > -1) { item.tags.splice(idx, 1); } idx = item.tags.indexOf('favorite'); if (this.state.favoriteUris.indexOf(item.remoteParty) === -1 && idx > -1) { item.tags.splice(idx, 1); } }); let filteredItems = []; items.forEach((item) => { if (this.props.filter && item.tags.indexOf(this.props.filter) > -1) { filteredItems.push(item); } else if (this.state.blockedUris.indexOf(item.remoteParty) === -1) { filteredItems.push(item); } }); items = filteredItems; if (items.length === 1) { items[0].showActions = true; } let columns = 1; if (this.props.isTablet) { columns = this.props.orientation === 'landscape' ? 3 : 2; } else { columns = this.props.orientation === 'landscape' ? 2 : 1; } return ( item.id} key={this.props.orientation} /> ); } } HistoryTileBox.propTypes = { account : PropTypes.object, password : PropTypes.string.isRequired, config : PropTypes.object.isRequired, targetUri : PropTypes.string, selectedContact : PropTypes.object, contacts : PropTypes.array, orientation : PropTypes.string, setTargetUri : PropTypes.func, isTablet : PropTypes.bool, refreshHistory : PropTypes.bool, cacheHistory : PropTypes.func, serverHistory : PropTypes.array, localHistory : PropTypes.array, myDisplayName : PropTypes.string, myPhoneNumber : PropTypes.string, setFavoriteUri : PropTypes.func, saveInvitedParties: PropTypes.func, myInvitedParties: PropTypes.object, setBlockedUri : PropTypes.func, deleteHistoryEntry : PropTypes.func, favoriteUris : PropTypes.array, blockedUris : PropTypes.array, setMissedCalls : PropTypes.func, filter : PropTypes.string, defaultDomain : PropTypes.string }; export default HistoryTileBox; diff --git a/app/components/IncomingCallModal.js b/app/components/IncomingCallModal.js index a3852ce..47d661b 100644 --- a/app/components/IncomingCallModal.js +++ b/app/components/IncomingCallModal.js @@ -1,117 +1,117 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import autoBind from 'auto-bind'; import UserIcon from './UserIcon'; import { Headline, IconButton, Title, Portal, Modal, Surface } from 'react-native-paper'; import { Platform, View } from 'react-native'; import styles from '../assets/styles/blink/_IncomingCallModal.scss'; function findObjectByKey(array, key, value) { for (var i = 0; i < array.length; i++) { if (array[i][key] === value) { return array[i]; } } return null; } class IncomingCallModal extends Component { constructor(props) { super(props); autoBind(this); } answerAudioOnly() { this.props.onAccept(this.props.call.id, {audio: true, video: false}); } answer() { this.props.onAccept(this.props.call.id, {audio: true, video: true}); }; reject() { this.props.onReject(this.props.call.id); }; render() { let answerButtons = []; if (!this.props.call) { return null; } answerButtons.push( - + ); let callType = 'audio'; if (this.props.call.mediaTypes.video) { callType = 'video'; /* answerButtons.push( ); */ } answerButtons.push( - + ); let remoteUri = this.props.call.remoteIdentity.uri; let remoteDisplayName = this.props.call.remoteIdentity.displayName || this.props.call.remoteIdentity.uri; let username = remoteUri.split('@')[0]; let isPhoneNumber = username.match(/^(\+|0)(\d+)$/); if (isPhoneNumber) { var contact_obj = findObjectByKey(this.props.contacts, 'remoteParty', username); } else { var contact_obj = findObjectByKey(this.props.contacts, 'remoteParty', remoteUri); } if (contact_obj) { remoteDisplayName = contact_obj.displayName; if (isPhoneNumber) { remoteUri = username; } } else { if (isPhoneNumber) { remoteUri = username; remoteDisplayName = username; } } let remoteIdentity = {uri: remoteUri, displayName: remoteDisplayName}; return ( {remoteDisplayName} is calling with {callType} {answerButtons} ); } } IncomingCallModal.propTypes = { call : PropTypes.object, onAccept : PropTypes.func.isRequired, onReject : PropTypes.func.isRequired, compact : PropTypes.bool, show : PropTypes.bool, contacts : PropTypes.array }; export default IncomingCallModal; diff --git a/app/components/NotificationCenter.js b/app/components/NotificationCenter.js index 8f09c0f..b0b8225 100644 --- a/app/components/NotificationCenter.js +++ b/app/components/NotificationCenter.js @@ -1,171 +1,185 @@ import React, { Component } from 'react'; import { ProgressBar, Colors, Snackbar } from 'react-native-paper'; import moment from 'moment'; import autoBind from 'auto-bind'; import styles from '../assets/styles/blink/_StatusBox.scss'; import config from '../config'; class NotificationCenter extends Component { constructor(props) { super(props); autoBind(this); this.state = { visible: false, message: null, title: null, autoDismiss: null, action: null } + this.ended = false; } componentDidMount() { //console.log('Notification Center mounted'); + this.ended = false; } componentWillUnmount() { //console.log('Notification Center will unmount'); + this.ended = true; } postSystemNotification(title, options={}) { // eslint-disable-line space-infix-ops + if (this.ended) { + return; + } + this.setState({ visible: true, autoDismiss: 5, title: title, message: options.body, action: null }); } postConferenceInvite(originator, room, cb) { // if (originator.uri.endsWith(config.defaultGuestDomain)) { // return; // } + if (this.ended) { + return; + } + const idx = room.indexOf('@'); if (idx === -1) { return; } const currentDate = moment().format('MMMM Do YYYY [at] HH:mm:ss'); const action = { label: 'Join', onPress: () => { cb(room); } }; this.setState({ visible: true, message: `${(originator.displayName || originator.uri)} invited you to join conference room ${room.substring(0, idx)} on ${currentDate}`, title: 'Conference Invite', autoDismiss: 20, action: action, }); } postMissedCall(originator, cb) { + if (this.ended) { + return; + } const currentDate = moment().format('MMMM Do YYYY [at] HH:mm:ss'); let action; if (originator.uri.endsWith(config.defaultGuestDomain)) { action = null; } else { action = { label: 'Call', onPress: () => { cb(originator.uri); } }; } this.setState({ visible: true, message: `From ${(originator.displayName || originator.uri)}
On ${currentDate}`, title: 'Missed Call', autoDismiss: 0, action: action }); } postFileUploadProgress(filename, cb) { this.setState({ visible: true, message: `${filename}`, title: 'Uploading file', autoDismiss: 0, action: { label: 'OK', onPress: () => cb() }, // children: ( // // // // ) }); } editFileUploadNotification(progress, notification) { if (progress === undefined) { progress = 100; } this.setState({ visible: true, message: `${filename}`, title: 'Upload Successful', autoDismiss: 3, action: null }); } removeFileUploadNotification(notification) { let timer = setTimeout(() => { this.setState({visible: false}); }, 3000); } removeNotification(notification) { this.setState({visible: false}); } postFileUploadFailed(filename) { this.setState({ visible: true, message: `Uploading of ${filename} failed`, title: 'File sharing failed', autoDismiss: 10, action: null }); } postFileShared(file, cb) { const uploader = file.uploader.displayName || file.uploader.uri || file.uploader; this.setState({ visible: true, message: `${uploader} shared ${file.filename}`, title: 'File shared', autoDismiss: 10, action: { label: 'Show Files', onPress: () => cb() } }); } render() { //console.log('showing snackbar'); return ( this.setState({ visible: false, message: null, title: null })} action={this.state.action} > {this.state.title} {this.state.message} ); } } export default NotificationCenter; diff --git a/app/components/ReadyBox.js b/app/components/ReadyBox.js index 9304623..07e45de 100644 --- a/app/components/ReadyBox.js +++ b/app/components/ReadyBox.js @@ -1,295 +1,312 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import autoBind from 'auto-bind'; import { View, Platform} from 'react-native'; import { IconButton, Title, Button } from 'react-native-paper'; import ConferenceModal from './ConferenceModal'; import HistoryTileBox from './HistoryTileBox'; import FooterBox from './FooterBox'; import URIInput from './URIInput'; import config from '../config'; import utils from '../utils'; import styles from '../assets/styles/blink/_ReadyBox.scss'; class ReadyBox extends Component { constructor(props) { super(props); autoBind(this); this.state = { targetUri: '', contacts: this.props.contacts, selectedContact: null, showConferenceModal: false, sticky: false, favoriteUris: this.props.favoriteUris, blockedUris: this.props.blockedUris, historyFilter: null, missedCalls: false, participants: null, myInvitedParties: this.props.myInvitedParties }; + this.ended = false; } UNSAFE_componentWillReceiveProps(nextProps) { + if (this.ended) { + return; + } this.setState({myInvitedParties: nextProps.myInvitedParties}); } getTargetUri() { return utils.normalizeUri(this.state.targetUri, this.props.defaultDomain); } async componentDidMount() { + this.ended = false; + } + + componentWillUnmount() { + this.ended = true; } setMissedCalls(flag) { + if (this.ended) { + return; + } + this.setState({missedCalls: flag}); } filterHistory(filter) { + if (this.ended) { + return; + } + this.setState({'historyFilter': filter}); this.handleTargetChange(''); } handleTargetChange(value, contact) { let new_value = value; if (contact) { if (this.state.targetUri === contact.uri) { new_value = ''; } } if (this.state.targetUri === value) { new_value = ''; } if (new_value === '') { contact = null; } this.setState({targetUri: new_value, selectedContact: contact}); } handleTargetSelect() { if (this.props.connection === null) { this.props._notificationCenter.postSystemNotification("Server unreachable", {timeout: 2}); return; } // the user pressed enter, start a video call by default if (this.state.targetUri.endsWith(`@${config.defaultConferenceDomain}`)) { let participants; if (this.state.myInvitedParties && this.state.myInvitedParties.hasOwnProperty(this.state.targetUri)) { participants = this.state.myInvitedParties[this.state.targetUri]; } this.props.startConference(this.state.targetUri, {audio: true, video: true, participants: this.state.participants}); } else { this.props.startCall(this.getTargetUri(), {audio: true, video: true}); } } showConferenceModal(event) { event.preventDefault(); this.setState({showConferenceModal: true}); return; if (this.state.targetUri.length !== 0) { const uri = `${this.state.targetUri.split('@')[0].replace(/[\s()-]/g, '')}@${config.defaultConferenceDomain}`; this.handleConferenceCall(uri.toLowerCase()); } else { this.setState({showConferenceModal: true}); } } handleAudioCall(event) { event.preventDefault(); if (this.state.targetUri.endsWith(`@${config.defaultConferenceDomain}`)) { this.props.startConference(this.state.targetUri, {audio: true, video: false}); } else { this.props.startCall(this.getTargetUri(), {audio: true, video: false}); } } handleVideoCall(event) { event.preventDefault(); if (this.state.targetUri.endsWith(`@${config.defaultConferenceDomain}`)) { this.props.startConference(this.state.targetUri, {audio: true, video: false}); } else { this.props.startCall(this.getTargetUri(), {audio: true, video: true}); } } handleConferenceCall(targetUri, options={audio: true, video: true, participants: []}) { this.props.startConference(targetUri, {audio: true, video: options.video, participants: options.participants}); this.setState({showConferenceModal: false}); } conferenceButtonActive() { if (this.state.targetUri.indexOf('@') > -1 && this.state.targetUri.indexOf(config.defaultConferenceDomain) === -1) { return false; } if (this.state.targetUri.match(/^(\+)(\d+)$/)) { return false; } return true; } render() { let uriClass = styles.portraitUriInputBox; let uriGroupClass = styles.portraitUriButtonGroup; let titleClass = styles.portraitTitle; const buttonClass = (Platform.OS === 'ios') ? styles.iosButton : styles.androidButton; if (this.props.isTablet) { titleClass = this.props.orientation === 'landscape' ? styles.landscapeTabletTitle : styles.portraitTabletTitle; } else { titleClass = this.props.orientation === 'landscape' ? styles.landscapeTitle : styles.portraitTitle; } if (this.props.isTablet) { uriGroupClass = this.props.orientation === 'landscape' ? styles.landscapeTabletUriButtonGroup : styles.portraitTabletUriButtonGroup; } else { uriGroupClass = this.props.orientation === 'landscape' ? styles.landscapeUriButtonGroup : styles.portraitUriButtonGroup; } if (this.props.isTablet) { uriClass = this.props.orientation === 'landscape' ? styles.landscapeTabletUriInputBox : styles.portraitTabletUriInputBox; } else { uriClass = this.props.orientation === 'landscape' ? styles.landscapeUriInputBox : styles.portraitUriInputBox; } const historyClass = this.props.orientation === 'landscape' ? styles.landscapeHistory : styles.portraitHistory; return ( -1} onPress={this.handleAudioCall} icon="phone" /> -1} onPress={this.handleVideoCall} icon="video" /> {((this.state.favoriteUris.length > 0 || this.state.blockedUris.length > 0 ) || (this.state.favoriteUris.length === 0 && this.state.historyFilter === 'favorite') || (this.state.blockedUris.length === 0 && this.state.historyFilter === 'blocked') || (this.state.historyFilter === 'missed') ) ? {this.state.historyFilter !== null ? : null} {(this.state.favoriteUris.length > 0 && this.state.historyFilter !== 'favorite')? : null} {(this.state.blockedUris.length > 0 && this.state.historyFilter !== 'blocked')? : null} {(this.state.missedCalls && this.state.historyFilter !== 'missed')? : null} : null} {this.props.isTablet && 0? : null} ); } } ReadyBox.propTypes = { account : PropTypes.object, password : PropTypes.string.isRequired, config : PropTypes.object.isRequired, startCall : PropTypes.func.isRequired, startConference : PropTypes.func.isRequired, contacts : PropTypes.array, orientation : PropTypes.string, isTablet : PropTypes.bool, refreshHistory : PropTypes.bool, cacheHistory : PropTypes.func, serverHistory : PropTypes.array, localHistory : PropTypes.array, myDisplayName : PropTypes.string, myPhoneNumber : PropTypes.string, deleteHistoryEntry: PropTypes.func, setFavoriteUri : PropTypes.func, myInvitedParties: PropTypes.object, setBlockedUri : PropTypes.func, favoriteUris : PropTypes.array, blockedUris : PropTypes.array, defaultDomain : PropTypes.string }; export default ReadyBox;