diff --git a/app/components/ReadyBox.js b/app/components/ReadyBox.js index fd96f1e..c503b36 100644 --- a/app/components/ReadyBox.js +++ b/app/components/ReadyBox.js @@ -1,987 +1,987 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import autoBind from 'auto-bind'; import { FlatList, View, Platform, TouchableHighlight, TouchableOpacity} from 'react-native'; import { IconButton, Title, Button, Colors, Text } from 'react-native-paper'; import ConferenceModal from './ConferenceModal'; import ContactsListBox from './ContactsListBox'; import FooterBox from './FooterBox'; import URIInput from './URIInput'; import config from '../config'; import utils from '../utils'; import styles from '../assets/styles/blink/_ReadyBox.scss'; import {Keyboard} from 'react-native'; import QRCodeScanner from 'react-native-qrcode-scanner'; import { RNCamera } from 'react-native-camera'; class ReadyBox extends Component { constructor(props) { super(props); autoBind(this); this.state = { targetUri: this.props.selectedContact ? this.props.selectedContact.uri : '', contacts: this.props.contacts, selectedContact: this.props.selectedContact, showConferenceModal: this.props.showConferenceModal, sticky: false, favoriteUris: this.props.favoriteUris, blockedUris: this.props.blockedUris, historyCategoryFilter: null, historyPeriodFilter: null, missedCalls: this.props.missedCalls, isLandscape: this.props.isLandscape, participants: null, myInvitedParties: this.props.myInvitedParties, messages: this.props.messages, myDisplayName: this.props.myDisplayName, chat: (this.props.selectedContact !== null) && (this.props.call !== null), call: this.props.call, inviteContacts: this.props.inviteContacts, shareToContacts: this.props.shareToContacts, selectedContacts: this.props.selectedContacts, pinned: this.props.pinned, messageZoomFactor: this.props.messageZoomFactor, isTyping: this.props.isTyping, navigationItems: this.props.navigationItems, fontScale: this.props.fontScale, historyFilter: this.props.historyFilter, isTablet: this.props.isTablet, myContacts: this.props.myContacts, showQRCodeScanner: this.props.showQRCodeScanner, ssiCredentials: this.props.ssiCredentials, ssiConnections: this.props.ssiConnections }; this.ended = false; } UNSAFE_componentWillReceiveProps(nextProps) { if (this.ended) { return; } if (this.state.selectedContact) { this.setState({targetUri: nextProps.selectedContact ? nextProps.selectedContact.uri : '', chat: false}); } if (!this.state.inviteContacts && nextProps.inviteContacts) { this.handleTargetChange(''); this.setState({chat: false}); } if (this.state.selectedContact !== nextProps.selectedContact && nextProps.selectedContact) { this.setState({chat: !this.chatDisabledForUri(nextProps.selectedContact.uri)}); } if (!nextProps.historyFilter && this.state.historyFilter) { this.filterHistory(null); } if (nextProps.historyFilter === 'ssi' && !nextProps.selectedContact && !this.state.selectedContact) { this.setState({'historyCategoryFilter': 'ssi'}); if (this.navigationRef) { this.navigationRef.scrollToIndex({animated: true, index: this.navigationItems.length-1}); } } if (nextProps.missedCalls.length === 0 && this.state.historyCategoryFilter === 'missed') { this.setState({'historyCategoryFilter': null}); } if (nextProps.blockedUris.length === 0 && this.state.historyCategoryFilter === 'blocked') { this.setState({'historyCategoryFilter': null}); } if (nextProps.favoriteUris.length === 0 && this.state.historyCategoryFilter === 'favorite') { this.setState({'historyCategoryFilter': null}); } if (Object.keys(this.state.myContacts).length === 0 && nextProps.myContacts && Object.keys(nextProps.myContacts).length > 0) { this.bounceNavigation(); } this.setState({myInvitedParties: nextProps.myInvitedParties, myContacts: nextProps.myContacts, messages: nextProps.messages, historyFilter: nextProps.historyFilter, myDisplayName: nextProps.myDisplayName, call: nextProps.call, showConferenceModal: nextProps.showConferenceModal, isTyping: nextProps.isTyping, navigationItems: nextProps.navigationItems, messageZoomFactor: nextProps.messageZoomFactor, contacts: nextProps.contacts, inviteContacts: nextProps.inviteContacts, shareToContacts: nextProps.shareToContacts, selectedContacts: nextProps.selectedContacts, selectedContact: nextProps.selectedContact, pinned: nextProps.pinned, favoriteUris: nextProps.favoriteUris, blockedUris: nextProps.blockedUris, missedCalls: nextProps.missedCalls, fontScale: nextProps.fontScale, isTablet: nextProps.isTablet, showQRCodeScanner: nextProps.showQRCodeScanner, isLandscape: nextProps.isLandscape, ssiCredentials: nextProps.ssiCredentials, ssiConnections: nextProps.ssiConnections }); } getTargetUri(uri) { return utils.normalizeUri(uri, this.props.defaultDomain); } async componentDidMount() { this.ended = false; } componentWillUnmount() { this.ended = true; } filterHistory(filter) { if (this.ended) { return; } this.props.filterHistoryFunc(filter); if (!filter) { this.setState({'historyPeriodFilter': null, historyCategoryFilter: null}); } else if (filter === 'today' || filter === 'yesterday') { filter = this.state.historyPeriodFilter === filter ? null : filter; this.setState({'historyPeriodFilter': filter}); } else { this.setState({'historyCategoryFilter': filter}); } this.handleTargetChange(''); } chatDisabledForUri(uri) { if (uri.indexOf('@videoconference') > -1) { return true; } if (uri.indexOf('@guest') > -1) { return true; } if (uri.indexOf('3333@') > -1) { return true; } if (uri.indexOf('4444@') > -1) { return true; } return false; } get showNavigationBar(){ if (this.state.selectedContact) { return false; } return true; } get showSearchBar() { if (this.state.selectedContact && !this.state.isTablet) { return false; } if (this.state.showQRCodeScanner) { return false; } if (this.state.isTablet || (!this.state.isLandscape && this.state.selectedContact)) { return true; } if (this.state.call && this.state.call.state !== 'incoming' && !this.state.inviteContacts) { return false; } return true; } get showButtonsBar() { if (this.state.historyCategoryFilter === 'blocked') { return false; } if (this.state.historyCategoryFilter === 'ssi') { return false; } if (this.state.showQRCodeScanner) { return false; } if (this.state.isTablet) { return true; } if (this.state.call) { return true; } if (!this.state.targetUri) { return true; } if (this.state.selectedContact) { if (this.state.isLandscape && !this.state.isTablet) { return false; } return true; } return true; } handleTargetChange(new_uri, contact) { //console.log('---handleTargetChange new_uri =', new_uri); //console.log('handleTargetChange contact =', contact); if ((this.state.inviteContacts || this.state.shareToContacts) && contact) { const uri = contact.uri; this.props.updateSelection(uri); return; } // This URLs are used to request SSI credentials if (new_uri && new_uri.startsWith('https://didcomm.issuer.bloqzone.com?c_i=')) { this.props.handleSSIEnrolment(new_uri); return; } - if (contact && contact.tags.indexOf('ssi') > -1) { + if (contact && contact.tags.indexOf('ssi') > -1 && this.state.selectedContact !== contact) { this.setState({'historyCategoryFilter': 'ssi'}); } if (this.state.selectedContact === contact) { if (this.state.chat) { this.setState({chat: false}); } return; } else { this.setState({chat: false}); } let new_value = new_uri; if (contact) { if (this.state.targetUri === contact.uri) { new_value = ''; } } else { contact = null; } if (this.state.targetUri === new_uri) { new_value = ''; } if (new_value === '') { contact = null; } if (new_value.indexOf(' ') === -1) { new_value = new_value.trim().toLowerCase(); } //new_value = new_value.replace(' ',''); //console.log('--- Select new contact', contact? contact.uri : null); //console.log('--- Select new targetUri', new_value); this.props.selectContact(contact); this.setState({targetUri: new_value}); } handleTargetSelect() { if (this.props.connection === null) { this.props._notificationCenter.postSystemNotification("Server unreachable"); return; } let uri = this.state.targetUri.toLowerCase(); if (uri.endsWith(`@${config.defaultConferenceDomain}`)) { let participants; if (this.state.myInvitedParties && this.state.myInvitedParties.hasOwnProperty(uri)) { participants = this.state.myInvitedParties[uri]; } this.props.startConference(uri, {audio: true, video: true, participants: this.state.participants}); } else { this.props.startCall(this.getTargetUri(uri), {audio: true, video: true}); } } shareContent() { this.props.shareContent(); } showConferenceModal(event) { event.preventDefault(); this.props.showConferenceModalFunc(); } handleChat(event) { event.preventDefault(); let targetUri; if (!this.state.chat && !this.state.selectedContact) { targetUri = this.getTargetUri(this.state.targetUri); this.setState({targetUri: targetUri}); } let uri = this.state.targetUri.trim().toLowerCase(); if (!this.state.chat && !this.selectedContact && uri) { if (uri.indexOf('@') === -1) { uri = uri + '@' + this.props.defaultDomain; } let contact = this.props.newContactFunc(uri, null, {src: 'new chat'}); console.log('Create synthetic contact', contact); this.props.selectContact(contact); this.setState({targetUri: uri, chat: true}); Keyboard.dismiss(); } this.setState({chat: !this.state.chat}); } handleAudioCall(event) { event.preventDefault(); Keyboard.dismiss(); let uri = this.state.targetUri.trim().toLowerCase(); var uri_parts = uri.split("/"); if (uri_parts.length === 5 && uri_parts[0] === 'https:') { // https://webrtc.sipthor.net/conference/DaffodilFlyChill0 from external web link // https://webrtc.sipthor.net/call/alice@example.com from external web link let event = uri_parts[3]; uri = uri_parts[4]; if (event === 'conference') { uri = uri.split("@")[0] + '@' + config.defaultConferenceDomain; } } if (uri.endsWith(`@${config.defaultConferenceDomain}`)) { this.props.startConference(uri, {audio: true, video: false}); } else { this.props.startCall(this.getTargetUri(uri), {audio: true, video: false}); } } handleVideoCall(event) { event.preventDefault(); Keyboard.dismiss(); let uri = this.state.targetUri.toLowerCase(); var uri_parts = uri.split("/"); if (uri_parts.length === 5 && uri_parts[0] === 'https:') { // https://webrtc.sipthor.net/conference/DaffodilFlyChill0 from external web link // https://webrtc.sipthor.net/call/alice@example.com from external web link let event = uri_parts[3]; uri = uri_parts[4]; if (event === 'conference') { uri = uri.split("@")[0] + '@' + config.defaultConferenceDomain; } } if (uri.endsWith(`@${config.defaultConferenceDomain}`)) { this.props.startConference(uri, {audio: true, video: true}); } else { this.props.startCall(this.getTargetUri(uri), {audio: true, video: true}); } } handleConferenceCall(targetUri, options={audio: true, video: true, participants: []}) { Keyboard.dismiss(); this.props.startConference(targetUri, {audio: options.audio, video: options.video, participants: options.participants}); this.props.hideConferenceModalFunc(); } get chatButtonDisabled() { let uri = this.state.targetUri.trim(); if (this.state.selectedContact) { return true; } if (this.state.shareToContacts) { return true; } if (!uri || uri.indexOf(' ') > -1 || uri.indexOf('@guest.') > -1 || uri.indexOf('@videoconference') > -1) { return true; } let username = uri.split('@')[0]; let isPhoneNumber = username.match(/^(\+|0)(\d+)$/); if (isPhoneNumber) { return true; } if (uri.indexOf('@') > -1) { let email_reg = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,})+$/; let validEmail = email_reg.test(uri); if (!validEmail) { return true; } } if (this.chatDisabledForUri(uri)) { return true; } return false; } get callButtonDisabled() { let uri = this.state.targetUri.trim(); if (!uri || uri.indexOf(' ') > -1 || uri.indexOf('@guest.') > -1 || uri.indexOf('@videoconference') > -1) { return true; } if (this.state.shareToContacts) { return true; } if (uri.indexOf('@') > -1) { let email_reg = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,})+$/; let validEmail = email_reg.test(uri); if (!validEmail) { return true; } } return false; } get videoButtonDisabled() { let uri = this.state.targetUri.trim(); if (!uri || uri.indexOf(' ') > -1 || uri.indexOf('@guest.') > -1 || uri.indexOf('@videoconference') > -1) { return true; } if (uri.indexOf('4444@') > -1) { return true; } if (this.state.shareToContacts) { return true; } let username = uri.split('@')[0]; let isPhoneNumber = username.match(/^(\+|0)(\d+)$/); if (isPhoneNumber) { return true; } return this.callButtonDisabled; } get conferenceButtonDisabled() { let uri = this.state.targetUri.trim(); if (uri.indexOf(' ') > -1) { return true; } if (this.state.shareToContacts) { return true; } let username = uri.split('@')[0]; let isPhoneNumber = username.match(/^(\+|0)(\d+)$/); if (isPhoneNumber) { return true; } if (uri.indexOf('@') > -1 && uri.indexOf(config.defaultConferenceDomain) === -1) { return true; } var uri_parts = uri.split("/"); if (uri_parts.length === 5 && uri_parts[0] === 'https:') { // https://webrtc.sipthor.net/conference/DaffodilFlyChill0 from external web link // https://webrtc.sipthor.net/call/alice@example.com from external web link let event = uri_parts[3]; if (event === 'call') { return true; } } return false; } renderNavigationItem(object) { if (!object.item.enabled) { return (null); } let title = object.item.title; let key = object.item.key; let buttonStyle = object.item.selected ? styles.navigationButtonSelected : styles.navigationButton; if (key === "hideQRCodeScanner") { return (); } return (); } bounceNavigation() { setTimeout(() => { if (this.navigationRef && !this.state.selectedContact) { this.navigationRef.scrollToIndex({animated: true, index: Math.floor(this.navigationItems.length / 2)}); } }, 3000); setTimeout(() => { if (this.navigationRef && !this.state.selectedContact) { this.navigationRef.scrollToIndex({animated: true, index: this.navigationItems.length-1}); } }, 4500); setTimeout(() => { if (this.navigationRef && !this.state.selectedContact) { this.navigationRef.scrollToIndex({animated: true, index: 0}); } }, 6000); } get navigationItems() { let conferenceEnabled = Object.keys(this.state.myInvitedParties).length > 0 || this.state.navigationItems['conference']; if (this.state.inviteContacts) { conferenceEnabled = false; } if (this.state.showQRCodeScanner) { return [ {key: "hideQRCodeScanner", title: 'Cancel', enabled: true, selected: false} ]; } return [ {key: null, title: 'All', enabled: true, selected: false}, {key: 'history', title: 'Calls', enabled: true, selected: this.state.historyCategoryFilter === 'history'}, {key: 'chat', title: 'Chat', enabled: true, selected: this.state.historyCategoryFilter === 'chat'}, {key: 'today', title: 'Today', enabled: this.state.navigationItems['today'], selected: this.state.historyPeriodFilter === 'today'}, {key: 'yesterday', title: 'Yesterday', enabled: this.state.navigationItems['yesterday'], selected: this.state.historyPeriodFilter === 'yesterday'}, {key: 'missed', title: 'Missed', enabled: this.state.missedCalls.length > 0, selected: this.state.historyCategoryFilter === 'missed'}, {key: 'favorite', title: 'Favorites', enabled: this.state.favoriteUris.length > 0, selected: this.state.historyCategoryFilter === 'favorite'}, {key: 'blocked', title: 'Blocked', enabled: this.state.blockedUris.length > 0, selected: this.state.historyCategoryFilter === 'blocked'}, {key: 'conference', title: 'Conference', enabled: conferenceEnabled, selected: this.state.historyCategoryFilter === 'conference'}, {key: 'test', title: 'Test', enabled: !this.state.shareToContacts && !this.state.inviteContacts, selected: this.state.historyCategoryFilter === 'test'}, {key: 'ssi', title: 'SSI', enabled: (this.state.ssiConnections && this.state.ssiConnections.length > 0) || (this.state.ssiCredentials && this.state.ssiCredentials.length > 0), selected: this.state.historyCategoryFilter === 'ssi'}, ]; } toggleQRCodeScanner(event) { if (event) { event.preventDefault(); } console.log('Scan QR code...'); this.props.toggleQRCodeScannerFunc(); } QRCodeRead(e) { //console.log('QR code object:', e); console.log('QR code data:', e.data); this.props.toggleQRCodeScannerFunc(); this.handleTargetChange(e.data); } get showQRCodeButton() { let uri = this.state.targetUri.toLowerCase(); return uri.length === 0; } render() { let URIContainerClass = styles.portraitUriInputBox; let uriGroupClass = styles.portraitUriButtonGroup; let titleClass = styles.portraitTitle; let uri = this.state.targetUri.toLowerCase(); var uri_parts = uri.split("/"); if (uri_parts.length === 5 && uri_parts[0] === 'https:') { // https://webrtc.sipthor.net/conference/DaffodilFlyChill0 from external web link // https://webrtc.sipthor.net/call/alice@example.com from external web link let event = uri_parts[3]; uri = uri_parts[4]; if (event === 'conference') { uri = uri.split("@")[0] + '@' + config.defaultConferenceDomain; } } /* console.log('Render -----', this.state.historyFilter, this.state.historyCategoryFilter); if (this.state.selectedContact) { console.log('Render selectedContact', this.state.selectedContact.name); } if (this.state.callContact) { console.log('Render callContact', this.state.callContact.uri); } if (this.state.targetUri) { console.log('Render targetUri', this.state.targetUri); } */ //console.log('RB', this.state.isTablet); if (this.state.isTablet) { titleClass = this.props.orientation === 'landscape' ? styles.landscapeTabletTitle : styles.portraitTabletTitle; } else { titleClass = this.props.orientation === 'landscape' ? styles.landscapeTitle : styles.portraitTitle; } if (this.state.isTablet) { uriGroupClass = this.props.orientation === 'landscape' ? styles.landscapeTabletUriButtonGroup : styles.portraitTabletUriButtonGroup; } else { uriGroupClass = this.props.orientation === 'landscape' ? styles.landscapeUriButtonGroup : styles.portraitUriButtonGroup; } if (this.state.isTablet) { URIContainerClass = this.props.orientation === 'landscape' ? styles.landscapeTabletUriInputBox : styles.portraitTabletUriInputBox; } else { URIContainerClass = this.props.orientation === 'landscape' ? styles.landscapeUriInputBox : styles.portraitUriInputBox; } const historyContainer = this.props.orientation === 'landscape' ? styles.historyLandscapeContainer : styles.historyPortraitContainer; const buttonGroupClass = this.props.orientation === 'landscape' ? styles.landscapeButtonGroup : styles.buttonGroup; const borderClass = this.state.chat ? null : styles.historyBorder; let backButtonTitle = 'Back to call'; const showBackToCallButton = this.state.call && this.state.call.state !== 'incoming' && this.state.call.state !== 'terminated' ? true : false ; if (showBackToCallButton) { if (this.state.call.hasOwnProperty('_participants')) { backButtonTitle = this.state.selectedContacts.length > 0 ? 'Invite people' : 'Back to conference'; } else { backButtonTitle = this.state.selectedContacts.length > 0 ? 'Invite people' : 'Back to call'; } } let greenButtonClass = Platform.OS === 'ios' ? styles.greenButtoniOS : styles.greenButton; let blueButtonClass = Platform.OS === 'ios' ? styles.blueButtoniOS : styles.blueButton; let disabledGreenButtonClass = Platform.OS === 'ios' ? styles.disabledGreenButtoniOS : styles.disabledGreenButton; let disabledBlueButtonClass = Platform.OS === 'ios' ? styles.disabledBlueButtoniOS : styles.disabledBlueButton; return ( {this.showSearchBar && !this.state.isLandscape ? : null} {this.showButtonsBar ? {this.showSearchBar && this.state.isLandscape ? : null} {showBackToCallButton ? : { this.showQRCodeButton ? : null} } : null} { this.state.showQRCodeScanner && !showBackToCallButton ? : } {this.showNavigationBar ? { this.navigationRef = ref; }} onScrollToIndexFailed={info => { const wait = new Promise(resolve => setTimeout(resolve, 700)); wait.then(() => { this.navigationRef.current?.scrollToIndex({ index: info.index, animated: true/false }); }); }} data={this.navigationItems} extraData={this.state} keyExtractor={(item, index) => item.key} renderItem={this.renderNavigationItem} /> : null} {this.state.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, isLandscape : PropTypes.bool, refreshHistory : PropTypes.bool, refreshFavorites: PropTypes.bool, saveHistory : PropTypes.func, localHistory : PropTypes.array, myDisplayName : PropTypes.string, myPhoneNumber : PropTypes.string, toggleFavorite : PropTypes.func, myInvitedParties: PropTypes.object, toggleBlocked : PropTypes.func, favoriteUris : PropTypes.array, blockedUris : PropTypes.array, defaultDomain : PropTypes.string, saveContact : PropTypes.func, selectContact : PropTypes.func, lookupContacts : PropTypes.func, call : PropTypes.object, goBackFunc : PropTypes.func, messages : PropTypes.object, sendMessage : PropTypes.func, reSendMessage : PropTypes.func, confirmRead : PropTypes.func, deleteMessage : PropTypes.func, expireMessage : PropTypes.func, getMessages : PropTypes.func, deleteMessages : PropTypes.func, pinMessage : PropTypes.func, unpinMessage : PropTypes.func, sendPublicKey : PropTypes.func, inviteContacts : PropTypes.bool, shareToContacts : PropTypes.bool, showQRCodeScanner : PropTypes.bool, selectedContacts: PropTypes.array, updateSelection : PropTypes.func, loadEarlierMessages: PropTypes.func, newContactFunc : PropTypes.func, missedCalls : PropTypes.array, messageZoomFactor: PropTypes.string, isTyping: PropTypes.bool, navigationItems: PropTypes.object, showConferenceModal: PropTypes.bool, showConferenceModalFunc: PropTypes.func, hideConferenceModalFunc: PropTypes.func, shareContent: PropTypes.func, fetchSharedItems: PropTypes.func, filterHistoryFunc: PropTypes.func, historyFilter: PropTypes.string, fontScale: PropTypes.number, inviteToConferenceFunc: PropTypes.func, toggleQRCodeScannerFunc: PropTypes.func, myContacts: PropTypes.object, handleSSIEnrolment: PropTypes.func, ssiCredentials: PropTypes.array, ssiConnections: PropTypes.array }; export default ReadyBox;