diff --git a/app/assets/styles/blink/_ContactCard.scss b/app/assets/styles/blink/_ContactCard.scss
new file mode 100644
index 0000000..2184e95
--- /dev/null
+++ b/app/assets/styles/blink/_ContactCard.scss
@@ -0,0 +1,100 @@
+.containerPortrait {
+}
+
+.containerLandscape {
+}
+
+.cardPortraitContainer {
+ margin-top: 1px;
+}
+
+.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 {
+ margin-top: 5px;
+ flex: 1;
+ flex-direction: row;
+ justify-content: space-between;
+}
+
+.cardContent {
+ flex: 1;
+ flex-direction: row;
+}
+
+.title {
+ font-size: 18px;
+ margin: 0px;
+}
+
+.subtitle {
+ font-size:14px;
+ margin: 0px;
+}
+
+.description {
+ font-size:12px;
+ margin: 0px;
+}
+
+.avatarContent {
+ margin-top: 7px;
+}
+
+.mainContent {
+ margin-left: 10px;
+}
+
+.rightContent {
+ margin-top: 15px;
+ margin-right: 20px;
+ align-items: flex-end;
+ border: 0px;
+}
+
+.badgeTextStyle {
+ font-size: 12px;
+}
+
+.badgeContainer {
+ position: absolute;
+ bottom: 10;
+ right: 0;
+ size: 30;
+}
+
+.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;
+}
diff --git a/app/assets/styles/blink/_ContactsListBox.scss b/app/assets/styles/blink/_ContactsListBox.scss
new file mode 100644
index 0000000..4920df0
--- /dev/null
+++ b/app/assets/styles/blink/_ContactsListBox.scss
@@ -0,0 +1,37 @@
+.portraitContainer {
+ flex: 1;
+ flex-direction: column;
+}
+
+.contactsPortraitContainer {
+ flex: 1;
+ flex-direction: column;
+}
+
+.chatPortraitContainer {
+ flex: 6;
+ margin-top: 15px;
+}
+
+.landscapeContainer {
+ flex: 1;
+ flex-direction: row;
+}
+
+.contactsLandscapeContainer {
+ flex: 1;
+ flex-direction: row;
+}
+
+.chatLandscapeContainer {
+ flex: 1;
+}
+
+.chatBorder {
+ border-width: 0px;
+ margin: 1px;
+ border-radius: 5px;
+ border-color: gray;
+}
+
+
diff --git a/app/components/ContactCard.js b/app/components/ContactCard.js
new file mode 100644
index 0000000..5551b34
--- /dev/null
+++ b/app/components/ContactCard.js
@@ -0,0 +1,425 @@
+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 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 }) => (
+
+ {name !== uri?
+ {name} ({uri})
+ :
+ {uri}
+ }
+
+
+);
+
+const renderItem = ({ item }) => (
+ -
+);
+
+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
+ }
+
+
+ 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,
+ pinned: nextProps.pinned,
+ messages: nextProps.messages,
+ unread: nextProps.unread
+ });
+ }
+
+ 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;
+ }
+
+ render () {
+ let showActions = this.state.contact.showActions &&
+ this.state.contact.tags.indexOf('test') === -1 &&
+ this.state.contact.tags !== ["syntetic"];
+
+ //console.log('Render contact', this.state.contact);
+
+ 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.organization + ')';
+ }
+
+ let showBlockButton = !this.state.contact.conference && !this.state.chat;
+ let showBlockDomainButton = false;
+ let blockTextbutton = 'Block';
+ let blockDomainTextbutton = 'Block domain';
+
+ let participantsData = [];
+
+ if (this.state.favorite) {
+ if (!this.state.blocked) {
+ showBlockButton = false;
+ }
+ }
+
+ if (username.indexOf('3333@') > -1) {
+ showBlockButton = false;
+ }
+
+ if (username.indexOf('4444@') > -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';
+
+ if (this.state.contact.timestamp) {
+ description = moment(this.state.contact.timestamp).format('MMM D HH: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;
+
+ 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 = 85;
+
+ 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';
+ }
+
+ let dn;
+ if (participantsData.length > 4 || participantsData.length < 2) {
+ title = username.length > 10 ? 'Conference' : toTitleCase(username);
+ } else if (participantsData.length > 1 || participantsData.length <= 4 ) {
+ let j = 0;
+ if (username.length < 10) {
+ title = toTitleCase(username);
+ } else {
+ participantsData.forEach((participant) => {
+ if (participant.name === participant.uri) {
+ dn = toTitleCase(participant.uri.split('@')[0]);
+ } else {
+ dn = participant.name.split(' ')[0];
+ }
+ title = title + dn;
+ if (j < participantsData.length - 1) {
+ title = title + ' & ';
+ }
+ j = j + 1;
+ });
+ }
+ }
+ }
+
+ if (!name) {
+ title = uri;
+ }
+
+ if (duration === 'missed') {
+ subtitle = 'Last call missed';
+ } else if (duration === 'cancelled') {
+ subtitle = 'Last call cancelled';
+ } else {
+ if (duration) {
+ subtitle = 'Last call duration ' + duration ;
+ }
+ }
+
+ if (title.indexOf('@videoconference') > -1) {
+ title = username;
+ }
+
+ 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 + ')';
+ }
+
+ const container = this.state.isLandscape ? styles.containerLandscape : styles.containerPortrait;
+ const chatContainer = this.state.isLandscape ? styles.chatLandscapeContainer : styles.chatPortraitContainer;
+
+ if (showActions && participantsData.length > 0) {
+ cardHeight = cardHeight + 20 * participantsData.length + 10;
+ }
+
+ 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;
+ //description = description + ': ' + this.state.contact.lastMessage;
+ } else {
+ subtitle = subtitle + label;
+ }
+
+ return (
+
+ {this.setTargetUri(uri, this.state.contact)}}
+ >
+
+
+
+
+
+
+
+
+ {title}
+ {subtitle}
+
+
+ {this.state.contact.direction ?
+
+ : null}
+ {description}
+
+
+
+ {participantsData && participantsData.length && showActions ?
+
+
+
+ item.id}
+ key={item => item.id}
+ />
+
+
+ : null}
+
+
+
+ { this.state.contact.selected ?
+
+ : null
+ }
+ {this.state.contact.unread.length ?
+
+ : null
+ }
+
+
+
+ {showActions && false ?
+
+
+ {showBlockButton? : null}
+ {showBlockDomainButton? : null}
+
+
+ : null}
+
+
+
+ );
+ }
+}
+
+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
+};
+
+
+export default ContactCard;
diff --git a/app/components/ContactsListBox.js b/app/components/ContactsListBox.js
new file mode 100644
index 0000000..6804794
--- /dev/null
+++ b/app/components/ContactsListBox.js
@@ -0,0 +1,914 @@
+import React, { Component} from 'react';
+import autoBind from 'auto-bind';
+
+import PropTypes from 'prop-types';
+import { Clipboard, SafeAreaView, View, FlatList, Text } 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 } from 'react-native-gifted-chat'
+import MessageInfoModal from './MessageInfoModal';
+import ShareMessageModal from './ShareMessageModal';
+import CustomChatActions from './ChatActions';
+
+
+import moment from 'moment';
+import momenttz from 'moment-timezone';
+
+import styles from '../assets/styles/blink/_ContactsListBox.scss';
+
+
+class ContactsListBox extends Component {
+ constructor(props) {
+ super(props);
+ autoBind(this);
+
+ 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: [],
+ chat: this.props.chat,
+ pinned: false,
+ showMessageModal: false,
+ message: null,
+ showShareMessageModal: false,
+ inviteContacts: this.props.inviteContacts,
+ selectedContacts: this.props.selectedContacts,
+ pinned: this.props.pinned,
+ filter: this.props.filter
+ }
+
+ this.echoTest = this.props.newContactFunc('4444@sylk.link', 'Test microphone');
+ this.echoTest.tags.push('test');
+
+ this.videoTest = this.props.newContactFunc('3333@sylk.link', 'Test video');
+ this.videoTest.tags.push('test');
+
+ this.ended = false;
+ }
+
+ componentDidMount() {
+ this.ended = false;
+ }
+
+ componentWillUnmount() {
+ this.ended = true;
+ }
+
+ //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.selectedContact !== this.state.selectedContact) {
+ this.setState({selectedContact: nextProps.selectedContact});
+ if (nextProps.selectedContact) {
+ this.getMessages(nextProps.selectedContact);
+ }
+ };
+
+ if (nextProps.myContacts !== this.state.myContacts) {
+ this.setState({myContacts: nextProps.myContacts});
+ };
+
+ if (this.state.messages) {
+ let renderMessages = [];
+ if (this.state.selectedContact) {
+ let uri = this.state.selectedContact.uri;
+ let username = uri.split('@')[0];
+ if (this.state.selectedContact.uri.indexOf('@videoconference') > -1) {
+ uri = username;
+ }
+
+ if (nextProps.messages && nextProps.messages.hasOwnProperty(uri)) {
+ renderMessages = nextProps.messages[uri];
+ if (this.state.renderMessages.length !== renderMessages.length) {
+ this.props.confirmRead(uri);
+ }
+ }
+
+ this.setState({renderMessages: GiftedChat.append(renderMessages, [])});
+ }
+ }
+
+ this.setState({isLandscape: nextProps.isLandscape,
+ chat: nextProps.chat,
+ filter: nextProps.filter,
+ password: nextProps.password,
+ showMessageModal: nextProps.showMessageModal,
+ message: nextProps.message,
+ inviteContacts: nextProps.inviteContacts,
+ selectedContacts: nextProps.selectedContacts,
+ pinned: nextProps.pinned,
+ targetUri: nextProps.selectedContact ? nextProps.selectedContact.uri : nextProps.targetUri
+ });
+
+ }
+
+ renderCustomActions = props =>
+ (
+
+ )
+
+ onSendFromUser() {
+ console.log('On send from user...');
+ }
+
+ getMessages(contact) {
+ if (!contact) {
+ return;
+ }
+ let uri = contact.uri;
+
+ if (uri.indexOf('@videoconference') > -1) {
+ let username = uri.split('@')[0];
+ uri = username;
+ }
+
+ this.props.getMessages(uri);
+ }
+
+ 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(
+ );
+ }
+
+ 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});
+ }
+
+ loadEarlierMessages() {
+ this.props.loadEarlierMessages();
+ }
+
+ onSendWithFile(selectedFile) {
+ 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 fileData = {
+ name: selectedFile.name,
+ type: selectedFile.type,
+ size: selectedFile.size,
+ uri: selectedFile.uri
+ };
+
+ console.log('Sending file', fileData);
+ //this.props.sendMessage(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,
+ */
+ this.props.sendMessage(uri, message);
+ });
+ this.setState({renderMessages: GiftedChat.append(this.state.renderMessages, messages)});
+ }
+
+ searchedContact(uri) {
+ let contacts = [];
+ if (uri.indexOf('@') === -1) {
+ uri = uri + '@' + this.props.defaultDomain;
+ }
+
+ const item = this.props.newContactFunc(uri.toLowerCase());
+ item.tags.push('syntetic');
+ contacts.push(item);
+ return contacts;
+ }
+
+ getServerHistory() {
+ if (!this.state.accountId) {
+ return;
+ }
+ if (this.ended || !this.state.accountId || this.state.isRefreshing) {
+ return;
+ }
+
+ this.setState({isRefreshing: true});
+
+ let history = [];
+ let localTime;
+ let hasMissedCalls = false;
+
+ 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;
+ }
+
+ if (elem.remoteParty.split('@')[0] === '3333') {
+ elem.uri = this.videoTest.uri;
+ elem.label = this.videoTest.label;
+ }
+
+ if (elem.remoteParty.split('@')[0] === '4444') {
+ elem.uri = this.echoTest.uri;
+ elem.label = this.echoTest.label;
+ }
+
+ if (known.indexOf(elem.uri) > -1) {
+ return null;
+ }
+
+ known.push(elem.uri);
+
+ elem.uri = elem.remoteParty.toLowerCase();
+
+ let username = elem.uri.split('@')[0];
+ let isPhoneNumber = username.match(/^(\+|0)(\d+)$/);
+ let contact_obj;
+
+ if (elem.displayName) {
+ elem.name = elem.displayName;
+ } else {
+ elem.name = elem.uri;
+ }
+
+ if (this.state.contacts) {
+ if (isPhoneNumber) {
+ contact_obj = this.findObjectByKey(this.state.contacts, 'uri', username);
+ } else {
+ contact_obj = this.findObjectByKey(this.state.contacts, 'uri', elem.uri);
+ }
+
+ if (contact_obj) {
+ elem.name = contact_obj.name;
+ elem.photo = contact_obj.photo;
+ elem.label = contact_obj.label;
+ if (isPhoneNumber) {
+ elem.uri = username;
+ }
+ // TODO update icon here
+ } else {
+ elem.photo = null;
+ }
+ }
+
+ if (elem.uri.indexOf('@guest.') > -1) {
+ elem.uri = elem.name.toLowerCase().replace(/ /g, '') + '@' + elem.uri.split('@')[1];
+ }
+
+ if (elem.remoteParty.indexOf('@videoconference.') > -1) {
+ elem.name = elem.uri.split('@')[0];
+ elem.uri = elem.uri.split('@')[0] + '@' + this.props.config.defaultConferenceDomain;
+ 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');
+ hasMissedCalls = true;
+ }
+ 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});
+ }
+
+ matchContact(contact, filter='', tags=[]) {
+ if (tags.length > 0 && !tags.some(item => contact.tags.includes(item))) {
+ return false;
+ }
+
+ if (contact.uri.toLowerCase().startsWith(filter.toLowerCase())) {
+ return true;
+ }
+
+ if (contact.contacts && contact.contacts.toLowerCase().indexOf(filter.toLowerCase()) > -1) {
+ 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;
+ }
+
+ onLongMessagePress(context, currentMessage) {
+ if (currentMessage && currentMessage.text) {
+ let options = ['Copy']
+ options.push('Delete');
+
+ if (this.state.targetUri.indexOf('@videoconference') === -1) {
+ if (currentMessage.direction === 'outgoing') {
+ if (currentMessage.failed) {
+ options.push('Resend')
+ }
+ }
+ }
+
+ if (currentMessage.pinned) {
+ options.push('Unpin');
+ } else {
+ options.push('Pin');
+ }
+
+ options.push('Share');
+ options.push('Info');
+ options.push('Cancel');
+
+ const cancelButtonIndex = options.length - 1;
+ const infoButtonIndex = options.length - 2;
+ const shareButtonIndex = options.length - 3;
+ const pinButtonIndex = options.length - 4;
+
+ context.actionSheet().showActionSheetWithOptions({
+ options,
+ cancelButtonIndex,
+ }, (buttonIndex) => {
+ switch (buttonIndex) {
+ case 0:
+ Clipboard.setString(currentMessage.text);
+ break;
+ case 1:
+ this.props.deleteMessage(currentMessage._id, this.state.targetUri);
+ break;
+ case pinButtonIndex:
+ if (currentMessage.pinned) {
+ this.props.unpinMessage(currentMessage._id);
+ } else {
+ this.props.pinMessage(currentMessage._id);
+ }
+ break;
+ case infoButtonIndex:
+ this.setState({message: currentMessage,
+ showMessageModal: true});
+
+ break;
+ case shareButtonIndex:
+ this.setState({message: currentMessage,
+ showShareMessageModal: true
+ });
+ break;
+ case 2:
+ if (this.state.targetUri.indexOf('@videoconference') === -1) {
+ if (currentMessage.failed) {
+ this.props.reSendMessage(currentMessage, this.state.targetUri);
+ }
+ }
+
+ break;
+ default:
+ break;
+ }
+ });
+ }
+ };
+
+ shouldUpdateMessage(props, nextProps) {
+ return true;
+ }
+
+ toggleShareMessageModal() {
+ this.setState({showShareMessageModal: !this.state.showShareMessageModal});
+ }
+
+ renderMessageBubble (props) {
+ let rightColor = '#0084ff';
+ let leftColor = '#f0f0f0';
+
+ if (props.currentMessage.failed) {
+ rightColor = 'red';
+ } else {
+ if (props.currentMessage.pinned) {
+ rightColor = '#2ecc71';
+ leftColor = '#2ecc71';
+ }
+ }
+
+ return (
+
+ )
+ }
+
+ get showChat() {
+ if (this.props.selectedContact && this.props.selectedContact.tags && this.props.selectedContact.tags.indexOf('blocked') > -1) {
+ return false;
+ }
+
+ if (this.props.selectedContact || this.state.targetUri) {
+ return true;
+ }
+
+ return false;
+ }
+
+ render() {
+ //console.log('Render contacts with filter', this.state.filter);
+
+ let searchExtraItems = [];
+ let items = [];
+ let matchedContacts = [];
+ let messages = this.state.renderMessages;
+ let contacts = [];
+ Object.keys(this.state.myContacts).forEach((uri) => {
+ contacts.push(this.state.myContacts[uri]);
+ });
+
+ let chatInputClass;
+
+ if (this.state.selectedContact && this.state.selectedContact.uri.indexOf('@videoconference') > -1) {
+ chatInputClass = this.noChatInputToolbar;
+ } else if (!this.state.chat) {
+ chatInputClass = this.noChatInputToolbar;
+ }
+
+ if (this.state.inviteContacts) {
+ items = contacts.filter(contact => this.matchContact(contact, this.state.targetUri));
+ } else if (this.state.filter === 'favorite') {
+ items = contacts.filter(contact => this.matchContact(contact, this.state.targetUri, ['favorite']));
+ } else if (this.state.filter === 'blocked') {
+ items = contacts.filter(contact => this.matchContact(contact, this.state.targetUri, ['blocked']));
+ } else if (this.state.filter === 'missed') {
+ items = contacts.filter(contact => this.matchContact(contact, this.state.targetUri) && contact.tags.indexOf('missed') > -1);
+ } else {
+ items = contacts.filter(contact => this.matchContact(contact, this.state.targetUri));
+ searchExtraItems = searchExtraItems.concat(this.state.contacts);
+ searchExtraItems = searchExtraItems.concat(this.videoTest);
+ searchExtraItems = searchExtraItems.concat(this.echoTest);
+
+ if (this.state.targetUri && this.state.targetUri.length > 2 && !this.state.selectedContact) {
+ 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.length == 0) {
+ items = items.concat(this.searchedContact(this.state.targetUri));
+ }
+
+ const known = [];
+ items = items.filter((elem) => {
+ if (known.indexOf(elem.uri) <= -1) {
+ known.push(elem.uri);
+ return elem;
+ }
+ });
+
+ if (!this.state.targetUri && !this.state.filter && !this.state.inviteContacts) {
+ if (!this.findObjectByKey(items, 'uri', this.echoTest.uri)) {
+ items.push(this.echoTest);
+ }
+ if (!this.findObjectByKey(items, 'uri', this.videoTest.uri)) {
+ items.push(this.videoTest);
+ }
+ }
+
+ items.forEach((item) => {
+ item.showActions = false;
+
+ if (!item.tags) {
+ item.tags = [];
+ }
+
+ if (!Array.isArray(item.unread)) {
+ item.unread = [];
+ }
+
+ if (this.state.favoriteUris.indexOf(item.uri) > -1 && item.tags.indexOf('favorite') === -1) {
+ item.tags.push('favorite');
+ }
+
+ if (this.state.blockedUris.indexOf(item.uri) > -1 && item.tags.indexOf('blocked') === -1) {
+ item.tags.push('blocked');
+ }
+
+ let idx = item.tags.indexOf('blocked');
+ if (this.state.blockedUris.indexOf(item.uri) === -1 && idx > -1) {
+ item.tags.splice(idx, 1);
+ }
+
+ idx = item.tags.indexOf('favorite');
+
+ if (this.state.favoriteUris.indexOf(item.uri) === -1 && idx > -1) {
+ item.tags.splice(idx, 1);
+ }
+
+ if (item.uri === this.echoTest.uri) {
+ item.name = this.echoTest.name;
+ }
+
+ if (item.uri === this.videoTest.uri) {
+ item.name = this.videoTest.name;
+ }
+
+ 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();
+
+ items.forEach((item) => {
+ const fromDomain = '@' + item.uri.split('@')[1];
+ 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.timestamp, item.type, item.uri);
+
+ });
+
+ items = filteredItems;
+ items.sort((a, b) => (a.timestamp < b.timestamp) ? 1 : -1)
+
+ if (items.length === 1) {
+ //console.log(items[0]);
+ items[0].showActions = true;
+ }
+
+ let columns = 1;
+
+ if (this.props.isTablet) {
+ columns = this.props.orientation === 'landscape' ? 3 : 2;
+ } else {
+ columns = this.props.orientation === 'landscape' ? 2 : 1;
+ }
+
+ 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;
+
+ if (items.length === 1) {
+ items[0].unread = [];
+ if (items[0].tags.toString() === 'syntetic') {
+ messages = [];
+ }
+ }
+
+ 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;
+
+ return (
+
+ {items.length === 1 ?
+ (this.renderItem(items[0]))
+ :
+ item.id}
+ key={this.props.orientation}
+ loadEarlier
+ />
+ }
+
+ {this.showChat ?
+
+
+
+ : (items.length === 1) ?
+
+ { return null }}
+ renderBubble={this.renderBubble}
+ onSend={this.onSendMessage}
+ onLongPress={this.onLongMessagePress}
+ shouldUpdateMessage={this.shouldUpdateMessage}
+ onPress={this.onLongMessagePress}
+ scrollToBottom={true}
+ inverted={false}
+ timeTextStyle={{ left: { color: 'red' }, right: { color: 'yellow' } }}
+ infiniteScroll
+ loadEarlier={showLoadEarlier}
+ onLoadEarlier={this.loadEarlierMessages}
+ />
+
+ : null
+ }
+
+
+
+
+
+
+ );
+ }
+}
+
+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,
+ saveInvitedParties: PropTypes.func,
+ myInvitedParties: PropTypes.object,
+ setBlockedUri : PropTypes.func,
+ favoriteUris : PropTypes.array,
+ blockedUris : PropTypes.array,
+ filter : PropTypes.string,
+ defaultDomain : PropTypes.string,
+ saveContact : PropTypes.func,
+ myContacts : PropTypes.object,
+ messages : PropTypes.object,
+ getMessages : PropTypes.func,
+ confirmRead : 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,
+ selectedContacts: PropTypes.array,
+ toggleBlocked : PropTypes.func,
+ togglePinned : PropTypes.func,
+ loadEarlierMessages: PropTypes.func,
+ newContactFunc : PropTypes.func
+};
+
+
+export default ContactsListBox;