Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F7313547
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
160 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/app/assets/styles/blink/_ContactCard.scss b/app/assets/styles/blink/_ContactCard.scss
index af91b89..7dc6077 100644
--- a/app/assets/styles/blink/_ContactCard.scss
+++ b/app/assets/styles/blink/_ContactCard.scss
@@ -1,115 +1,155 @@
.containerPortrait {
}
.containerLandscape {
}
.cardPortraitContainer {
margin-top: 0.5px;
}
.cardLandscapeContainer {
flex: 1;
margin-left: 1px;
margin-top: 1px;
border-radius: 2px;
}
.cardLandscapeTabletContainer {
flex: 1;
border: 1px;
border-radius: 2px;
}
.cardPortraitTabletContainer {
flex: 1;
border: 1px;
border-radius: 2px;
}
.rowContent {
flex: 1;
flex-direction: row;
justify-content: space-between;
}
.cardContent {
flex: 1;
flex-direction: row;
}
.title {
- padding-top:7px;
font-size: 18px;
line-height: 20px;
flex: 1;
}
+.titlePaddingSmall {
+ padding-top:0px;
+}
+
+.titlePadding {
+ padding-top:7px;
+}
+
+.titlePaddingBig {
+ padding-top:14px;
+}
+
.subtitle {
font-size:14px;
line-height: 20px;
flex: 1;
}
.description {
font-size:12px;
flex: 1;
}
.avatarContent {
- margin-top: 5px;
+ margin-top: 10px;
}
.gravatar {
width: 50px;
height: 50px;
border-width: 2px;
border-color: white;
border-radius: 50px;
}
.mainContent {
margin-left: 10px;
}
.rightContent {
margin-top: 10px;
margin-left: 60px;
margin-right: 10px;
align-items: flex-end;
border: 0px;
}
.badgeTextStyle {
font-size: 12px;
}
.badgeContainer {
position: absolute;
bottom: 10;
right: 0;
size: 30;
}
.selectedContact {
margin-top: 15px;
}
+.timestamp {
+ margin-top: -5px;
+}
+
.participants {
margin-top: 10px;
}
.participant {
font-size: 14px;
}
.buttonContainer {
margin: 0 auto;
margin-top:auto;
}
.button {
border-radius: 2px;
padding-left: 30px;
padding-right: 30px;
}
+
+.greenButtonContainer {
+ padding-right: 15px;
+}
+
+.recordingLabel {
+ margin-top: 7px;
+}
+
+.greenButton {
+ background-color: rgba(#6DAA63, .8);
+ margin-left: 0px;
+}
+
+.audioButton {
+ background-color: white;
+}
+
+.redButton {
+ background-color: red;
+}
+
+.callButtons {
+ flex-direction: row;
+}
diff --git a/app/components/ChatActions.js b/app/components/ChatActions.js
index b28c905..3e348ff 100644
--- a/app/components/ChatActions.js
+++ b/app/components/ChatActions.js
@@ -1,206 +1,204 @@
import PropTypes from 'prop-types'
import React from 'react'
import autoBind from 'auto-bind';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
import AudioRecorderPlayer from 'react-native-audio-recorder-player'
import {TouchableOpacity, View, Platform} from 'react-native'
import styles from '../assets/styles/blink/_ContactsListBox.scss';
const RNFS = require('react-native-fs');
import AudioRecord from 'react-native-audio-record';
const options = {
sampleRate: 16000, // default 44100
channels: 1, // 1 or 2, default 1
bitsPerSample: 16, // 8 or 16, default 16
audioSource: 6, // android only (see below)
wavFile: 'sylk-audio-recording.wav' // default 'audio.wav'
};
AudioRecord.init(options);
class CustomActions extends React.Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {recording: false, texting: false, sendingImage: false}
this.timer = null;
this.audioRecorderPlayer = new AudioRecorderPlayer();
this.ended = false;
}
componentWillUnmount() {
this.ended = true;
this.stopRecording();
}
UNSAFE_componentWillReceiveProps(nextProps) {
this.setState({texting: nextProps.texting,
playing: nextProps.playing,
audioSendFinished: nextProps.audioSendFinished,
sendingImage: nextProps.sendingImage
});
if (nextProps.audioSendFinished) {
this.deleteAudioRecording()
}
}
onActionsPress = () => {
if (this.state.audioRecording) {
this.setState({audioRecording: false});
this.props.audioRecorded(null);
/*
if (this.state.playing) {
this.setState({playing: false});
this.onStopPlay();
} else {
this.setState({playing: true});
this.onStartPlay()
}
*/
} else {
if (this.state.playing) {
this.props.stopPlaying();
} else if (!this.state.recording) {
this.setState({recording: true});
this.props.onRecording(true);
console.log('Recording audio start');
this.onStartRecord();
this.timer = setTimeout(() => {
this.stopRecording();
}, 20000);
} else {
this.stopRecording();
}
}
}
stopRecording() {
if (this.timer !== null) {
clearTimeout(this.timer);
this.timer = null;
}
this.setState({recording: false});
this.props.onRecording(false);
this.onStopRecord();
}
renderIcon () {
let color = "green";
let name = this.state.recording ? "pause" : "microphone";
if (this.state.audioRecording) {
name = "delete";
color = "red"
}
if (this.state.texting || this.state.sendingImage || this.state.playing || (this.props.selectedContact && this.props.selectedContact.tags.indexOf('test') > -1)) {
return (<View></View>)
}
return (
<View>
<Icon
type="font-awesome"
name={name}
style={styles.chatAudioIcon}
size={20}
color={color}
/>
</View>
)
}
deleteAudioRecording() {
this.setState({audioRecording: null});
}
onStartRecord = async () => {
AudioRecord.start();
/* bellow code only works on Android
let path = RNFS.DocumentDirectoryPath + "/" + 'sylk-audio-recording.mp4';
const result = await this.audioRecorderPlayer.startRecorder(path);
this.audioRecorderPlayer.addRecordBackListener((e) => {
this.setState({
recordSecs: e.currentPosition,
recordTime: this.audioRecorderPlayer.mmssss(
Math.floor(e.currentPosition),
),
});
});
*/
};
onStopRecord = async () => {
if (this.ended) {
return;
}
const result = await AudioRecord.stop();
this.props.audioRecorded(result);
this.setState({audioRecording: result});
/* bellow code only works on Android
const result = await this.audioRecorderPlayer.stopRecorder();
this.audioRecorderPlayer.removeRecordBackListener();
this.setState({recordSecs: 0});
*/
-
- this.props.audioRecorded(result);
};
async onStartPlay () {
const msg = await this.audioRecorderPlayer.startPlayer();
this.audioRecorderPlayer.addPlayBackListener((e) => {
this.setState({
currentPositionSec: e.currentPosition,
currentDurationSec: e.duration,
playTime: this.audioRecorderPlayer.mmssss(Math.floor(e.currentPosition)),
duration: this.audioRecorderPlayer.mmssss(Math.floor(e.duration)),
});
});
};
onPausePlay = async () => {
await this.audioRecorderPlayer.pausePlayer();
};
onStopPlay = async () => {
console.log('onStopPlay');
this.audioRecorderPlayer.stopPlayer();
this.audioRecorderPlayer.removePlayBackListener();
this.setState({playing: false});
};
render() {
let chatLeftActionsContainer = Platform.OS === 'ios' ? styles.chatLeftActionsContaineriOS : styles.chatLeftActionsContainer;
return (
<View style={{flexDirection: 'row'}}>
<TouchableOpacity
style={[chatLeftActionsContainer]}
onPress={this.onActionsPress}
>
{this.renderIcon()}
</TouchableOpacity>
</View>
)
}
}
CustomActions.propTypes = {
audioRecorded: PropTypes.func,
onRecording: PropTypes.func,
stopPlaying: PropTypes.func,
options: PropTypes.object,
texting: PropTypes.bool,
sendingImage: PropTypes.bool,
audioSendFinished: PropTypes.bool,
selectedContact: PropTypes.object
}
export default CustomActions;
diff --git a/app/components/ContactCard.js b/app/components/ContactCard.js
index 59bb62f..587b5ab 100644
--- a/app/components/ContactCard.js
+++ b/app/components/ContactCard.js
@@ -1,432 +1,498 @@
import React, { Component, Fragment} from 'react';
import { View, SafeAreaView, FlatList } from 'react-native';
import { Badge } from 'react-native-elements'
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, Menu} from 'react-native-paper';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import uuid from 'react-native-uuid';
import styles from '../assets/styles/blink/_ContactCard.scss';
import UserIcon from './UserIcon';
import { GiftedChat } from 'react-native-gifted-chat'
import {Gravatar, GravatarApi} from 'react-native-gravatar';
+import {Keyboard} from 'react-native';
import utils from '../utils';
function toTitleCase(str) {
return str.replace(
/\w\S*/g,
function(txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
}
);
}
const Item = ({ nr, uri, name }) => (
<View style={styles.participantView}>
{name !== uri?
<Text style={styles.participant}>{name} ({uri})</Text>
:
<Text style={styles.participant}>{uri}</Text>
}
</View>
);
const renderItem = ({ item }) => (
<Item nr={item.nr} uri={item.uri} name={item.name}/>
);
function isIp(ipaddress) {
if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ipaddress)) {
return (true)
}
return (false)
}
class ContactCard extends Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
id: this.props.contact.id,
contact: this.props.contact,
invitedParties: this.props.invitedParties,
orientation: this.props.orientation,
isTablet: this.props.isTablet,
isLandscape: this.props.isLandscape,
favorite: (this.props.contact.tags.indexOf('favorite') > -1)? true : false,
blocked: (this.props.contact.tags.indexOf('blocked') > -1)? true : false,
confirmRemoveFavorite: false,
confirmPurgeChat: false,
messages: this.props.messages,
unread: this.props.unread,
chat: this.props.chat,
pinned: this.props.pinned,
fontScale: this.props.fontScale,
selectMode: this.props.selectMode
}
this.menuRef = React.createRef();
}
UNSAFE_componentWillReceiveProps(nextProps) {
this.setState({
id: nextProps.contact.id,
contact: nextProps.contact,
invitedParties: nextProps.invitedParties,
isLandscape: nextProps.isLandscape,
orientation: nextProps.orientation,
favorite: (nextProps.contact.tags.indexOf('favorite') > -1)? true : false,
blocked: (nextProps.contact.tags.indexOf('blocked') > -1)? true : false,
chat: nextProps.chat,
+ recording: nextProps.recording,
+ audioRecording: nextProps.audioRecording,
pinned: nextProps.pinned,
messages: nextProps.messages,
unread: nextProps.unread,
fontScale: nextProps.fontScale,
selectMode: nextProps.selectMode
});
}
shouldComponentUpdate(nextProps) {
//https://medium.com/sanjagh/how-to-optimize-your-react-native-flatlist-946490c8c49b
return true;
}
findObjectByKey(array, key, value) {
for (var i = 0; i < array.length; i++) {
if (array[i][key] === value) {
return array[i];
}
}
return null;
}
- toggleBlocked() {
- this.props.toggleBlocked(this.state.contact.uri);
- }
-
- setBlockedDomain() {
- let newBlockedState = this.props.toggleBlocked('@' + this.state.contact.uri.split('@')[1]);
- this.setState({blocked: newBlockedState});
- }
-
undo() {
this.setState({confirmRemoveFavorite: false,
confirmDeleteChat: false,
action: null});
}
onSendMessage(messages) {
messages.forEach((message) => {
// TODO send messages using account API
});
this.setState({messages: GiftedChat.append(this.state.messages, messages)});
}
setTargetUri(uri, contact) {
this.props.setTargetUri(uri, this.state.contact);
}
renderChatComposer () {
return null;
}
+ startCall(event) {
+ event.preventDefault();
+ Keyboard.dismiss();
+ this.props.startCall(this.state.contact.uri, {audio: true, video: false});
+ }
+
+ recordAudio(event) {
+ event.preventDefault();
+ Keyboard.dismiss();
+ this.props.recordAudio();
+ }
+
+ sendAudio(event) {
+ event.preventDefault();
+ Keyboard.dismiss();
+ this.props.sendAudio();
+ }
+
+ deleteAudio(event) {
+ event.preventDefault();
+ Keyboard.dismiss();
+ this.props.deleteAudio();
+ }
+
+ startVideoCall(event) {
+ event.preventDefault();
+ Keyboard.dismiss();
+ this.props.startCall(this.state.contact.uri, {audio: true, video: true});
+ }
+
render () {
let showActions = this.state.contact.showActions &&
this.state.contact.tags.indexOf('test') === -1 &&
this.state.contact.tags !== ["synthetic"];
let tags = this.state.contact ? this.state.contact.tags : [];
let uri = this.state.contact.uri;
let username = uri.split('@')[0];
let domain = uri.split('@')[1];
let isPhoneNumber = username.match(/^(\+|0)(\d+)$/);
let name = this.state.contact.name;
if (this.state.contact.organization) {
name = name + ' - ' + this.state.contact.organization;
}
- let showBlockButton = !this.state.contact.conference && !this.state.chat;
- let showBlockDomainButton = false;
- let blockTextbutton = 'Block';
- let blockDomainTextbutton = 'Block domain';
let contact_ts = '';
let participantsData = [];
- if (this.state.favorite) {
- if (!this.state.blocked) {
- showBlockButton = false;
- }
- }
-
- if (tags.indexOf('test') > -1) {
- showBlockButton = false;
- }
-
- if (name === 'Myself') {
- showBlockButton = false;
- }
-
- if (this.state.blocked) {
- blockTextbutton = 'Unblock';
- }
-
let color = {};
let title = name || username;
let subtitle = uri;
let description = 'No calls or messages';
var todayStart = new Date();
todayStart.setHours(0,0,0,0);
if (this.state.contact.timestamp) {
description = moment(this.state.contact.timestamp).format('MMM D HH:mm');
if (this.state.contact.timestamp > todayStart) {
contact_ts = moment(this.state.contact.timestamp).format('HH:mm');
} else {
contact_ts = moment(this.state.contact.timestamp).format('DD-MM');
}
}
if (name === uri) {
title = toTitleCase(username);
}
if (isPhoneNumber && isIp(domain)) {
title = 'Tel ' + username;
subtitle = 'From @' + domain;
- showBlockDomainButton = true;
}
if (utils.isAnonymous(uri)) {
//uri = 'anonymous@anonymous.invalid';
if (uri.indexOf('@guest.') > -1) {
subtitle = 'From the Web';
} else {
name = 'Anonymous';
}
- showBlockDomainButton = true;
- if (!this.state.blocked) {
- showBlockButton = false;
- }
- blockDomainTextbutton = 'Block Web callers';
}
if (!username || username.length === 0) {
if (isIp(domain)) {
title = 'IP domain';
} else if (domain.indexOf('guest.') > -1) {
title = 'Calls from the Web';
} else {
title = 'Domain';
}
}
let cardContainerClass = styles.portraitContainer;
+ let callButtonsEnabled = this.state.chat;
if (this.state.isTablet) {
cardContainerClass = (this.state.orientation === 'landscape') ? styles.cardLandscapeTabletContainer : styles.cardPortraitTabletContainer;
} else {
cardContainerClass = (this.state.orientation === 'landscape') ? styles.cardLandscapeContainer : styles.cardPortraitContainer;
}
let cardHeight = this.state.fontScale <= 1 ? 75 : 70;
let duration;
if (this.state.contact.tags.indexOf('history') > -1) {
duration = moment.duration(this.state.contact.lastCallDuration, 'seconds').format('HH:mm:ss', {trim: false});
if (this.state.contact.direction === 'incoming' && this.state.contact.lastCallDuration === 0) {
duration = 'missed';
} else if (this.state.contact.direction === 'outgoing' && this.state.contact.lastCallDuration === 0) {
duration = 'cancelled';
}
}
if (this.state.contact.conference) {
let participants = this.state.contact.participants;
if (this.state.invitedParties && this.state.invitedParties.length > 0 ) {
participants = this.state.invitedParties;
}
if (participants && participants.length > 0) {
const p_text = participants.length > 1 ? 'participants' : 'participant';
subtitle = 'With ' + participants.length + ' ' + p_text;
let i = 1;
let contact_obj;
let dn;
let _item;
participants.forEach((participant) => {
contact_obj = this.findObjectByKey(this.props.contacts, 'uri', participant);
dn = contact_obj ? contact_obj.name : participant;
_item = {nr: i, id: uuid.v4(), uri: participant, name: dn};
participantsData.push(_item);
i = i + 1;
});
} else {
subtitle = 'With no participants';
}
}
if (!name) {
title = uri;
}
if (duration === 'missed') {
subtitle = 'Last call missed from ', + uri;
} else if (duration === 'cancelled') {
subtitle = 'Last call cancelled to ' + uri;
} else {
subtitle = uri;
}
if (subtitle !== uri) {
subtitle = subtitle + ' ' + uri;
}
if (title.indexOf('@videoconference') > -1) {
title = name || username;
}
if (uri === 'anonymous@anonymous.invalid') {
title = 'Anonymous caller';
} else {
let isAnonymous = uri.indexOf('@guest.') > -1;
if (isAnonymous) {
title = title + ' (Web call)';
}
}
if (duration && duration !== "00:00:00") {
let media = 'Audio call';
if (this.state.contact.lastCallMedia.indexOf('video') > -1) {
media = 'Video call';
}
description = description + ' (' + media + ' ' + duration + ')';
}
+ let greenButtonClass = Platform.OS === 'ios' ? styles.greenButtoniOS : styles.greenButton;
+
const container = this.state.isLandscape ? styles.containerLandscape : styles.containerPortrait;
const chatContainer = this.state.isLandscape ? styles.chatLandscapeContainer : styles.chatPortraitContainer;
let showSubtitle = (showActions || this.state.isTablet || !description);
let label = this.state.contact.label ? (" (" + this.state.contact.label + ")" ) : '';
if (this.state.contact.lastMessage) {
subtitle = this.state.contact.lastMessage.split("\n")[0];
//description = description + ': ' + this.state.contact.lastMessage;
} else {
subtitle = subtitle + label;
}
let unread = (this.state.contact && this.state.contact.unread && !this.state.selectMode) ? this.state.contact.unread.length : 0;
let selectCircle = this.state.contact.selected ? 'check-circle' : 'circle-outline';
+ let recordIcon = this.state.recording ? 'pause' : 'microphone';
+ let recordStyle = this.state.recording ? styles.greenButton : styles.greenButton;
+
+ if (this.state.audioRecording) {
+ recordIcon = 'delete';
+ recordStyle = styles.redButton;
+ }
+
+ let titlePadding = styles.titlePadding;
+
+ if (this.state.fontScale < 1) {
+ titlePadding = styles.titlePaddingBig;
+ } else if (this.state.fontScale > 1.2) {
+ titlePadding = styles.titlePaddingSmall;
+ }
+
return (
<Fragment>
<Card style={[cardContainerClass, {height: cardHeight}]}
onPress={() => {this.setTargetUri(uri, this.state.contact)}}
>
<View style={styles.rowContent}>
<Card.Content style={styles.cardContent}>
<View style={styles.avatarContent}>
{ this.state.contact.photo || ! this.state.contact.email ?
<UserIcon style={styles.userIcon} identity={this.state.contact} unread={unread}/>
:
<Gravatar options={{email: this.state.contact.email, parameters: { "size": "50", "d": "mm" }, secure: true}} style={styles.gravatar} />
}
</View>
<View style={styles.mainContent}>
- <Title noWrap style={styles.title}>{title}</Title>
- <Subheading style={styles.subtitle}>{subtitle}</Subheading>
- {this.state.fontScale <= 1 ?
+ <Title noWrap style={[styles.title, titlePadding]}>{title}</Title>
+ {callButtonsEnabled ?
+ <View style={styles.callButtons}>
+ <View style={styles.greenButtonContainer}>
+ <IconButton
+ style={recordStyle}
+ size={18}
+ onPress={this.recordAudio}
+ icon={recordIcon}
+ />
+ </View>
+ {!this.state.recording && !this.state.audioRecording ?
+ <View style={styles.greenButtonContainer}>
+ <IconButton
+ style={styles.greenButton}
+ size={18}
+ onPress={this.startCall}
+ icon="phone"
+ />
+ </View>
+ : null
+ }
+
+ { this.state.recording && !this.state.audioRecording ?
+ <Text style={styles.recordingLabel}>Recording audio...</Text>
+ : null
+ }
+
+ {!this.state.recording && !this.state.audioRecording ?
+ <View style={styles.greenButtonContainer}>
+ <IconButton
+ style={styles.greenButton}
+ size={18}
+ onPress={this.startVideoCall}
+ icon="video"
+ />
+ </View>
+ : null}
+
+ {!this.state.recording && this.state.audioRecording ?
+ <View style={styles.greenButtonContainer}>
+ <IconButton
+ style={styles.greenButton}
+ size={18}
+ onPress={this.sendAudio}
+ icon="send"
+ />
+ </View>
+ : null
+ }
+
+ </View>
+ : <Subheading style={styles.subtitle}>{subtitle}</Subheading>}
+
+ {this.state.fontScale <= 1 && !callButtonsEnabled ?
<Caption style={styles.description}>
{this.state.contact.direction ?
<Icon name={this.state.contact.direction == 'incoming' ? 'arrow-bottom-left' : 'arrow-top-right'}/>
: null}
{description}
</Caption>
: null}
{participantsData && participantsData.length && showActions && false?
<View style={styles.participants}>
<SafeAreaView style={styles.participant}>
<FlatList
horizontal={false}
data={participantsData}
renderItem={renderItem}
listKey={item => item.id}
key={item => item.id}
/>
</SafeAreaView>
</View>
: null}
</View>
</Card.Content>
+
<View style={styles.rightContent}>
{ this.state.selectMode ?
<Icon style={styles.selectedContact} name={selectCircle} size={20} />
:
- <Text>{contact_ts}</Text>
+ <Text style={styles.timestamp}>{contact_ts}</Text>
}
+
{unread ?
<Badge value={unread} status="error" textStyle={styles.badgeTextStyle} containerStyle={styles.badgeContainer}/>
: null
}
+
</View>
</View>
-
- {showActions && false ?
- <View style={styles.buttonContainer}>
- <Card.Actions>
- {showBlockButton? <Button mode={buttonMode} style={styles.button} onPress={() => {this.toggleBlocked()}}>{blockTextbutton}</Button>: null}
- {showBlockDomainButton? <Button mode={buttonMode} style={styles.button} onPress={() => {this.setBlockedDomain()}}>{blockDomainTextbutton}</Button>: null}
- </Card.Actions>
- </View>
- : null}
</Card>
</Fragment>
);
}
}
ContactCard.propTypes = {
id : PropTypes.string,
contact : PropTypes.object,
setTargetUri : PropTypes.func,
chat : PropTypes.bool,
orientation : PropTypes.string,
isTablet : PropTypes.bool,
isLandscape : PropTypes.bool,
contacts : PropTypes.array,
defaultDomain : PropTypes.string,
accountId : PropTypes.string,
favoriteUris : PropTypes.array,
messages : PropTypes.array,
pinned : PropTypes.bool,
unread : PropTypes.array,
- toggleBlocked : PropTypes.func,
sendPublicKey : PropTypes.func,
fontScale : PropTypes.number,
- selectMode : PropTypes.bool
+ selectMode : PropTypes.bool,
+ startCall : PropTypes.func,
+ recordAudio : PropTypes.func,
+ sendAudio : PropTypes.func,
+ deleteAudio : PropTypes.func,
+ recording : PropTypes.bool,
+ audioRecording : PropTypes.string
};
export default ContactCard;
diff --git a/app/components/ContactsListBox.js b/app/components/ContactsListBox.js
index 59a318c..0181873 100644
--- a/app/components/ContactsListBox.js
+++ b/app/components/ContactsListBox.js
@@ -1,2298 +1,2363 @@
import React, { Component} from 'react';
import autoBind from 'auto-bind';
import PropTypes from 'prop-types';
import { Image, Clipboard, Dimensions, SafeAreaView, View, FlatList, Text, Linking, PermissionsAndroid, Switch, TouchableOpacity, BackHandler, TouchableHighlight} from 'react-native';
import ContactCard from './ContactCard';
import utils from '../utils';
import DigestAuthRequest from 'digest-auth-request';
import uuid from 'react-native-uuid';
import { GiftedChat, IMessage, Bubble, MessageText, Send, InputToolbar, MessageImage, Time} from 'react-native-gifted-chat'
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
import MessageInfoModal from './MessageInfoModal';
import EditMessageModal from './EditMessageModal';
import ShareMessageModal from './ShareMessageModal';
import DeleteMessageModal from './DeleteMessageModal';
import CustomChatActions from './ChatActions';
import FileViewer from 'react-native-file-viewer';
import OpenPGP from "react-native-fast-openpgp";
import DocumentPicker from 'react-native-document-picker';
import AudioRecorderPlayer from 'react-native-audio-recorder-player';
import VideoPlayer from 'react-native-video-player';
import RNFetchBlob from "rn-fetch-blob";
import { IconButton} from 'react-native-paper';
import ImageViewer from 'react-native-image-zoom-viewer';
import fileType from 'react-native-file-type';
import path from 'react-native-path';
import Sound from 'react-native-sound';
import SoundPlayer from 'react-native-sound-player';
import moment from 'moment';
import momenttz from 'moment-timezone';
import Video from 'react-native-video';
const RNFS = require('react-native-fs');
import CameraRoll from "@react-native-community/cameraroll";
import {launchCamera, launchImageLibrary} from 'react-native-image-picker';
+import AudioRecord from 'react-native-audio-record';
import styles from '../assets/styles/blink/_ContactsListBox.scss';
String.prototype.toDate = function(format)
{
var normalized = this.replace(/[^a-zA-Z0-9]/g, '-');
var normalizedFormat= format.toLowerCase().replace(/[^a-zA-Z0-9]/g, '-');
var formatItems = normalizedFormat.split('-');
var dateItems = normalized.split('-');
var monthIndex = formatItems.indexOf("mm");
var dayIndex = formatItems.indexOf("dd");
var yearIndex = formatItems.indexOf("yyyy");
var hourIndex = formatItems.indexOf("hh");
var minutesIndex = formatItems.indexOf("ii");
var secondsIndex = formatItems.indexOf("ss");
var today = new Date();
var year = yearIndex>-1 ? dateItems[yearIndex] : today.getFullYear();
var month = monthIndex>-1 ? dateItems[monthIndex]-1 : today.getMonth()-1;
var day = dayIndex>-1 ? dateItems[dayIndex] : today.getDate();
var hour = hourIndex>-1 ? dateItems[hourIndex] : today.getHours();
var minute = minutesIndex>-1 ? dateItems[minutesIndex] : today.getMinutes();
var second = secondsIndex>-1 ? dateItems[secondsIndex] : today.getSeconds();
return new Date(year,month,day,hour,minute,second);
};
const audioRecorderPlayer = new AudioRecorderPlayer();
+const options = {
+ sampleRate: 16000, // default 44100
+ channels: 1, // 1 or 2, default 1
+ bitsPerSample: 16, // 8 or 16, default 16
+ audioSource: 6, // android only (see below)
+ wavFile: 'sylk-audio-recording.wav' // default 'audio.wav'
+};
+
+AudioRecord.init(options);
+
// Note: copy and paste all styles in App.js from my repository
function renderBubble (props) {
let leftColor = 'green';
let rightColor = '#fff';
if (props.currentMessage.failed) {
rightColor = 'red';
leftColor = 'red';
} else {
if (props.currentMessage.pinned) {
rightColor = '#2ecc71';
leftColor = '#2ecc71';
}
}
if (props.currentMessage.image) {
return (
<Bubble
{...props}
wrapperStyle={{
right: {
backgroundColor: '#fff',
alignSelf: 'stretch',
marginLeft: 0
},
left: {
backgroundColor: '#fff',
alignSelf: 'stretch',
marginRight: 0
}
}}
textProps={{
style: {
color: props.position === 'left' ? '#000' : '#000',
},
}}
textStyle={{
left: {
color: '#fff',
},
right: {
color: '#000',
},
}}
/>
)
} else if (props.currentMessage.video) {
return (
<Bubble
{...props}
wrapperStyle={{
right: {
backgroundColor: '#000',
alignSelf: 'stretch',
marginLeft: 0
},
left: {
backgroundColor: '#000',
alignSelf: 'stretch',
marginRight: 0
}
}}
textProps={{
style: {
color: props.position === 'left' ? '#fff' : '#fff',
},
}}
textStyle={{
left: {
color: '#000',
},
right: {
color: '#000',
},
}}
/>
)
} else if (props.currentMessage.audio) {
return (
<Bubble
{...props}
wrapperStyle={{
right: {
backgroundColor: 'transparent',
},
left: {
backgroundColor: 'transparent',
}
}}
textProps={{
style: {
color: props.position === 'left' ? '#fff' : '#fff',
},
}}
textStyle={{
left: {
color: '#000',
},
right: {
color: '#000',
},
}}
/>
)
} else {
return (
<Bubble
{...props}
wrapperStyle={{
left: {
backgroundColor: leftColor,
},
right: {
backgroundColor: rightColor,
},
}}
textProps={{
style: {
color: props.position === 'left' ? '#fff' : '#000',
},
}}
textStyle={{
left: {
color: '#fff',
},
right: {
color: '#000',
},
}}
style={styles.bubbleContainer}
/>
)
}
}
class ContactsListBox extends Component {
constructor(props) {
super(props);
autoBind(this);
this.chatListRef = React.createRef();
this.default_placeholder = 'Enter message...'
let renderMessages = [];
if (this.props.selectedContact) {
let uri = this.props.selectedContact.uri;
if (uri in this.props.messages) {
renderMessages = this.props.messages[uri];
//renderMessages.sort((a, b) => (a.createdAt < b.createdAt) ? 1 : -1);
renderMessages = renderMessages.sort(function(a, b) {
if (a.createdAt < b.createdAt) {
return 1; //nameA comes first
}
if (a.createdAt > b.createdAt) {
return -1; // nameB comes first
}
if (a.createdAt === b.createdAt) {
if (a.msg_id < b.msg_id) {
return 1; //nameA comes first
}
if (a.msg_id > b.msg_id) {
return -1; // nameB comes first
}
}
return 0; // names must be equal
});
}
}
this.state = {
accountId: this.props.account ? this.props.account.id : null,
password: this.props.password,
targetUri: this.props.selectedContact ? this.props.selectedContact.uri : this.props.targetUri,
favoriteUris: this.props.favoriteUris,
blockedUris: this.props.blockedUris,
isRefreshing: false,
isLandscape: this.props.isLandscape,
contacts: this.props.contacts,
myInvitedParties: this.props.myInvitedParties,
refreshHistory: this.props.refreshHistory,
selectedContact: this.props.selectedContact,
myContacts: this.props.myContacts,
messages: this.props.messages,
renderMessages: GiftedChat.append(renderMessages, []),
chat: this.props.chat,
pinned: false,
message: null,
inviteContacts: this.props.inviteContacts,
shareToContacts: this.props.shareToContacts,
selectMode: this.props.shareToContacts || this.props.inviteContacts,
selectedContacts: this.props.selectedContacts,
pinned: this.props.pinned,
filter: this.props.filter,
periodFilter: this.props.periodFilter,
scrollToBottom: true,
messageZoomFactor: this.props.messageZoomFactor,
isTyping: false,
isLoadingEarlier: false,
fontScale: this.props.fontScale,
call: this.props.call,
isTablet: this.props.isTablet,
ssiCredentials: this.props.ssiCredentials,
ssiConnections: this.props.ssiConnections,
keys: this.props.keys,
recording: false,
playing: false,
texting: false,
audioRecording: null,
cameraAsset: null,
placeholder: this.default_placeholder,
audioSendFinished: false,
messagesCategoryFilter: this.props.messagesCategoryFilter,
isTexting: this.props.isTexting,
showDeleteMessageModal: false
}
this.ended = false;
this.recordingTimer = null;
this.outgoingPendMessages = {};
BackHandler.addEventListener('hardwareBackPress', this.backPressed);
this.listenforSoundNotifications()
}
componentDidMount() {
this.ended = false;
}
componentWillUnmount() {
this.ended = true;
this.stopRecordingTimer()
}
backPressed() {
this.stopRecordingTimer()
}
//getDerivedStateFromProps(nextProps, state) {
UNSAFE_componentWillReceiveProps(nextProps) {
if (this.ended) {
return;
}
if (nextProps.myInvitedParties !== this.state.myInvitedParties) {
this.setState({myInvitedParties: nextProps.myInvitedParties});
}
if (nextProps.contacts !== this.state.contacts) {
this.setState({contacts: nextProps.contacts});
}
if (nextProps.favoriteUris !== this.state.favoriteUris) {
this.setState({favoriteUris: nextProps.favoriteUris});
}
if (nextProps.blockedUris !== this.state.blockedUris) {
this.setState({blockedUris: nextProps.blockedUris});
}
if (nextProps.account !== null && nextProps.account !== this.props.account) {
this.setState({accountId: nextProps.account.id});
}
if (nextProps.refreshHistory !== this.state.refreshHistory) {
this.setState({refreshHistory: nextProps.refreshHistory});
this.getServerHistory();
}
if (nextProps.messageZoomFactor !== this.state.messageZoomFactor) {
this.setState({scrollToBottom: false, messageZoomFactor: nextProps.messageZoomFactor});
}
if (nextProps.messagesCategoryFilter !== this.state.messagesCategoryFilter && nextProps.selectedContact) {
this.props.getMessages(nextProps.selectedContact.uri, {category: nextProps.messagesCategoryFilter, pinned: this.state.pinned});
}
if (nextProps.pinned !== this.state.pinned && nextProps.selectedContact) {
this.props.getMessages(nextProps.selectedContact.uri, {category: nextProps.messagesCategoryFilter, pinned: nextProps.pinned});
}
if (nextProps.selectedContact !== this.state.selectedContact) {
//console.log('Selected contact changed to', nextProps.selectedContact);
this.resetContact()
this.setState({selectedContact: nextProps.selectedContact});
if (nextProps.selectedContact) {
this.setState({scrollToBottom: true});
if (Object.keys(this.state.messages).indexOf(nextProps.selectedContact.uri) === -1 && nextProps.selectedContact) {
this.props.getMessages(nextProps.selectedContact.uri);
}
} else {
this.setState({renderMessages: []});
}
};
if (nextProps.myContacts !== this.state.myContacts) {
this.setState({myContacts: nextProps.myContacts});
};
if (nextProps.selectedContact) {
let renderMessages = [];
let uri = nextProps.selectedContact.uri;
if (uri in nextProps.messages) {
renderMessages = nextProps.messages[uri];
// remove duplicate messages no mater what
renderMessages = renderMessages.filter((v,i,a)=>a.findIndex(v2=>['_id'].every(k=>v2[k] ===v[k]))===i);
if (this.state.renderMessages.length < renderMessages.length) {
//console.log('Number of messages changed', this.state.renderMessages.length, '->', renderMessages.length);
this.setState({isLoadingEarlier: false});
this.props.confirmRead(uri);
if (this.state.renderMessages.length > 0 && renderMessages.length > 0) {
let last_message_ts = this.state.renderMessages[0].createdAt;
if (renderMessages[0].createdAt > last_message_ts) {
this.setState({scrollToBottom: true});
}
}
}
}
let delete_ids = [];
Object.keys(this.outgoingPendMessages).forEach((_id) => {
if (renderMessages.some((obj) => obj._id === _id)) {
//console.log('Remove pending message id', _id);
delete_ids.push(_id);
// message exists
} else {
if (this.state.renderMessages.some((obj) => obj._id === _id)) {
//console.log('Pending message id', _id, 'already exists');
} else {
//console.log('Adding pending message id', _id);
renderMessages.push(this.outgoingPendMessages[_id]);
}
}
});
delete_ids.forEach((_id) => {
delete this.outgoingPendMessages[_id];
});
renderMessages = renderMessages.sort(function(a, b) {
if (a.createdAt < b.createdAt) {
return 1; //nameA comes first
}
if (a.createdAt > b.createdAt) {
return -1; // nameB comes first
}
if (a.createdAt === b.createdAt) {
if (a.msg_id < b.msg_id) {
return 1; //nameA comes first
}
if (a.msg_id > b.msg_id) {
return -1; // nameB comes first
}
}
return 0; // names must be equal
});
this.setState({renderMessages: GiftedChat.append(renderMessages, [])});
if (!this.state.scrollToBottom && renderMessages.length > 0) {
//console.log('Scroll to first message');
//this.scrollToMessage(0);
}
}
this.setState({isLandscape: nextProps.isLandscape,
isTablet: nextProps.isTablet,
chat: nextProps.chat,
fontScale: nextProps.fontScale,
filter: nextProps.filter,
call: nextProps.call,
password: nextProps.password,
messages: nextProps.messages,
inviteContacts: nextProps.inviteContacts,
shareToContacts: nextProps.shareToContacts,
selectedContacts: nextProps.selectedContacts,
pinned: nextProps.pinned,
isTyping: nextProps.isTyping,
periodFilter: nextProps.periodFilter,
ssiCredentials: nextProps.ssiCredentials,
ssiConnections: nextProps.ssiConnections,
messagesCategoryFilter: nextProps.messagesCategoryFilter,
targetUri: nextProps.selectedContact ? nextProps.selectedContact.uri : nextProps.targetUri,
keys: nextProps.keys,
isTexting: nextProps.isTexting,
showDeleteMessageModal: nextProps.showDeleteMessageModal,
selectMode: nextProps.shareToContacts || nextProps.inviteContacts
});
if (nextProps.isTyping) {
setTimeout(() => {
this.setState({isTyping: false});
}, 3000);
}
}
listenforSoundNotifications() {
// Subscribe to event(s) you want when component mounted
this._onFinishedPlayingSubscription = SoundPlayer.addEventListener('FinishedPlaying', ({ success }) => {
//console.log('finished playing', success)
this.setState({playing: false, placeholder: this.default_placeholder});
})
this._onFinishedLoadingSubscription = SoundPlayer.addEventListener('FinishedLoading', ({ success }) => {
//console.log('finished loading', success)
})
this._onFinishedLoadingFileSubscription = SoundPlayer.addEventListener('FinishedLoadingFile', ({ success, name, type }) => {
//console.log('finished loading file', success, name, type)
})
this._onFinishedLoadingURLSubscription = SoundPlayer.addEventListener('FinishedLoadingURL', ({ success, url }) => {
//console.log('finished loading url', success, url)
})
}
async _launchCamera() {
let options = {maxWidth: 2000,
maxHeight: 2000,
mediaType: 'mixed',
quality:0.8,
cameraType: 'front',
formatAsMp4: true
}
const cameraAllowed = await this.props.requestCameraPermission();
if (cameraAllowed) {
await launchCamera(options, this.cameraCallback);
}
}
async _launchImageLibrary() {
let options = {maxWidth: 2000,
maxHeight: 2000,
mediaType: 'mixed',
formatAsMp4: true
}
await launchImageLibrary(options, this.libraryCallback);
}
async libraryCallback(result) {
if (!result.assets || result.assets.length === 0) {
return;
}
result.assets.forEach((asset) => {
this.cameraCallback({assets: [asset]});
});
}
async cameraCallback(result) {
if (!result.assets || result.assets.length === 0) {
return;
}
this.setState({scrollToBottom: true});
let asset = result.assets[0];
asset.preview = true;
let msg = await this.file2GiftedChat(asset);
let assetType = 'file';
if (msg.video) {
assetType = 'movie';
} else if (msg.image) {
assetType = 'photo';
}
this.outgoingPendMessages[msg.metadata.transfer_id] = msg;
this.setState({renderMessages: GiftedChat.append(this.state.renderMessages, [msg]),
cameraAsset: msg,
placeholder: 'Send ' + assetType + ' of ' + utils.beautySize(msg.metadata.filesize)
});
}
renderMessageImage =(props) => {
/*
return(
<TouchableOpacity onPress={() => this.onMessagePress(context, props.currentMessage)}>
<Image
source={{ uri: props.currentMessage.image }}
style = {{
width: '98%',
height: Dimensions.get('window').width,
resizeMode: 'cover'
}}
/>
</TouchableOpacity>
);
*/
return (
<MessageImage
{...props}
imageStyle={{
width: '98%',
height: Dimensions.get('window').width,
resizeMode: 'cover'
}}
/>
)
}
renderCustomActions = props =>
(
<CustomChatActions {...props} audioRecorded={this.audioRecorded} stopPlaying={this.stopPlaying} onRecording={this.onRecording} texting={this.state.texting} audioSendFinished={this.state.audioSendFinished} playing={this.state.playing} sendingImage={this.state.cameraAsset !==null} selectedContact={this.state.selectedContact}/>
)
customInputToolbar = props => {
return (
<InputToolbar
{...props}
renderComposer={() => {this.renderComposer}}
containerStyle={styles.chatInsideRightActionsContainer}
/>
);
};
chatInputChanged(text) {
this.setState({texting: (text.length > 0)})
}
+ recordAudio() {
+ if (!this.state.recording) {
+ if (this.state.audioRecording) {
+ this.deleteAudio();
+ } else {
+ this.onStartRecord();
+ }
+ } else {
+ this.onStopRecord();
+ }
+ }
+
+ deleteAudio() {
+ console.log('Delete audio');
+ this.setState({audioRecording: null, recording: false});
+ if (this.recordingStopTimer !== null) {
+ clearTimeout(this.recordingStopTimer);
+ this.recordingStopTimer = null;
+ }
+ }
+
+ async onStartRecord () {
+ console.log('Start recording...');
+ this.setState({recording: true});
+ this.recordingStopTimer = setTimeout(() => {
+ this.stopRecording();
+ }, 20000);
+ AudioRecord.start();
+ };
+
+ stopRecording() {
+ console.log('Stop recording...');
+ this.setState({recording: false});
+ if (this.recordingStopTimer !== null) {
+ clearTimeout(this.recordingStopTimer);
+ this.recordingStopTimer = null;
+ }
+ this.onRecording(false);
+ this.onStopRecord();
+ }
+
+ async onStopRecord () {
+ console.log('Stop recording...');
+ const result = await AudioRecord.stop();
+ this.audioRecorded(result);
+ this.setState({audioRecording: result});
+ };
+
resetContact() {
this.stopRecordingTimer()
this.outgoingPendMessages = {};
this.setState({
recording: false,
texting: false,
audioRecording: null,
cameraAsset: null,
placeholder: this.default_placeholder,
audioSendFinished: false
});
}
renderComposer(props) {
return(
<Composer
{...props}
onTextChanged={(text) => this.setState({ composerText: text })}
text={this.state.composerText}
multiline={true}
placeholderTextColor={'red'}
></Composer>
)
}
onRecording(state) {
this.setState({recording: state});
if (state) {
this.startRecordingTimer();
} else {
this.stopRecordingTimer()
}
}
startRecordingTimer() {
let i = 0;
this.setState({placeholder: 'Recording audio'});
this.recordingTimer = setInterval(() => {
i = i + 1
this.setState({placeholder: 'Recording audio ' + i + 's'});
}, 1000);
}
stopRecordingTimer() {
if (this.recordingTimer) {
clearInterval(this.recordingTimer);
this.recordingTimer = null;
this.setState({placeholder: this.default_placeholder});
}
}
updateMessageMetadata(metadata) {
let renderMessages = this.state.renderMessages;
let newRenderMessages = [];
renderMessages.forEach((message) => {
if (metadata.transfer_id === message._id) {
message.metadata = metadata;
}
newRenderMessages.push(message);
});
this.setState({renderMessages: GiftedChat.append(newRenderMessages, [])});
}
async startPlaying(message) {
if (this.state.playing || this.state.recording) {
console.log('Already playing or recording');
return;
}
this.setState({playing: true, placeholder: 'Playing audio message'});
message.metadata.playing = true;
this.updateMessageMetadata(message.metadata);
if (Platform.OS === "android") {
const msg = await audioRecorderPlayer.startPlayer(message.audio);
console.log('Audio playback started', message.audio);
audioRecorderPlayer.addPlayBackListener((e) => {
//console.log('duration', e.duration, e.currentPosition);
if (e.duration === e.currentPosition) {
this.setState({playing: false, placeholder: this.default_placeholder});
//console.log('Audio playback ended', message.audio);
message.metadata.playing = false;
this.updateMessageMetadata(message.metadata);
}
this.setState({
currentPositionSec: e.currentPosition,
currentDurationSec: e.duration,
playTime: audioRecorderPlayer.mmssss(Math.floor(e.currentPosition)),
duration: audioRecorderPlayer.mmssss(Math.floor(e.duration)),
});
});
} else {
/*
console.log('startPlaying', file);
this.sound = new Sound(file, '', error => {
if (error) {
console.log('failed to load the file', file, error);
}
});
return;
*/
try {
SoundPlayer.playUrl('file://'+message.audio);
this.setState({playing: true, placeholder: 'Playing audio message'});
} catch (e) {
console.log(`cannot play the sound file`, e)
}
try {
const info = await SoundPlayer.getInfo() // Also, you need to await this because it is async
console.log('Sound info', info) // {duration: 12.416, currentTime: 7.691}
} catch (e) {
console.log('There is no song playing', e)
}
}
};
async stopPlaying(message) {
console.log('Audio playback ended', message.audio);
this.setState({playing: false, placeholder: this.default_placeholder});
message.metadata.playing = false;
this.updateMessageMetadata(message.metadata);
if (Platform.OS === "android") {
const msg = await audioRecorderPlayer.stopPlayer();
} else {
SoundPlayer.stop();
}
}
async audioRecorded(file) {
const placeholder = file ? 'Delete or send audio...' : this.default_placeholder;
if (file) {
console.log('Audio recording ready to send', file);
} else {
console.log('Audio recording removed');
}
this.setState({recording: false, placeholder: placeholder, audioRecording: file});
}
renderSend = (props) => {
let chatRightActionsContainer = Platform.OS === 'ios' ? styles.chatRightActionsContaineriOS : styles.chatRightActionsContainer;
if (this.state.recording) {
return (
<View style={styles.chatSendContainer}>
</View>
);
} else {
if (this.state.cameraAsset) {
return (
<Send {...props}>
<View style={styles.chatSendContainer}>
<TouchableOpacity onPress={this.deleteCameraAsset}>
<Icon
style={chatRightActionsContainer}
type="font-awesome"
name="delete"
size={20}
color='red'
/>
</TouchableOpacity>
<TouchableOpacity onPress={this.sendCameraAsset}>
<Icon
type="font-awesome"
name="send"
style={styles.chatSendArrow}
size={20}
color='gray'
/>
</TouchableOpacity>
</View>
</Send>
);
} else if (this.state.audioRecording) {
return (
<Send {...props}>
<View style={styles.chatSendContainer}>
<TouchableOpacity onPress={this.sendAudioFile}>
<Icon
type="font-awesome"
name="send"
style={styles.chatSendArrow}
size={20}
color='gray'
/>
</TouchableOpacity>
</View>
</Send>
);
} else {
if (this.state.playing || (this.state.selectedContact && this.state.selectedContact.tags.indexOf('test') > -1)) {
return <View></View>;
} else {
return (
<Send {...props}>
<View style={styles.chatSendContainer}>
{this.state.texting ?
null
:
<TouchableOpacity onPress={this._launchCamera}>
<Icon
style={chatRightActionsContainer}
type="font-awesome"
name="camera"
size={20}
color='gray'
/>
</TouchableOpacity>
}
{this.state.texting ?
null
:
<TouchableOpacity onPress={this._launchImageLibrary} onLongPress={this._pickDocument}>
<Icon
style={chatRightActionsContainer}
type="font-awesome"
name="paperclip"
size={20}
color='gray'
/>
</TouchableOpacity>
}
<Icon
type="font-awesome"
name="send"
style={styles.chatSendArrow}
size={20}
color={'gray'}
/>
</View>
</Send>
);
}
}
}
};
setTargetUri(uri, contact) {
//console.log('Set target uri uri in history list', uri);
this.props.setTargetUri(uri, contact);
}
setFavoriteUri(uri) {
return this.props.setFavoriteUri(uri);
}
setBlockedUri(uri) {
return this.props.setBlockedUri(uri);
}
renderItem(object) {
let item = object.item || object;
let invitedParties = [];
let uri = item.uri;
let myDisplayName;
let username = uri.split('@')[0];
if (this.state.myContacts && this.state.myContacts.hasOwnProperty(uri)) {
myDisplayName = this.state.myContacts[uri].name;
}
if (this.state.myInvitedParties && this.state.myInvitedParties.hasOwnProperty(username)) {
invitedParties = this.state.myInvitedParties[username];
}
if (myDisplayName) {
if (item.name === item.uri || item.name !== myDisplayName) {
item.name = myDisplayName;
}
}
return(
<ContactCard
contact={item}
setTargetUri={this.setTargetUri}
chat={this.state.chat}
fontScale={this.state.fontScale}
orientation={this.props.orientation}
isTablet={this.state.isTablet}
isLandscape={this.state.isLandscape}
contacts={this.state.contacts}
defaultDomain={this.props.defaultDomain}
accountId={this.state.accountId}
favoriteUris={this.state.favoriteUris}
messages={this.state.renderMessages}
pinned={this.state.pinned}
unread={item.unread}
toggleBlocked={this.props.toggleBlocked}
sendPublicKey={this.props.sendPublicKey}
selectMode={this.state.selectMode}
+ startCall={this.props.startCall}
+ recordAudio={this.recordAudio}
+ sendAudio={this.sendAudioFile}
+ deleteAudio={this.deleteAudio}
+ recording={this.state.recording}
+ audioRecording = {this.state.audioRecording}
/>);
}
findObjectByKey(array, key, value) {
for (var i = 0; i < array.length; i++) {
if (array[i][key] === value) {
return array[i];
}
}
return null;
}
closeMessageModal() {
this.setState({showMessageModal: false, message: null});
}
closeEditMessageModal() {
this.setState({showEditMessageModal: false, message: null});
}
loadEarlierMessages() {
//console.log('Load earlier messages...');
this.setState({scrollToBottom: false, isLoadingEarlier: true});
this.props.loadEarlierMessages();
}
sendEditedMessage(message, text) {
if (!this.state.selectedContact.uri) {
return;
}
if (message.text === text) {
return;
}
this.props.deleteMessage(message._id, this.state.selectedContact.uri);
message._id = uuid.v4();
message.key = message._id;
message.text = text;
this.props.sendMessage(this.state.selectedContact.uri, message);
}
onSendMessage(messages) {
let uri;
if (!this.state.selectedContact) {
if (this.state.targetUri && this.state.chat) {
let contacts = this.searchedContact(this.state.targetUri);
if (contacts.length !== 1) {
return;
}
uri = contacts[0].uri;
} else {
return;
}
} else {
uri = this.state.selectedContact.uri;
}
messages.forEach((message) => {
/*
sent: true,
// Mark the message as received, using two tick
received: true,
// Mark the message as pending with a clock loader
pending: true,
*/
message.encrypted = this.state.selectedContact && this.state.selectedContact.publicKey ? 2 : 0;
this.props.sendMessage(uri, message);
});
this.setState({renderMessages: GiftedChat.append(this.state.renderMessages, messages)});
}
searchedContact(uri, contact=null) {
if (uri.indexOf(' ') > -1) {
return [];
}
const item = this.props.newContactFunc(uri.toLowerCase(), null, {src: 'search_contact'});
if (!item) {
return [];
}
if (contact) {
item.name = contact.name;
item.photo = contact.photo;
}
return [item];
}
getServerHistory() {
if (!this.state.accountId) {
return;
}
if (this.ended || !this.state.accountId || this.state.isRefreshing) {
return;
}
//console.log('Get server history...');
this.setState({isRefreshing: true});
let history = [];
let localTime;
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 = 'incoming'; return elem});
history = history.concat(data.received);
}
if (data.placed) {
data.placed.map(elem => {elem.direction = 'outgoing'; 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;
elem.id = uuid.v4();
if (!elem.tags) {
elem.tags = [];
}
if (elem.remoteParty.indexOf('@conference.') > -1) {
return null;
}
elem.uri = elem.remoteParty.toLowerCase();
let uri_els = elem.uri.split('@');
let username = uri_els[0];
let domain;
if (uri_els.length > 1) {
domain = uri_els[1];
}
if (elem.uri.indexOf('@guest.') > -1) {
if (!elem.displayName) {
elem.uri = 'guest@' + elem.uri.split('@')[1];
} else {
elem.uri = elem.displayName.toLowerCase().replace(/\s|\-|\(|\)/g, '') + '@' + elem.uri.split('@')[1];
}
}
if (utils.isPhoneNumber(elem.uri)) {
username = username.replace(/\s|\-|\(|\)/g, '');
username = username.replace(/^00/, "+");
elem.uri = username;
}
if (known.indexOf(elem.uri) > -1) {
return null;
}
known.push(elem.uri);
if (elem.displayName) {
elem.name = elem.displayName;
} else {
elem.name = elem.uri;
}
if (elem.remoteParty.indexOf('@videoconference.') > -1) {
elem.conference = true;
elem.media = ['audio', 'video', 'chat'];
}
if (elem.uri === this.state.accountId) {
elem.name = this.props.myDisplayName || 'Myself';
}
if (!elem.media || !Array.isArray(elem.media)) {
elem.media = ['audio'];
}
if (elem.timezone !== undefined) {
localTime = momenttz.tz(elem.startTime, elem.timezone).toDate();
elem.startTime = localTime;
elem.timestamp = localTime;
localTime = momenttz.tz(elem.stopTime, elem.timezone).toDate();
elem.stopTime = localTime;
}
if (elem.direction === 'incoming' && elem.duration === 0) {
elem.tags.push('missed');
}
return elem;
});
this.props.saveHistory(history);
if (this.ended) {
return;
}
this.setState({isRefreshing: false});
}
}, (errorCode) => {
console.log('Error getting call history from server', errorCode);
});
this.setState({isRefreshing: false});
}
deleteCameraAsset() {
if (this.state.cameraAsset && this.state.cameraAsset.metadata.transfer_id in this.outgoingPendMessages) {
delete this.outgoingPendMessages[this.state.cameraAsset.metadata.transfer_id]
}
this.setState({cameraAsset: null, placeholder: this.default_placeholder});
this.props.getMessages(this.state.selectedContact.uri);
}
sendCameraAsset() {
this.transferFile(this.state.cameraAsset);
this.setState({cameraAsset: null, placeholder: this.default_placeholder});
}
async sendAudioFile() {
if (this.state.audioRecording) {
this.setState({audioSendFinished: true, placeholder: this.default_placeholder});
setTimeout(() => {
this.setState({audioSendFinished: false});
}, 10);
let msg = await this.file2GiftedChat(this.state.audioRecording);
this.transferFile(msg);
this.setState({audioRecording: null});
}
}
async _pickDocument() {
try {
const result = await DocumentPicker.pick({
type: [DocumentPicker.types.allFiles],
copyTo: 'documentDirectory',
mode: 'import',
allowMultiSelection: false,
});
const fileUri = result[0].fileCopyUri;
if (!fileUri) {
console.log('File URI is undefined or null');
return;
}
let msg = await this.file2GiftedChat(fileUri);
this.transferFile(msg);
} catch (err) {
if (DocumentPicker.isCancel(err)) {
console.log('User cancelled file picker');
} else {
console.log('DocumentPicker err => ', err);
throw err;
}
}
};
postChatSystemMessage(text, imagePath=null) {
var id = uuid.v4();
let giftedChatMessage;
if (imagePath) {
giftedChatMessage = {
_id: id,
key: id,
createdAt: new Date(),
text: text,
image: 'file://' + imagePath,
user: {}
};
} else {
giftedChatMessage = {
_id: id,
key: id,
createdAt: new Date(),
text: text,
system: true,
};
}
this.setState({renderMessages: GiftedChat.append(this.state.renderMessages, [giftedChatMessage])});
}
transferComplete(evt) {
console.log("Upload has finished", evt);
this.postChatSystemMessage('Upload has finished');
}
transferFailed(evt) {
console.log("An error occurred while transferring the file.", evt);
this.postChatSystemMessage('Upload failed')
}
transferCanceled(evt) {
console.log("The transfer has been canceled by the user.");
this.postChatSystemMessage('Upload has canceled')
}
async transferFile(msg) {
msg.metadata.preview = false;
this.props.sendMessage(msg.metadata.receiver.uri, msg, 'application/sylk-file-transfer');
}
async file2GiftedChat(fileObject) {
var id = uuid.v4();
let uri;
if (!this.state.selectedContact) {
if (this.state.targetUri && this.state.chat) {
let contacts = this.searchedContact(this.state.targetUri);
if (contacts.length !== 1) {
return;
- }
+ }
uri = contacts[0].uri;
} else {
return;
}
} else {
uri = this.state.selectedContact.uri;
}
let filepath = fileObject.uri ? fileObject.uri : fileObject;
let basename = fileObject.fileName || filepath.split('\\').pop().split('/').pop();
basename = basename.replace(/\s|:/g, '_');
let file_transfer = { 'path': filepath,
'filename': basename,
'sender': {'uri': this.state.accountId},
'receiver': {'uri': uri},
'transfer_id': id,
'direction': 'outgoing'
};
if (filepath.startsWith('content://')) {
// on android we must copy this file early
const localPath = RNFS.DocumentDirectoryPath + "/" + this.state.accountId + "/" + uri + "/" + id + "/" + basename;
const dirname = path.dirname(localPath);
await RNFS.mkdir(dirname);
console.log('Copy', filepath, localPath);
await RNFS.copyFile(filepath, localPath);
filepath = localPath;
file_transfer.local_url = localPath;
}
let stats_filename = filepath.startsWith('file://') ? filepath.substr(7, filepath.length - 1) : filepath;
const { size } = await RNFetchBlob.fs.stat(stats_filename);
file_transfer.filesize = fileObject.fileSize || size;
if (fileObject.preview) {
file_transfer.preview = fileObject.preview;
}
if (fileObject.duration) {
file_transfer.duration = fileObject.duration;
}
if (fileObject.fileType) {
file_transfer.filetype = fileObject.fileType;
} else {
try {
let mime = await fileType(filepath);
if (mime.mime) {
file_transfer.filetype = mime.mime;
}
} catch (e) {
console.log('Error getting mime type', e.message);
}
}
let text = utils.beautyFileNameForBubble(file_transfer);
let msg = {
_id: id,
key: id,
text: text,
metadata: file_transfer,
createdAt: new Date(),
direction: 'outgoing',
user: {}
}
if (utils.isImage(basename)) {
msg.image = filepath;
} else if (utils.isAudio(basename)) {
msg.audio = filepath;
} else if (utils.isVideo(basename) || file_transfer.duration) {
msg.video = filepath;
}
return msg;
}
matchContact(contact, filter='', tags=[]) {
if (!contact) {
return false;
}
if (tags.indexOf('conference') > -1 && contact.conference) {
return true;
}
if (tags.length > 0 && !tags.some(item => contact.tags.includes(item))) {
return false;
}
if (contact.name && contact.name.toLowerCase().indexOf(filter.toLowerCase()) > -1) {
return true;
}
if (contact.uri.toLowerCase().startsWith(filter.toLowerCase())) {
return true;
}
if (!this.state.selectedContact && contact.conference && contact.metadata && filter.length > 2 && contact.metadata.indexOf(filter) > -1) {
return true;
}
return false;
}
noChatInputToolbar () {
return null;
}
onMessagePress(context, message) {
if (message.metadata && message.metadata.filename) {
//console.log('File metadata', message.metadata);
let file_transfer = message.metadata;
if (!file_transfer.local_url) {
if (!file_transfer.path) {
// this was a local created upload, don't download as the file has not yet been uploaded
this.props.downloadFunc(message.metadata, true);
}
return;
}
RNFS.exists(file_transfer.local_url).then((exists) => {
if (exists) {
if (file_transfer.local_url.endsWith('.asc')) {
if (file_transfer.error === 'decryption failed') {
this.onLongMessagePress(context, message);
} else {
this.props.decryptFunc(message.metadata);
}
} else {
this.onLongMessagePress(context, message);
//this.openFile(message)
}
} else {
if (file_transfer.path) {
// this was a local created upload, don't download as the file has not yet been uploaded
this.onLongMessagePress(context, message);
} else {
this.props.downloadFunc(message.metadata, true);
}
}
});
} else {
this.onLongMessagePress(context, message);
}
}
openFile(message) {
let file_transfer = message.metadata;
let file_path = file_transfer.local_url;
if (!file_path) {
console.log('Cannot open empty path');
return;
}
if (file_path.endsWith('.asc')) {
file_path = file_path.slice(0, -4);
console.log('Open decrypted file', file_path)
} else {
console.log('Open file', file_path)
}
if (utils.isAudio(file_transfer.filename)) {
// this.startPlaying(file_path);
return;
}
RNFS.exists(file_path).then((exists) => {
if (exists) {
FileViewer.open(file_path, { showOpenWithDialog: true })
.then(() => {
// success
})
.catch(error => {
// error
});
} else {
console.log(file_path, 'does not exist');
return;
}
});
}
onLongMessagePress(context, currentMessage) {
if (!currentMessage.metadata) {
currentMessage.metadata = {};
}
//console.log('currentMessage metadata', currentMessage.metadata);
if (currentMessage && currentMessage.text) {
let isSsiMessage = this.state.selectedContact && this.state.selectedContact.tags.indexOf('ssi') > -1;
let options = []
if (currentMessage.metadata && !currentMessage.metadata.error) {
if (!isSsiMessage && this.isMessageEditable(currentMessage)) {
options.push('Edit');
}
if (currentMessage.metadata && currentMessage.metadata.local_url) {
options.push('Open')
//
} else {
options.push('Copy');
}
}
if (!isSsiMessage) {
options.push('Delete');
}
let showResend = currentMessage.failed;
if (currentMessage.metadata && currentMessage.metadata.error === 'decryption failed') {
showResend = false;
}
if (this.state.targetUri.indexOf('@videoconference') === -1) {
if (currentMessage.direction === 'outgoing') {
if (showResend) {
options.push('Resend')
}
}
}
if (currentMessage.pinned) {
options.push('Unpin');
} else {
if (!isSsiMessage && !currentMessage.metadata.error) {
options.push('Pin');
}
}
//options.push('Info');
if (!isSsiMessage && !currentMessage.metadata.error) {
options.push('Forward');
}
if (!isSsiMessage && !currentMessage.metadata.error) {
options.push('Share');
}
if (currentMessage.local_url) {
if (utils.isImage(currentMessage.local_url)) {
options.push('Save');
}
options.push('Open');
}
if (currentMessage.metadata && currentMessage.metadata.filename) {
if (!currentMessage.metadata.filename.local_url || currentMessage.metadata.filename.error === 'decryption failed') {
if (currentMessage.metadata.direction !== 'outgoing') {
options.push('Download again');
}
} else {
options.push('Download');
}
}
options.push('Cancel');
let l = options.length - 1;
context.actionSheet().showActionSheetWithOptions({options, l}, (buttonIndex) => {
let action = options[buttonIndex];
if (action === 'Copy') {
Clipboard.setString(currentMessage.text);
} else if (action === 'Delete') {
this.setState({showDeleteMessageModal: true, currentMessage: currentMessage});
} else if (action === 'Pin') {
this.props.pinMessage(currentMessage._id);
} else if (action === 'Unpin') {
this.props.unpinMessage(currentMessage._id);
} else if (action === 'Info') {
this.setState({message: currentMessage, showMessageModal: true});
} else if (action === 'Edit') {
this.setState({message: currentMessage, showEditMessageModal: true});
} else if (action.startsWith('Share')) {
this.setState({message: currentMessage, showShareMessageModal: true});
} else if (action.startsWith('Forward')) {
this.props.forwardMessageFunc(currentMessage, this.state.targetUri);
} else if (action === 'Resend') {
this.props.reSendMessage(currentMessage, this.state.targetUri);
} else if (action === 'Save') {
this.savePicture(currentMessage.local_url);
} else if (action.startsWith('Download')) {
console.log('Starting download...');
this.props.downloadFunc(currentMessage.metadata, true);
} else if (action === 'Open') {
FileViewer.open(currentMessage.metadata.local_url, { showOpenWithDialog: true })
.then(() => {
// success
})
.catch(error => {
console.log('Failed to open', currentMessage, error.message);
});
}
});
}
};
isMessageEditable(message) {
if (message.direction === 'incoming') {
return false;
}
if (message.image || message.audio || message.video) {
return false;
}
if (message.metadata && message.metadata.filename) {
return false;
}
return true;
}
closeDeleteMessageModal() {
this.setState({showDeleteMessageModal: false});
}
async hasAndroidPermission() {
const permission = PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE;
const hasPermission = await PermissionsAndroid.check(permission);
if (hasPermission) {
return true;
}
const status = await PermissionsAndroid.request(permission);
return status === 'granted';
}
async savePicture(file) {
if (Platform.OS === "android" && !(await this.hasAndroidPermission())) {
return;
}
file = 'file://' + file;
console.log('Save to camera roll', file);
CameraRoll.save(file);
};
shouldUpdateMessage(props, nextProps) {
return true;
}
toggleShareMessageModal() {
this.setState({showShareMessageModal: !this.state.showShareMessageModal});
}
renderMessageVideo(props){
const { currentMessage } = props;
return (
<View style={styles.videoContainer}>
<VideoPlayer
video={{ uri: currentMessage.video}}
autoplay={false}
pauseOnPress={true}
showDuration={true}
controlsTimeout={2}
fullScreenOnLongPress={true}
customStyles={styles.videoPlayer}
/>
</View>
);
};
renderMessageAudio(props){
const { currentMessage } = props;
let playAudioButtonStyle = Platform.OS === 'ios' ? styles.playAudioButtoniOS : styles.playAudioButton;
if (currentMessage.metadata.playing === true) {
return (
<View style={styles.audioContainer}>
<TouchableHighlight style={styles.roundshape}>
<IconButton
size={32}
onPress={() => this.stopPlaying(currentMessage)}
style={playAudioButtonStyle}
icon="pause"
/>
</TouchableHighlight>
</View>
);
} else {
return (
<View style={styles.audioContainer}>
<TouchableHighlight style={styles.roundshape}>
<IconButton
size={32}
onPress={() => this.startPlaying(currentMessage)}
style={playAudioButtonStyle}
icon="play"
/>
</TouchableHighlight>
</View>
);
}
};
videoError() {
console.log('Video streaming error');
}
onBuffer() {
console.log('Video buffer error');
}
// https://github.com/FaridSafi/react-native-gifted-chat/issues/571
// add view after bubble
renderMessageText(props) {
const { currentMessage } = props;
if (currentMessage.video || currentMessage.image || currentMessage.audio) {
return (
<View>
<MessageText
{...props}
customTextStyle={{fontSize: 9}}
/>
</View>
);
} else {
return (
<View>
<MessageText
{...props}
customTextStyle={{fontSize: 14}}
/>
</View>
);
}
};
renderTime = (props) => {
const { currentMessage } = props;
if (currentMessage.metadata && currentMessage.metadata.preview) {
return null;
}
if (currentMessage.video) {
return (
<Time
{...props}
timeTextStyle={{
left: {
color: 'white',
},
right: {
color: 'white',
}
}}
/>
)
} else if (currentMessage.audio) {
return (
<Time
{...props}
timeTextStyle={{
left: {
color: 'white',
},
right: {
color: 'white',
}
}}
/>
)
} else {
return (
<Time
{...props}
timeTextStyle={{
left: {
color: 'black',
},
right: {
color: 'black',
}
}}
/>
)
}
}
scrollToMessage(id) {
//console.log('scrollToMessage', id);
//https://github.com/FaridSafi/react-native-gifted-chat/issues/938
this.chatListRef.current?._messageContainerRef?.current?.scrollToIndex({
animated: true,
index: id
});
}
get showChat() {
if (this.state.selectedContact) {
if (this.state.selectedContact.tags && this.state.selectedContact.tags.indexOf('blocked') > -1) {
return false;
}
if (this.state.selectedContact.uri.indexOf('@guest.') > -1) {
return false;
}
if (this.state.selectedContact.uri.indexOf('anonymous@') > -1) {
return false;
}
}
let username = this.state.targetUri ? this.state.targetUri.split('@')[0] : null;
let isPhoneNumber = username ? username.match(/^(\+|0)(\d+)$/) : false;
if (isPhoneNumber) {
return false;
}
if (this.props.selectedContact) {
return true;
}
return false;
}
ssi2GiftedChat(from_uri, content, timestamp) {
let id = uuid.v4();
let msg;
msg = {
_id: id,
key: id,
text: content,
createdAt: timestamp,
direction: 'incoming',
sent: false,
received: true,
pending: false,
system: false,
failed: false,
user: {_id: from_uri, name: from_uri}
}
return msg;
}
getSsiContacts() {
//console.log('Get SSI contacts');
let contacts = [];
if (this.state.ssiCredentials) {
this.state.ssiCredentials.forEach((item) => {
let contact = this.props.newContactFunc(item.id, 'Credential');
contact.ssiCredential = item;
contact.credential = new Object();
const schemaId = item.metadata.data['_internal/indyCredential'].schemaId;
if (schemaId === 'EwAf16U6ZphXsZq6E5qmPz:2:Bloqzone_IDIN_ver5:5.0') {
contact.schemaId = schemaId;
item.credentialAttributes.forEach((attribute) => {
contact.credential[attribute.name] = attribute.value;
if (attribute.value.length > 0) {
if (attribute.name === 'legalName') {
contact.name = attribute.value;
} else if (attribute.name === 'acceptDateTime') {
contact.timestamp = attribute.value.toDate("dd-mm-yy hh:ii:ss");
} else if (attribute.name === 'createdAt') {
contact.timestamp = attribute.value;
} else if (attribute.name === 'emailAddress') {
contact.email = attribute.value;
}
}
});
}
if (contact.credential.initials) {
contact.name = contact.credential.initials;
}
if (contact.credential.legalName) {
contact.name = contact.name + ' ' + contact.credential.legalName;
}
if (contact.credential.dob) {
contact.name = contact.name + ' (' + contact.credential.dob + ')';
}
if (contact.credential.birthDate) {
contact.name = contact.name + ' (' + contact.credential.birthDate + ')';
}
if (contact.credential.acceptDateTime && item.state === 'done') {
contact.lastMessage = 'Credential issued at ' + contact.credential.acceptDateTime + ' (' + item.state + ')';
}
contact.tags.push('ssi');
contact.tags.push('ssi-credential');
contact.tags.push('readonly');
contacts.push(contact);
});
}
if (this.state.ssiConnections) {
this.state.ssiConnections.forEach((item) => {
//console.log('Contacts SSI connection', item);
let uri = item.id;
let contact = this.props.newContactFunc(uri, item.theirLabel);
contact.credential = new Object();
contact.timestamp = item.createdAt;
contact.lastMessage = 'Connection is ' + item.state;
contact.tags.push('ssi');
contact.tags.push('ssi-connection');
contact.ssiConnection = item;
contacts.push(contact);
});
}
return contacts;
}
render() {
let searchExtraItems = [];
let items = [];
let matchedContacts = [];
let ssiContacts = [];
let messages = this.state.renderMessages;
let contacts = [];
//console.log('----');
//console.log('--- Render contacts with filter', this.state.filter);
//console.log('--- Render contacts', this.state.selectedContact);
//console.log(this.state.renderMessages);
if (this.state.filter === 'ssi') {
contacts = this.getSsiContacts();
} else {
Object.keys(this.state.myContacts).forEach((uri) => {
contacts.push(this.state.myContacts[uri]);
});
}
//console.log(contacts);
let chatInputClass = this.customInputToolbar;
if (this.state.selectedContact) {
if (this.state.selectedContact.uri.indexOf('@videoconference') > -1) {
chatInputClass = this.noChatInputToolbar;
}
if (this.state.selectedContact.tags.indexOf('test') > -1) {
chatInputClass = this.noChatInputToolbar;
}
} else if (!this.state.chat) {
chatInputClass = this.noChatInputToolbar;
}
if (!this.state.selectedContact && this.state.filter) {
items = contacts.filter(contact => this.matchContact(contact, this.state.targetUri, [this.state.filter]));
} else {
items = contacts.filter(contact => this.matchContact(contact, this.state.targetUri));
searchExtraItems = searchExtraItems.concat(this.state.contacts);
if (this.state.targetUri && this.state.targetUri.length > 2 && !this.state.selectedContact && !this.state.inviteContacts) {
matchedContacts = searchExtraItems.filter(contact => this.matchContact(contact, this.state.targetUri));
} else if (this.state.selectedContact && this.state.selectedContact.type === 'contact') {
matchedContacts.push(this.state.selectedContact);
} else if (this.state.selectedContact) {
items = [this.state.selectedContact];
}
items = items.concat(matchedContacts);
}
if (this.state.targetUri) {
items = items.concat(this.searchedContact(this.state.targetUri, this.state.selectedContact));
}
if (this.state.filter && this.state.targetUri) {
items = contacts.filter(contact => this.matchContact(contact, this.state.targetUri));
}
const known = [];
items = items.filter((elem) => {
//console.log(elem.uri);
if (this.state.shareToContacts && elem.tags.indexOf('test') > -1) {
//console.log('Remove', elem.uri, 'test');
return;
}
if (this.state.inviteContacts && elem.tags.indexOf('conference') > -1 ) {
//console.log('Remove', elem.uri, 'conf');
return;
}
if (this.state.shareToContacts && elem.uri === this.state.accountId) {
//console.log('Remove', elem.uri, 'myself');
return;
}
if (this.state.accountId === elem.uri && elem.tags.length === 0) {
//console.log('Remove', elem.uri, 'no tags');
return;
}
if (this.state.shareToContacts && elem.uri.indexOf('@') === -1) {
//console.log('Remove', elem.uri, 'no @');
return;
}
if (known.indexOf(elem.uri) <= -1) {
known.push(elem.uri);
return elem;
}
});
items.forEach((item) => {
item.showActions = false;
if (item.uri.indexOf('@videoconference.') === -1) {
item.conference = false;
} else {
item.conference = true;
}
if (this.state.selectedContacts && this.state.selectedContacts.indexOf(item.uri) > -1) {
item.selected = true;
} else {
item.selected = false;
}
});
let filteredItems = [];
items.reverse();
var todayStart = new Date();
todayStart.setHours(0,0,0,0);
var yesterdayStart = new Date();
yesterdayStart.setDate(todayStart.getDate() - 2);
yesterdayStart.setHours(0,0,0,0);
items.forEach((item) => {
const fromDomain = '@' + item.uri.split('@')[1];
if (this.state.periodFilter === 'today') {
if(item.timestamp < todayStart) {
return;
}
}
if (item.uri === 'anonymous@anonymous.invalid' && this.state.filter !== 'blocked') {
return;
}
if (this.state.periodFilter === 'yesterday') {
if(item.timestamp < yesterdayStart || item.timestamp > todayStart) {
return;
}
}
if (this.state.inviteContacts && item.uri.indexOf('@videoconference.') > -1) {
return;
}
if (item.uri === this.state.accountId && !item.direction) {
return;
}
if (this.state.filter && item.tags.indexOf(this.state.filter) > -1) {
filteredItems.push(item);
} else if (this.state.blockedUris.indexOf(item.uri) === -1 && this.state.blockedUris.indexOf(fromDomain) === -1) {
filteredItems.push(item);
}
//console.log(item.uri, item.tags);
});
items = filteredItems;
items.sort((a, b) => (a.timestamp < b.timestamp) ? 1 : -1)
if (items.length === 1) {
items[0].showActions = true;
if (items[0].tags.indexOf('ssi-credential') > -1) {
let content = '';
let m;
chatInputClass = this.noChatInputToolbar;
items[0].ssiCredential.credentialAttributes.forEach((attribute) => {
content = content + attribute.name + ": " + attribute.value + '\n';
});
m = this.ssi2GiftedChat(items[0].uri, content.trim(), items[0].timestamp);
messages.push(m);
m = this.ssi2GiftedChat(items[0].uri, 'SSI credential body' , items[0].timestamp);
m.system = true;
messages.push(m);
content = '';
content = content + 'Id: ' + items[0].ssiCredential.id;
content = content + '\nState: ' + items[0].ssiCredential.state;
content = content + '\nSchema Id:' + items[0].schemaId;
let issuer = this.state.ssiConnections.filter(x => x.id === items[0].ssiCredential.connectionId);
if (issuer.length === 1) {
content = content + '\nIssuer: ' + issuer[0].theirLabel;
} else {
content = content + '\nIssuer: : ' + items[0].ssiCredential.connectionId;
}
m = this.ssi2GiftedChat(items[0].uri, content.trim(), items[0].timestamp);
messages.push(m);
m = this.ssi2GiftedChat(items[0].uri, 'SSI credential details' , items[0].timestamp);
m.system = true;
messages.push(m);
}
if (items[0].tags.indexOf('ssi-connection') > -1) {
let content = '';
let m;
m = this.ssi2GiftedChat(items[0].uri, 'SSI messages' , items[0].timestamp);
m.system = true;
messages.push(m);
chatInputClass = this.noChatInputToolbar;
content = 'Role: ' + items[0].ssiConnection.role;
m = this.ssi2GiftedChat(items[0].uri, content.trim(), items[0].timestamp);
messages.push(m);
content = 'State: ' + items[0].ssiConnection.state;
m = this.ssi2GiftedChat(items[0].uri, content.trim(), items[0].timestamp);
messages.push(m);
content = 'Multiple use: ' + items[0].ssiConnection.multiUseInvitation;
m = this.ssi2GiftedChat(items[0].uri, content.trim(), items[0].timestamp);
messages.push(m);
if (items[0].ssiConnection.mediatorId) {
content = 'Mediator: ' + items[0].ssiConnection.mediatorId;
m = this.ssi2GiftedChat(items[0].uri, content.trim(), items[0].timestamp);
messages.push(m);
}
content = 'Id: ' + items[0].ssiConnection.id;
m = this.ssi2GiftedChat(items[0].uri, content.trim(), items[0].timestamp);
messages.push(m);
content = 'Did: ' + items[0].ssiConnection.did;
m = this.ssi2GiftedChat(items[0].uri, content.trim(), items[0].timestamp);
messages.push(m);
content = 'From: ' + items[0].ssiConnection.theirLabel;
m = this.ssi2GiftedChat(items[0].uri, content.trim(), items[0].timestamp);
messages.push(m);
m = this.ssi2GiftedChat(items[0].uri, 'SSI connection details' , items[0].timestamp);
m.system = true;
messages.push(m);
}
}
let columns = 1;
if (this.state.isTablet) {
columns = this.props.orientation === 'landscape' ? 3 : 2;
} else {
columns = this.props.orientation === 'landscape' ? 2 : 1;
}
const chatContainer = this.props.orientation === 'landscape' ? styles.chatLandscapeContainer : styles.chatPortraitContainer;
const container = this.props.orientation === 'landscape' ? styles.landscapeContainer : styles.portraitContainer;
const contactsContainer = this.props.orientation === 'landscape' ? styles.contactsLandscapeContainer : styles.contactsPortraitContainer;
const borderClass = (messages.length > 0 && !this.state.chat) ? styles.chatBorder : null;
let pinned_messages = [];
if (this.state.pinned) {
messages.forEach((m) => {
if (m.pinned) {
pinned_messages.push(m);
}
});
messages = pinned_messages;
if (pinned_messages.length === 0) {
let msg = {
_id: uuid.v4(),
text: 'No pinned messages found. Touch individual messages to pin them.',
system: true
}
pinned_messages.push(msg);
}
}
let showLoadEarlier = (this.state.myContacts && this.state.selectedContact && this.state.selectedContact.uri in this.state.myContacts && this.state.myContacts[this.state.selectedContact.uri].totalMessages && this.state.myContacts[this.state.selectedContact.uri].totalMessages > messages.length) ? true: false;
messages.forEach((m) => {
});
return (
<SafeAreaView style={container}>
{items.length === 1 ?
(this.renderItem(items[0]))
:
<FlatList
horizontal={false}
numColumns={columns}
onRefresh={this.getServerHistory}
onLongPress={this.onLongMessagePress}
refreshing={this.state.isRefreshing}
data={items}
renderItem={this.renderItem}
listKey={item => item.id}
key={this.props.orientation}
loadEarlier
/>
}
{this.showChat && !this.state.inviteContacts?
<View style={[chatContainer, borderClass]}>
<GiftedChat innerRef={this.chatListRef}
messages={messages}
onSend={this.onSendMessage}
alwaysShowSend={true}
onLongPress={this.onLongMessagePress}
onPress={this.onMessagePress}
renderInputToolbar={chatInputClass}
renderBubble={renderBubble}
renderMessageText={this.renderMessageText}
renderMessageImage={this.renderMessageImage}
renderMessageAudio={this.renderMessageAudio}
renderMessageVideo={this.renderMessageVideo}
shouldUpdateMessage={this.shouldUpdateMessage}
- renderActions={this.renderCustomActions}
renderTime={this.renderTime}
placeholder={this.state.placeholder}
lockStyle={styles.lock}
renderSend={this.renderSend}
scrollToBottom={this.state.scrollToBottom}
inverted={true}
maxInputLength={16000}
tickStyle={{ color: 'green' }}
infiniteScroll
loadEarlier={showLoadEarlier}
isLoadingEarlier={this.state.isLoadingEarlier}
onLoadEarlier={this.loadEarlierMessages}
isTyping={this.state.isTyping}
onInputTextChanged={text => this.chatInputChanged(text)}
/>
</View>
: (items.length === 1) ?
<View style={[chatContainer, borderClass]}>
<GiftedChat innerRef={this.chatListRef}
messages={messages}
renderInputToolbar={() => { return null }}
renderBubble={renderBubble}
renderMessageText={this.renderMessageText}
renderMessageImage={this.renderMessageImage}
renderMessageAudio={this.renderMessageAudio}
renderMessageVideo={this.renderMessageVideo}
onSend={this.onSendMessage}
lockStyle={styles.lock}
onLongPress={this.onLongMessagePress}
shouldUpdateMessage={this.shouldUpdateMessage}
onPress={this.onMessagePress}
scrollToBottom={this.state.scrollToBottom}
inverted={true}
timeTextStyle={{ left: { color: 'red' }, right: { color: 'black' } }}
infiniteScroll
loadEarlier={showLoadEarlier}
onLoadEarlier={this.loadEarlierMessages}
/>
</View>
: null
}
<DeleteMessageModal
show={this.state.showDeleteMessageModal}
close={this.closeDeleteMessageModal}
contact={this.state.selectedContact}
deleteMessage={this.props.deleteMessage}
message={this.state.currentMessage}
/>
<MessageInfoModal
show={this.state.showMessageModal}
message={this.state.message}
close={this.closeMessageModal}
/>
<EditMessageModal
show={this.state.showEditMessageModal}
message={this.state.message}
close={this.closeEditMessageModal}
sendEditedMessage={this.sendEditedMessage}
/>
<ShareMessageModal
show={this.state.showShareMessageModal}
message={this.state.message}
close={this.toggleShareMessageModal}
/>
</SafeAreaView>
);
}
}
ContactsListBox.propTypes = {
account : PropTypes.object,
password : PropTypes.string.isRequired,
config : PropTypes.object.isRequired,
targetUri : PropTypes.string,
selectedContact : PropTypes.object,
contacts : PropTypes.array,
chat : PropTypes.bool,
orientation : PropTypes.string,
setTargetUri : PropTypes.func,
isTablet : PropTypes.bool,
isLandscape : PropTypes.bool,
refreshHistory : PropTypes.bool,
saveHistory : PropTypes.func,
myDisplayName : PropTypes.string,
myPhoneNumber : PropTypes.string,
setFavoriteUri : PropTypes.func,
saveConference : PropTypes.func,
myInvitedParties: PropTypes.object,
setBlockedUri : PropTypes.func,
favoriteUris : PropTypes.array,
blockedUris : PropTypes.array,
filter : PropTypes.string,
periodFilter : PropTypes.string,
defaultDomain : PropTypes.string,
saveContact : PropTypes.func,
myContacts : PropTypes.object,
messages : PropTypes.object,
getMessages : PropTypes.func,
sendMessage : PropTypes.func,
reSendMessage : PropTypes.func,
deleteMessage : PropTypes.func,
pinMessage : PropTypes.func,
unpinMessage : PropTypes.func,
deleteMessages : PropTypes.func,
sendPublicKey : PropTypes.func,
inviteContacts : PropTypes.bool,
shareToContacts : PropTypes.bool,
selectedContacts: PropTypes.array,
toggleBlocked : PropTypes.func,
togglePinned : PropTypes.func,
loadEarlierMessages: PropTypes.func,
newContactFunc : PropTypes.func,
messageZoomFactor: PropTypes.string,
isTyping : PropTypes.bool,
fontScale : PropTypes.number,
call : PropTypes.object,
ssiCredentials : PropTypes.array,
ssiConnections : PropTypes.array,
keys : PropTypes.object,
downloadFunc : PropTypes.func,
decryptFunc : PropTypes.func,
forwardMessageFunc: PropTypes.func,
messagesCategoryFilter: PropTypes.string,
- requestCameraPermission: PropTypes.func
+ requestCameraPermission: PropTypes.func,
+ startCall: PropTypes.func
};
export default ContactsListBox;
exports.renderBubble = renderBubble;
diff --git a/app/components/ReadyBox.js b/app/components/ReadyBox.js
index 45f6970..0afaf6d 100644
--- a/app/components/ReadyBox.js
+++ b/app/components/ReadyBox.js
@@ -1,1106 +1,1105 @@
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,
messagesCategoryFilter: 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,
keys: this.props.keys,
isTexting: this.props.isTexting,
keyboardVisible: this.props.keyboardVisible,
contentTypes: this.props.contentTypes,
sourceContact: this.props.sourceContact
};
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.selectedContact) {
+ this.setState({chat: !this.chatDisabledForUri(nextProps.selectedContact.uri), targetUri: nextProps.selectedContact.uri});
+ } else {
+ this.setState({chat: false, targetUri: ''});
}
if (nextProps.selectedContact !== this.state.selectedContact) {
this.setState({'messagesCategoryFilter': null});
if (this.navigationRef && !this.state.selectedContact) {
this.navigationRef.scrollToIndex({animated: true, index: 0});
}
if (this.state.selectedContact && this.state.pinned) {
this.props.togglePinned(this.state.selectedContact.uri);
}
}
if (!nextProps.historyFilter && this.state.historyFilter) {
this.filterHistory(null);
}
if (nextProps.historyFilter === 'ssi' && !nextProps.selectedContact) {
this.setState({'historyCategoryFilter': 'ssi'});
if (this.navigationRef && !this.state.selectedContact) {
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();
}
let newMyContacts = nextProps.myContacts;
if (nextProps.sourceContact && nextProps.sourceContact.uri in newMyContacts) {
console.log('Discard contact', nextProps.sourceContact.uri);
delete newMyContacts[nextProps.sourceContact.uri];
}
this.setState({myInvitedParties: nextProps.myInvitedParties,
myContacts: newMyContacts,
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,
keys: nextProps.keys,
isTexting: nextProps.isTexting,
keyboardVisible: nextProps.keyboardVisible,
contentTypes: nextProps.contentTypes,
sourceContact: nextProps.sourceContact
});
}
getTargetUri(uri) {
return utils.normalizeUri(uri, this.props.defaultDomain);
}
async componentDidMount() {
this.ended = false;
}
componentWillUnmount() {
this.ended = true;
}
filterHistory(filter) {
if (this.ended) {
return;
}
//console.log('filterHistory', filter);
if (this.state.selectedContact) {
if (!filter && this.state.pinned) {
this.props.togglePinned(this.state.selectedContact.uri);
}
if (filter === 'pinned') {
this.props.togglePinned(this.state.selectedContact.uri);
return;
}
if (filter === this.state.messagesCategoryFilter) {
this.setState({'messagesCategoryFilter': null});
} else {
this.setState({'messagesCategoryFilter': filter});
}
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.keyboardVisible) {
return;
}
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 false;
}
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;
}
// This URLs are used to request SSI credentials
if (new_uri && new_uri.startsWith('https://ssimandate.vismaconnect.nl/api/acapy?c_i=')) {
this.props.handleSSIEnrolment(new_uri);
return;
}
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() {
if (!this.props.canSend()) {
return true;
}
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 (<Button style={buttonStyle} onPress={() => {this.toggleQRCodeScanner()}}>{title}</Button>);
}
return (<Button style={buttonStyle} onPress={() => {this.filterHistory(key)}}>{title}</Button>);
}
bounceNavigation() {
if (this.ended) {
return;
}
setTimeout(() => {
if (this.ended) {
return;
}
if (this.navigationRef && !this.state.selectedContact) {
this.navigationRef.scrollToIndex({animated: true, index: Math.floor(this.navigationItems.length / 2)});
}
}, 3000);
setTimeout(() => {
if (this.ended) {
return;
}
if (this.navigationRef && !this.state.selectedContact) {
this.navigationRef.scrollToIndex({animated: true, index: this.navigationItems.length-1});
}
}, 4500);
setTimeout(() => {
if (this.ended) {
return;
}
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}
];
}
if (this.state.selectedContact) {
let content_items = [];
if ('pinned' in this.state.contentTypes) {
content_items.push({key: 'pinned', title: 'Pinned', enabled: true, selected: this.state.pinned});
}
if ('text' in this.state.contentTypes) {
content_items.push({key: 'text', title: 'Text', enabled: true, selected: this.state.messagesCategoryFilter === 'text'});
}
if ('audio' in this.state.contentTypes) {
content_items.push({key: 'audio', title: 'Audio', enabled: true, selected: this.state.messagesCategoryFilter === 'audio'});
}
if ('image' in this.state.contentTypes) {
content_items.push({key: 'image', title: 'Images', enabled: true, selected: this.state.messagesCategoryFilter === 'image'});
}
if ('movie' in this.state.contentTypes) {
content_items.push({key: 'movie', title: 'Movies', enabled: true, selected: this.state.messagesCategoryFilter === 'movie'});
}
if ('failed' in this.state.contentTypes) {
content_items.push({key: 'failed', title: 'Failed', enabled: true, selected: this.state.messagesCategoryFilter === 'failed'});
}
if ('paused' in this.state.contentTypes) {
content_items.push({key: 'paused', title: 'Paused', enabled: true, selected: this.state.messagesCategoryFilter === 'paused'});
}
if ('large' in this.state.contentTypes) {
content_items.push({key: 'large', title: 'Large', enabled: true, selected: this.state.messagesCategoryFilter === 'large'});
}
return content_items;
}
return [
{key: null, title: 'All', enabled: true, selected: false},
+ {key: 'favorite', title: 'Favorites', enabled: this.state.favoriteUris.length > 0, selected: this.state.historyCategoryFilter === 'favorite'},
{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() {
if (!this.props.canSend()) {
return false;
}
let uri = this.state.targetUri.toLowerCase();
return uri.length === 0 && !this.state.shareToContacts && !this.state.inviteContacts;
}
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('RB', this.state.myContacts);
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 (
<Fragment>
<View style={styles.container}>
<View >
{this.showSearchBar && !this.state.isLandscape ?
<View style={URIContainerClass}>
<URIInput
defaultValue={this.state.targetUri}
onChange={this.handleTargetChange}
onSelect={this.handleTargetSelect}
shareToContacts={this.state.shareToContacts}
inviteContacts={this.state.inviteContacts}
autoFocus={false}
/>
</View>
: null}
{this.showButtonsBar ?
<View style={uriGroupClass}>
{this.showSearchBar && this.state.isLandscape ?
<View style={URIContainerClass}>
<URIInput
defaultValue={this.state.targetUri}
onChange={this.handleTargetChange}
onSelect={this.handleTargetSelect}
shareToContacts={this.state.shareToContacts}
inviteContacts={this.state.inviteContacts}
autoFocus={false}
/>
</View>
: null}
{showBackToCallButton ?
<View style={buttonGroupClass}>
<Button
mode="contained"
style={styles.backButton}
onPress={this.props.goBackFunc}
accessibilityLabel={backButtonTitle}
>{backButtonTitle}
</Button>
</View>
:
<View style={buttonGroupClass}>
<View style={styles.buttonContainer}>
<TouchableHighlight style={styles.roundshape}>
<IconButton
style={this.chatButtonDisabled ? disabledGreenButtonClass : greenButtonClass}
size={32}
disabled={this.chatButtonDisabled}
onPress={this.handleChat}
icon="chat"
/>
</TouchableHighlight>
</View>
<View style={styles.buttonContainer}>
<TouchableHighlight style={styles.roundshape}>
<IconButton
style={this.callButtonDisabled ? disabledGreenButtonClass : greenButtonClass}
size={32}
disabled={this.callButtonDisabled}
onPress={this.handleAudioCall}
icon="phone"
/>
</TouchableHighlight>
</View>
<View style={styles.buttonContainer}>
<TouchableHighlight style={styles.roundshape}>
<IconButton
style={this.videoButtonDisabled ? disabledGreenButtonClass : greenButtonClass}
size={32}
disabled={this.videoButtonDisabled}
onPress={this.handleVideoCall}
icon="video"
/>
</TouchableHighlight>
</View>
<View style={styles.buttonContainer}>
<TouchableHighlight style={styles.roundshape}>
<IconButton
style={!this.state.shareToContacts ? disabledBlueButtonClass : blueButtonClass}
disabled={!this.state.shareToContacts}
size={32}
onPress={this.shareContent}
icon="share"
/>
</TouchableHighlight>
</View>
<View style={styles.buttonContainer}>
<TouchableHighlight style={styles.roundshape}>
<IconButton
style={this.conferenceButtonDisabled ? disabledBlueButtonClass : blueButtonClass}
disabled={this.conferenceButtonDisabled}
size={32}
onPress={this.showConferenceModal}
icon="account-group"
/>
</TouchableHighlight>
</View>
{ this.showQRCodeButton ?
<View style={styles.buttonContainer}>
<TouchableHighlight style={styles.roundshape}>
<IconButton
onPress={this.toggleQRCodeScanner}
style={styles.qrCodeButton}
disabled={!this.showQRCodeButton}
size={32}
icon="qrcode"
/>
</TouchableHighlight>
</View>
: null}
</View>
}
</View>
: null}
</View>
<View style={[historyContainer, borderClass]}>
{ this.state.showQRCodeScanner && !showBackToCallButton ?
<QRCodeScanner
onRead={this.QRCodeRead}
showMarker={true}
flashMode={RNCamera.Constants.FlashMode.off}
containerStyle={styles.QRcodeContainer}
/>
:
<ContactsListBox
contacts={this.state.contacts}
targetUri={this.state.targetUri}
fontScale = {this.state.fontScale}
orientation={this.props.orientation}
setTargetUri={this.handleTargetChange}
selectedContact={this.state.selectedContact}
isTablet={this.state.isTablet}
chat={this.state.chat && !this.state.inviteContacts}
isLandscape={this.state.isLandscape}
account={this.props.account}
password={this.props.password}
config={this.props.config}
refreshHistory={this.props.refreshHistory}
refreshFavorites={this.props.refreshFavorites}
localHistory={this.props.localHistory}
saveHistory={this.props.saveHistory}
myDisplayName={this.state.myDisplayName}
myPhoneNumber={this.props.myPhoneNumber}
saveConference={this.props.saveConference}
myInvitedParties = {this.state.myInvitedParties}
favoriteUris={this.props.favoriteUris}
blockedUris={this.props.blockedUris}
filter={this.state.historyCategoryFilter}
periodFilter={this.state.historyPeriodFilter}
defaultDomain={this.props.defaultDomain}
saveContact={this.props.saveContact}
myContacts = {this.state.myContacts}
messages = {this.state.messages}
sendMessage = {this.props.sendMessage}
reSendMessage = {this.props.reSendMessage}
deleteMessages = {this.props.deleteMessages}
expireMessage = {this.props.expireMessage}
deleteMessage = {this.props.deleteMessage}
getMessages = {this.props.getMessages}
pinMessage = {this.props.pinMessage}
unpinMessage = {this.props.unpinMessage}
confirmRead = {this.props.confirmRead}
sendPublicKey = {this.props.sendPublicKey}
inviteContacts = {this.state.inviteContacts}
shareToContacts = {this.state.shareToContacts}
selectedContacts = {this.state.selectedContacts}
toggleFavorite={this.props.toggleFavorite}
toggleBlocked={this.props.toggleBlocked}
togglePinned = {this.props.togglePinned}
pinned = {this.state.pinned}
loadEarlierMessages = {this.props.loadEarlierMessages}
newContactFunc = {this.props.newContactFunc}
messageZoomFactor = {this.state.messageZoomFactor}
isTyping = {this.state.isTyping}
call = {this.state.call}
ssiCredentials = {this.state.ssiCredentials}
ssiConnections = {this.state.ssiConnections}
keys = {this.state.keys}
downloadFunc = {this.props.downloadFunc}
decryptFunc = {this.props.decryptFunc}
messagesCategoryFilter = {this.state.messagesCategoryFilter}
isTexting = {this.state.isTexting}
forwardMessageFunc = {this.props.forwardMessageFunc}
requestCameraPermission = {this.props.requestCameraPermission}
+ startCall = {this.props.startCall}
/>
}
</View>
{this.showNavigationBar ?
<View style={styles.navigationContainer}>
<FlatList contentContainerStyle={styles.navigationButtonGroup}
horizontal={true}
ref={(ref) => { this.navigationRef = ref; }}
onScrollToIndexFailed={info => {
const wait = new Promise(resolve => setTimeout(resolve, 10));
wait.then(() => {
if (!this.state.selectedContact) {
this.navigationRef.current?.scrollToIndex({ index: info.index, animated: true/false });
}
});
}}
data={this.navigationItems}
extraData={this.state}
keyExtractor={(item, index) => item.key}
renderItem={this.renderNavigationItem}
/>
</View>
: null}
{this.state.isTablet && 0?
<View style={styles.footer}>
<FooterBox />
</View>
: null}
</View>
<ConferenceModal
show={this.state.showConferenceModal}
targetUri={uri}
myInvitedParties={this.state.myInvitedParties}
selectedContact={this.state.selectedContact}
handleConferenceCall={this.handleConferenceCall}
defaultDomain={this.props.defaultDomain}
accountId={this.props.account ? this.props.account.id: null}
lookupContacts={this.props.lookupContacts}
/>
</Fragment>
);
}
}
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,
keys : PropTypes.object,
downloadFunc : PropTypes.func,
decryptFunc : PropTypes.func,
isTexting :PropTypes.bool,
keyboardVisible: PropTypes.bool,
filteredMessageIds: PropTypes.array,
contentTypes: PropTypes.object,
canSend: PropTypes.func,
forwardMessageFunc: PropTypes.func,
sourceContact: PropTypes.object,
requestCameraPermission: PropTypes.func
};
export default ReadyBox;
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Feb 1, 6:50 PM (1 d, 18 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3489451
Default Alt Text
(160 KB)
Attached To
Mode
rSYLKWRTCM Sylk WebRTC mobile
Attached
Detach File
Event Timeline
Log In to Comment