diff --git a/app/components/HistoryCard.js b/app/components/HistoryCard.js index b8af4cc..9e9c234 100644 --- a/app/components/HistoryCard.js +++ b/app/components/HistoryCard.js @@ -1,164 +1,159 @@ -import React from 'react'; +import React, { Component} from 'react'; import { View } 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, Caption, Title, Subheading } from 'react-native-paper'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import styles from '../assets/styles/blink/_HistoryCard.scss'; import UserIcon from './UserIcon'; -const HistoryCard = (props) => { - const identity = { - displayName: props.historyItem.displayName || props.historyItem.name, - uri: props.historyItem.remoteParty || props.historyItem.uri, - type: props.historyItem.type || 'contact', - photo: props.historyItem.photo, - label: props.historyItem.label - } - //console.log('History card', props.historyItem); +class HistoryCard extends Component { + constructor(props) { + super(props); + autoBind(this); + + this.identity = { + displayName: this.props.contact.displayName || this.props.contact.name, + uri: this.props.contact.remoteParty || this.props.contact.uri, + type: this.props.contact.type || 'contact', + photo: this.props.contact.photo, + label: this.props.contact.label + } - const startVideoCall = (e) => { + //console.log(this.props.contact); + } + + startVideoCall(e) { e.stopPropagation(); - props.setTargetUri(identity.uri); + this.props.setTargetUri(this.identity.uri); // We need to wait for targetURI setImmediate(() => { - props.startVideoCall(e); + this.props.startVideoCall(e); }); } - const startAudioCall = (e) => { + startAudioCall(e) { e.stopPropagation(); - props.setTargetUri(identity.uri); + this.props.setTargetUri(this.identity.uri); // We need to wait for targetURI setImmediate(() => { - props.startAudioCall(e); + this.props.startAudioCall(e); }); } - let containerClass = styles.portraitContainer; + render () { + //console.log(this.props.contact.index, 'Render card', this.identity.uri, 'in', this.props.orientation); - if (props.isTablet) { - containerClass = (props.orientation === 'landscape') ? styles.landscapeTabletContainer : styles.portraitTabletContainer; - } else { - containerClass = (props.orientation === 'landscape') ? styles.landscapeContainer : styles.portraitContainer; - } + let containerClass = styles.portraitContainer; - let color = {}; - - const name = identity.displayName || identity.uri; - - let title = identity.displayName || identity.uri; - let subtitle = identity.uri; - - if (props.historyItem.conference) { -// console.log('Item participants', props.historyItem.participants); - if (props.historyItem.participants) { - if (props.historyItem.participants.length === 0) { - subtitle = 'No participants'; - } else if (props.historyItem.participants.length === 1) { - subtitle = 'One participant'; - } else { - subtitle = props.historyItem.participants.length + ' participants'; - } + if (this.props.isTablet) { + containerClass = (this.props.orientation === 'landscape') ? styles.landscapeTabletContainer : styles.portraitTabletContainer; + } else { + containerClass = (this.props.orientation === 'landscape') ? styles.landscapeContainer : styles.portraitContainer; } - } - let description = props.historyItem.startTime; + let color = {}; + const name = this.identity.displayName || this.identity.uri; + let title = this.identity.displayName || this.identity.uri; + let subtitle = this.identity.uri; - if (identity.type === 'history') { - let duration = moment.duration(props.historyItem.duration, 'seconds').format('hh:mm:ss', {trim: false}); + let description = this.props.contact.startTime; - if (props.historyItem.direction === 'received' && props.historyItem.duration === 0) { - color.color = '#a94442'; - duration = 'missed'; - } else if (props.historyItem.direction === 'placed' && props.historyItem.duration === 0) { - duration = 'cancelled'; - } + if (this.identity.type === 'history') { + let duration = moment.duration(this.props.contact.duration, 'seconds').format('hh:mm:ss', {trim: false}); - if (duration) { - let subtitle = identity.uri + ' (' + duration + ')'; - } + 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 (!identity.displayName) { - title = identity.uri; - if (duration === 'missed') { - subtitle = 'Last call missed'; - } else if (duration === 'cancelled') { - subtitle = 'Last call cancelled'; - } else { - subtitle = 'Last call duration ' + duration ; + if (duration) { + let subtitle = this.identity.uri + ' (' + duration + ')'; + } + + if (!this.identity.displayName) { + title = this.identity.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 ( - {props.setTargetUri(identity.uri)}} - onLongPress={startVideoCall} - style={containerClass} - > - - - {title} - {subtitle} - - {description} - - - - - - - - ); - - } else { - if (identity.label) { - subtitle = identity.uri + ' (' + identity.label + ')'; + description = description + ' (' + duration + ')'; + + return ( + {this.props.setTargetUri(this.identity.uri)}} + onLongPress={this.startVideoCall} + style={containerClass} + > + + + {title} + {subtitle} + + {description} + + + + + + + + ); + + } else { + return ( + {this.props.setTargetUri(this.identity.uri, this.props.contact)}} + onLongPress={this.startVideoCall} + style={containerClass} + > + + + {title} + {this.identity.uri} + + {this.identity.label} + + + + + + + + ); } - return ( - {props.setTargetUri(identity.uri, props.historyItem)}} - onLongPress={startVideoCall} - style={containerClass} - > - - - {title} - {subtitle} - - - - - - - ); } - /* */ } HistoryCard.propTypes = { - historyItem : PropTypes.object, + contact : PropTypes.object, startAudioCall : PropTypes.func, startVideoCall : PropTypes.func, setTargetUri : PropTypes.func, orientation : PropTypes.string, isTablet : PropTypes.bool }; export default HistoryCard; diff --git a/app/components/HistoryTileBox.js b/app/components/HistoryTileBox.js index 890c870..41719a5 100644 --- a/app/components/HistoryTileBox.js +++ b/app/components/HistoryTileBox.js @@ -1,61 +1,72 @@ -import React from 'react'; +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 styles from '../assets/styles/blink/_HistoryTileBox.scss'; -const HistoryTileBox = (props) => { - - const renderItem = ({ item }) => ( - - ); - - let columns = 1; - if (props.isTablet) { - columns = props.orientation === 'landscape' ? 3 : 2; - } else { - columns = props.orientation === 'landscape' ? 2 : 1; - } - - let items = props.historyItems.concat(props.contactItems); - -/* - console.log('History items', props.historyItems); - console.log('Contacts items', props.contactItems); - console.log('All items', items); -*/ - - return ( - - item.sessionId} - key={props.orientation} - /> - - ); +class HistoryTileBox extends Component { + constructor(props) { + super(props); + autoBind(this); + this.startItem = 0; + this.maxItems = 10; + } + + renderItem(item) { + return( + ); + } + + render() { + //utils.timestampedLog('Render history in', this.props.orientation); + + let columns = 1; + + if (this.props.isTablet) { + columns = this.props.orientation === 'landscape' ? 3 : 2; + } else { + columns = this.props.orientation === 'landscape' ? 2 : 1; + this.maxItems = this.props.orientation === 'landscape' ? 50 : 8; + } + + let allItems = this.props.historyItems.concat(this.props.contactItems); + let items = allItems.slice(this.startItem, this.maxItems); + + return ( + + item.sessionId} + key={this.props.orientation} + /> + + ); + } } HistoryTileBox.propTypes = { historyItems : PropTypes.array, contactItems : PropTypes.array, - orientation : PropTypes.string, - startAudioCall : PropTypes.func, - startVideoCall : PropTypes.func, - setTargetUri : PropTypes.func, - isTablet : PropTypes.bool + orientation : PropTypes.string, + startAudioCall : PropTypes.func, + startVideoCall : PropTypes.func, + setTargetUri : PropTypes.func, + isTablet : PropTypes.bool }; export default HistoryTileBox; diff --git a/app/components/ReadyBox.js b/app/components/ReadyBox.js index 8564a66..2959364 100644 --- a/app/components/ReadyBox.js +++ b/app/components/ReadyBox.js @@ -1,251 +1,255 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; // import VizSensor = require('react-visibility-sensor').default; import autoBind from 'auto-bind'; import { View, Platform} from 'react-native'; import { IconButton, Title } 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: this.props.missedTargetUri, showConferenceModal: false, - sticky: false, - matchedContacts: [] + sticky: false }; + + this.matchedContacts = []; + } getTargetUri() { const defaultDomain = this.props.account.id.substring(this.props.account.id.indexOf('@') + 1); return utils.normalizeUri(this.state.targetUri, defaultDomain); } async componentDidMount() { //console.log('Ready now'); if (this.state.targetUri) { console.log('We must call', this.state.targetUri); } } handleTargetChange(value, contact) { let matchedContacts = []; let new_value = value; if (contact) { if (this.state.targetUri != contact.uri) { matchedContacts = matchedContacts.concat(contact); } else { new_value = ''; } } if (this.state.targetUri) { let currentUri = this.getTargetUri(); if (currentUri.trim() === value.trim()) { new_value = ''; } } + this.setState({targetUri: new_value}); + if (new_value.length > 2 && !contact) { matchedContacts = this.props.contacts.filter(contact => (contact.uri.toLowerCase().search(new_value) > -1 || contact.name.toLowerCase().search(new_value) > -1)); } - this.setState({targetUri: new_value, matchedContacts: matchedContacts}); - //this.forceUpdate(); + this.matchedContacts = matchedContacts; } 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}`)) { this.props.startConference(this.state.targetUri, {audio: true, video: true}); } else { this.props.startCall(this.getTargetUri(), {audio: true, video: true}); } } showConferenceModal(event) { event.preventDefault(); 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) { if (this.props.connection === null) { this.props._notificationCenter.postSystemNotification("Server unreachable", {timeout: 2}); return; } 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) { if (this.props.connection === null) { this.props._notificationCenter.postSystemNotification("Server unreachable", {timeout: 2}); return; } 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}) { if (targetUri) { if (!options.video) { console.log('ReadyBox: Handle audio only conference call to',targetUri); } else { console.log('ReadyBox: Handle video conference call to',targetUri); } this.props.startConference(targetUri, options); } this.setState({showConferenceModal: false}); } render() { - //console.log('Render ready box'); + //utils.timestampedLog('Render ready box'); 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; // Join URIs from local and server history for input let history = this.props.history.concat( this.props.serverHistory.map(e => e.remoteParty) ); history = [...new Set(history)]; //console.log('history from server is', this.props.serverHistory); const placehoder = 'Enter a SIP address like alice@' + defaultDomain; let historyItems = this.props.serverHistory.filter(historyItem => historyItem.remoteParty.startsWith(this.state.targetUri)) return ( Enter address or phone number ); } } ReadyBox.propTypes = { account : PropTypes.object.isRequired, startCall : PropTypes.func.isRequired, startConference : PropTypes.func.isRequired, missedTargetUri : PropTypes.string, history : PropTypes.array, serverHistory : PropTypes.array, orientation : PropTypes.string, contacts : PropTypes.array, isTablet : PropTypes.bool }; export default ReadyBox; diff --git a/app/components/UserIcon.js b/app/components/UserIcon.js index 8c08917..cf206ad 100644 --- a/app/components/UserIcon.js +++ b/app/components/UserIcon.js @@ -1,70 +1,73 @@ import React, { useEffect, useState } from'react'; import PropTypes from 'prop-types'; import utils from '../utils'; import { Avatar } from 'react-native-paper'; const UserIcon = (props) => { /* const [photo, setPhoto] = useState(''); useEffect(() => { // You need to restrict it at some point // This is just dummy code and should be replaced by actual if (!photo && props.identity.uri) { getPhoto(); } }, []); const getPhoto = async () => { try { let contacts = await utils.findContact(props.identity.uri); contacts.some((contact) => { if (contact.hasThumbnail) { setPhoto(contact.thumbnailPath); return true; } }); } catch (err) { console.log('error getting contacts', err); } } */ + if (!props.identity) { + return (null) + } const name = props.identity.displayName || props.identity.uri; const photo = props.identity.photo; let initials = name.split(' ', 2).map(x => x[0]).join(''); const color = utils.generateMaterialColor(props.identity.uri)['300']; const avatarSize = props.large ? 120: 50; if (photo) { return } if (props.identity.uri.search('anonymous') !== -1) { return ( ) } if (props.identity.uri.search('videoconference') !== -1) { return ( ) } return ( ); }; UserIcon.propTypes = { identity: PropTypes.object.isRequired, large: PropTypes.bool, card: PropTypes.bool, active: PropTypes.bool }; export default UserIcon;