diff --git a/app/components/ConferenceModal.js b/app/components/ConferenceModal.js index d04ecfd..f353b34 100644 --- a/app/components/ConferenceModal.js +++ b/app/components/ConferenceModal.js @@ -1,154 +1,152 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { View } from 'react-native'; import { Portal, Dialog, Button, Text, TextInput, Surface, Chip } from 'react-native-paper'; import KeyboardAwareDialog from './KeyBoardAwareDialog'; const DialogType = Platform.OS === 'ios' ? KeyboardAwareDialog : Dialog; import config from '../config'; import styles from '../assets/styles/blink/_ConferenceModal.scss'; class ConferenceModal extends Component { constructor(props) { super(props); this.state = { targetUri: props.targetUri ? props.targetUri.split('@')[0] : '', myInvitedParties: props.myInvitedParties, participants: null }; this.handleConferenceTargetChange = this.handleConferenceTargetChange.bind(this); this.onHide = this.onHide.bind(this); this.joinAudio = this.joinAudio.bind(this); this.joinVideo = this.joinVideo.bind(this); this.handleConferenceTargetChange(this.state.targetUri); } //getDerivedStateFromProps(nextProps, state) { UNSAFE_componentWillReceiveProps(nextProps) { let uri = ''; if (nextProps.targetUri) { uri = nextProps.targetUri.split('@')[0]; } - console.log('Conference modal', nextProps.myInvitedParties); - this.setState({targetUri: uri, myInvitedParties: nextProps.myInvitedParties}); this.handleConferenceTargetChange(uri); } handleConferenceTargetChange(value) { let targetUri = value; let participants = null; let sanitizedParticipants = []; let username; let domain; if (targetUri) { let uri = `${targetUri.replace(/[\s()-]/g, '')}@${config.defaultConferenceDomain}`; uri = uri.split('@')[0].toLowerCase(); if (this.props.myInvitedParties && this.props.myInvitedParties.hasOwnProperty(uri)) { participants = this.props.myInvitedParties[uri].toString(); participants.split(',').forEach((item) => { item = item.trim(); if (item.indexOf('@') === -1) { item = `${item}@${config.defaultDomain}`; } username = item.split('@')[0]; domain = item.split('@')[1]; if (username && username !== ',') { if (domain === config.defaultDomain) { sanitizedParticipants.push(username); } else { sanitizedParticipants.push(item); } } }); } } this.setState({targetUri: targetUri, participants: sanitizedParticipants.toString()}); } joinAudio(event) { event.preventDefault(); const uri = `${this.state.targetUri.replace(/[\s()-]/g, '')}@${config.defaultConferenceDomain}`; this.props.handleConferenceCall(uri.toLowerCase(), {audio: true, video: false, participants: this.state.participants.split(',')}); } joinVideo(event) { event.preventDefault(); const uri = `${this.state.targetUri.replace(/[\s()-]/g, '')}@${config.defaultConferenceDomain}`; this.props.handleConferenceCall(uri.toLowerCase(), {audio: true, video: true, participants: this.state.participants.split(',')}); } onHide() { this.props.handleConferenceCall(null); } render() { const validUri = this.state.targetUri.length > 0 && this.state.targetUri.indexOf('@') === -1; return ( Join Conference {this.setState({participants: value});}} value={this.state.participants} placeholder="bob,carol,alice@sip2sip.info" /> ); } } ConferenceModal.propTypes = { show: PropTypes.bool.isRequired, handleConferenceCall: PropTypes.func.isRequired, myInvitedParties: PropTypes.object, targetUri: PropTypes.string.isRequired }; export default ConferenceModal; diff --git a/app/components/HistoryCard.js b/app/components/HistoryCard.js index c74e26a..42e1ba9 100644 --- a/app/components/HistoryCard.js +++ b/app/components/HistoryCard.js @@ -1,375 +1,392 @@ import React, { Component, Fragment} from 'react'; import { View, SafeAreaView, FlatList } from 'react-native'; import autoBind from 'auto-bind'; import PropTypes from 'prop-types'; import moment from 'moment'; import momentFormat from 'moment-duration-format'; import { Card, IconButton, Button, Caption, Title, Subheading, List, Text} from 'react-native-paper'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import uuid from 'react-native-uuid'; import EditConferenceModal from './EditConferenceModal'; import styles from '../assets/styles/blink/_HistoryCard.scss'; import UserIcon from './UserIcon'; function toTitleCase(str) { return str.replace( /\w\S*/g, function(txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); } ); } const Item = ({ nr, uri, displayName }) => ( {displayName !== uri? - {nr}. {displayName} ({uri}) + {displayName} ({uri}) : {nr}. {uri} } ); const renderItem = ({ item }) => ( ); class HistoryCard extends Component { constructor(props) { super(props); autoBind(this); this.state = { id: this.props.contact.id, displayName: this.props.contact.displayName, uri: this.props.contact.remoteParty, invitedParties: this.props.invitedParties, participants: this.props.contact.participants, conference: this.props.contact.conference, type: this.props.contact.type, photo: this.props.contact.photo, label: this.props.contact.label, orientation: this.props.orientation, isTablet: this.props.isTablet, favorite: (this.props.contact.tags.indexOf('favorite') > -1)? true : false, blocked: (this.props.contact.tags.indexOf('blocked') > -1)? true : false, confirmed: false, showEditConferenceModal: false } } + UNSAFE_componentWillReceiveProps(nextProps) { + this.setState({ + id: nextProps.contact.id, + displayName: nextProps.contact.displayName, + uri: nextProps.contact.remoteParty, + invitedParties: nextProps.invitedParties, + participants: nextProps.contact.participants, + conference: nextProps.contact.conference, + type: nextProps.contact.type, + photo: nextProps.contact.photo, + label: nextProps.contact.label, + orientation: nextProps.orientation, + favorite: (nextProps.contact.tags.indexOf('favorite') > -1)? true : false, + blocked: (nextProps.contact.tags.indexOf('blocked') > -1)? true : false, + }); + } + shouldComponentUpdate(nextProps) { //https://medium.com/sanjagh/how-to-optimize-your-react-native-flatlist-946490c8c49b return true; } toggleEditConferenceModal() { this.setState({showEditConferenceModal: !this.state.showEditConferenceModal}); } saveInvitedParties(uris) { this.setState({invitedParties: uris}); this.props.saveInvitedParties(this.state.uri.split('@')[0], uris); } handleParticipant() { } findObjectByKey(array, key, value) { for (var i = 0; i < array.length; i++) { if (array[i][key] === value) { return array[i]; } } return null; } setBlockedUri() { let newBlockedState = this.props.setBlockedUri(this.state.uri); this.setState({blocked: newBlockedState}); } editConference() { this.toggleEditConferenceModal(); } deleteHistoryEntry() { this.props.deleteHistoryEntry(this.state.uri); } undo() { this.setState({confirmed: false, action: null}); } setFavoriteUri() { if (this.state.favorite) { if (this.state.confirmed) { let newFavoriteState = this.props.setFavoriteUri(this.state.uri); this.setState({favorite: newFavoriteState, action: null, confirmed: false}); this.props.setTargetUri(this.state.uri); } else { this.setState({confirmed: true}); } } else { let newFavoriteState = this.props.setFavoriteUri(this.state.uri); this.setState({favorite: newFavoriteState}); } } setTargetUri(uri, contact) { if (this.isAnonymous(this.state.uri)) { return; } this.props.setTargetUri(uri, this.contact); } isAnonymous(uri) { if (uri.indexOf('@guest.') > -1 || uri.indexOf('@anonymous.') > -1) { return true } return false; } render () { let containerClass = styles.portraitContainer; let cardClass = styles.card; let showActions = this.props.contact.showActions && this.props.contact.tags.indexOf('test') === -1; let uri = this.state.uri; let displayName = this.state.displayName; let buttonMode = 'text'; let showBlockButton = uri.indexOf('@videoconference.') === -1 ? true : false; let showFavoriteButton = true; let showUndoButton = this.state.confirmed ? true : false; let showDeleteButton = (this.props.contact.tags.indexOf('local') > -1 && !this.state.favorite ) ? true: false; let showEditButton = (uri.indexOf('@videoconference.') > -1 && this.state.favorite && !this.state.confirmed ) ? true: false; let blockTextbutton = 'Block'; let editTextbutton = 'Edit'; let favoriteTextbutton = 'Favorite'; let undoTextbutton = 'Abort'; let deleteTextbutton = 'Delete'; let participantsData = []; if (this.isAnonymous(uri)) { uri = 'anonymous@anonymous.invalid'; displayName = displayName + ' - from the Web'; let showFavoriteButton = false; } if (this.state.favorite) { favoriteTextbutton = this.state.confirmed ? 'Confirm' : 'Remove favorite'; if (!this.state.blocked) { showBlockButton = false; } } if (uri.indexOf('3333@') > -1) { showBlockButton = false; } if (uri.indexOf('4444@') > -1) { showBlockButton = false; } if (displayName === 'Myself') { showBlockButton = false; } if (this.state.blocked) { blockTextbutton = 'Unblock'; showFavoriteButton = false; } if (this.state.isTablet) { containerClass = (this.state.orientation === 'landscape') ? styles.landscapeTabletContainer : styles.portraitTabletContainer; } else { containerClass = (this.state.orientation === 'landscape') ? styles.landscapeContainer : styles.portraitContainer; } if (showActions) { cardClass = styles.expandedCard; } let color = {}; let title = displayName || uri.split('@')[0]; let subtitle = uri; let description = this.props.contact.startTime; if (displayName === uri || this.state.conference) { title = toTitleCase(uri.split('@')[0]); } if (this.props.contact.tags.indexOf('history') > -1) { let duration = moment.duration(this.props.contact.duration, 'seconds').format('HH:mm:ss', {trim: false}); if (this.props.contact.direction === 'received' && this.props.contact.duration === 0) { color.color = '#a94442'; duration = 'missed'; } else if (this.props.contact.direction === 'placed' && this.props.contact.duration === 0) { duration = 'cancelled'; } if (this.state.conference) { if (this.state.invitedParties && this.state.invitedParties.length) { const p_text = this.state.invitedParties.length > 1 ? 'participants' : 'participant'; subtitle = 'With ' + this.state.invitedParties.length + ' favorite ' + p_text; let i = 1; let contact_obj; let dn; let _item; this.state.invitedParties.forEach((participant) => { contact_obj = this.findObjectByKey(this.props.contacts, 'remoteParty', participant); dn = contact_obj ? contact_obj.displayName : participant; _item = {nr: i, id: uuid.v4(), uri: participant, displayName: dn}; participantsData.push(_item); i = i + 1; }); } else if (this.state.participants && this.state.participants.length) { const p_text = this.state.participants.length > 1 ? 'participants' : 'participant'; subtitle = 'With ' + this.state.participants.length + ' ' + p_text; let i = 1; let contact_obj; let dn; let _item; this.state.participants.forEach((participant) => { contact_obj = this.findObjectByKey(this.props.contacts, 'remoteParty', participant); dn = contact_obj ? contact_obj.displayName : participant; _item = {nr: i, id: uuid.v4(), uri: participant, displayName: dn}; participantsData.push(_item); i = i + 1; }); } else { subtitle = 'No participants'; } } if (!displayName) { title = uri; if (duration === 'missed') { subtitle = 'Last call missed'; } else if (duration === 'cancelled') { subtitle = 'Last call cancelled'; } else { subtitle = 'Last call duration ' + duration ; } } description = description + ' (' + duration + ')'; return ( {this.setTargetUri(uri, this.props.contact)}} style={[containerClass, cardClass]} > {title} {subtitle} {description} {this.state.invitedParties && this.state.invitedParties.length && showActions ? item.id} key={item => item.id} /> : null} {showActions ? {showEditButton? : null} {showDeleteButton? : null} {showBlockButton? : null} {showFavoriteButton?: null} {showUndoButton?: null} : null} ); } else { return ( {this.props.setTargetUri(uri, this.props.contact)}} style={[containerClass, cardClass]} > {title} {uri} {this.state.label} {showActions ? {showBlockButton? : null} {showFavoriteButton?: null} {showUndoButton?: null} : null} ); } } } HistoryCard.propTypes = { id : PropTypes.string, contact : PropTypes.object, setTargetUri : PropTypes.func, setBlockedUri : PropTypes.func, setFavoriteUri : PropTypes.func, saveInvitedParties : PropTypes.func, deleteHistoryEntry : PropTypes.func, orientation : PropTypes.string, isTablet : PropTypes.bool, contacts : PropTypes.array }; export default HistoryCard; diff --git a/app/components/ReadyBox.js b/app/components/ReadyBox.js index 72173fd..f3904b5 100644 --- a/app/components/ReadyBox.js +++ b/app/components/ReadyBox.js @@ -1,298 +1,294 @@ 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 }; } UNSAFE_componentWillReceiveProps(nextProps) { - console.log('UNSAFE_componentWillReceiveProps', nextProps.myInvitedParties); this.setState({myInvitedParties: nextProps.myInvitedParties}); } getTargetUri() { const defaultDomain = this.props.account.id.substring(this.props.account.id.indexOf('@') + 1); return utils.normalizeUri(this.state.targetUri, defaultDomain); } async componentDidMount() { - if (this.state.targetUri) { - console.log('We must call', this.state.targetUri); - } } setMissedCalls(flag) { this.setState({missedCalls: flag}); } filterHistory(filter) { 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() { const defaultDomain = `${config.defaultDomain}`; 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 }; export default ReadyBox;