diff --git a/app/components/ContactsListBox.js b/app/components/ContactsListBox.js
index bcee0d5..59a318c 100644
--- a/app/components/ContactsListBox.js
+++ b/app/components/ContactsListBox.js
@@ -1,2298 +1,2298 @@
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 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();
// 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 (
)
} else if (props.currentMessage.video) {
return (
)
} else if (props.currentMessage.audio) {
return (
)
} else {
return (
)
}
}
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: 'Delete/send ' + assetType + ' of ' + utils.beautySize(msg.metadata.filesize)
+ placeholder: 'Send ' + assetType + ' of ' + utils.beautySize(msg.metadata.filesize)
});
}
renderMessageImage =(props) => {
/*
return(
this.onMessagePress(context, props.currentMessage)}>
);
*/
return (
)
}
renderCustomActions = props =>
(
)
customInputToolbar = props => {
return (
{this.renderComposer}}
containerStyle={styles.chatInsideRightActionsContainer}
/>
);
};
chatInputChanged(text) {
this.setState({texting: (text.length > 0)})
}
resetContact() {
this.stopRecordingTimer()
this.outgoingPendMessages = {};
this.setState({
recording: false,
texting: false,
audioRecording: null,
cameraAsset: null,
placeholder: this.default_placeholder,
audioSendFinished: false
});
}
renderComposer(props) {
return(
this.setState({ composerText: text })}
text={this.state.composerText}
multiline={true}
placeholderTextColor={'red'}
>
)
}
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 (
);
} else {
if (this.state.cameraAsset) {
return (
);
} else if (this.state.audioRecording) {
return (
);
} else {
if (this.state.playing || (this.state.selectedContact && this.state.selectedContact.tags.indexOf('test') > -1)) {
return ;
} else {
return (
{this.state.texting ?
null
:
}
{this.state.texting ?
null
:
}
);
}
}
}
};
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});
}
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 (
);
};
renderMessageAudio(props){
const { currentMessage } = props;
let playAudioButtonStyle = Platform.OS === 'ios' ? styles.playAudioButtoniOS : styles.playAudioButton;
if (currentMessage.metadata.playing === true) {
return (
this.stopPlaying(currentMessage)}
style={playAudioButtonStyle}
icon="pause"
/>
);
} else {
return (
this.startPlaying(currentMessage)}
style={playAudioButtonStyle}
icon="play"
/>
);
}
};
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 (
);
} else {
return (
);
}
};
renderTime = (props) => {
const { currentMessage } = props;
if (currentMessage.metadata && currentMessage.metadata.preview) {
return null;
}
if (currentMessage.video) {
return (
)
} else if (currentMessage.audio) {
return (
)
} else {
return (
)
}
}
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 (
{items.length === 1 ?
(this.renderItem(items[0]))
:
item.id}
key={this.props.orientation}
loadEarlier
/>
}
{this.showChat && !this.state.inviteContacts?
this.chatInputChanged(text)}
/>
: (items.length === 1) ?
{ 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}
/>
: 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,
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
};
export default ContactsListBox;
exports.renderBubble = renderBubble;
diff --git a/app/components/NavigationBar.js b/app/components/NavigationBar.js
index 861367d..5de0b00 100644
--- a/app/components/NavigationBar.js
+++ b/app/components/NavigationBar.js
@@ -1,650 +1,653 @@
import React, { Component } from 'react';
import { Linking, Image, Platform, View } from 'react-native';
import PropTypes from 'prop-types';
import autoBind from 'auto-bind';
import { Appbar, Menu, Divider, Text } from 'react-native-paper';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import config from '../config';
import styles from '../assets/styles/blink/_NavigationBar.scss';
const blinkLogo = require('../assets/images/blink-white-big.png');
import AboutModal from './AboutModal';
import CallMeMaybeModal from './CallMeMaybeModal';
import EditConferenceModal from './EditConferenceModal';
import AddContactModal from './AddContactModal';
import EditContactModal from './EditContactModal';
import GenerateKeysModal from './GenerateKeysModal';
import ExportPrivateKeyModal from './ExportPrivateKeyModal';
import DeleteHistoryModal from './DeleteHistoryModal';
import VersionNumber from 'react-native-version-number';
import ShareConferenceLinkModal from './ShareConferenceLinkModal';
class NavigationBar extends Component {
constructor(props) {
super(props);
autoBind(this);
let displayName = this.props.selectedContact ? this.props.selectedContact.name : this.props.displayName;
let organization = this.props.selectedContact ? this.props.selectedContact.organization : this.props.organization;
this.state = {
syncConversations: this.props.syncConversations,
inCall: this.props.inCall,
showCallMeMaybeModal: this.props.showCallMeMaybeModal,
contactsLoaded: this.props.contactsLoaded,
appStoreVersion: this.props.appStoreVersion,
showExportPrivateKeyModal: this.props.showExportPrivateKeyModal,
privateKeyPassword: null,
registrationState: this.props.registrationState,
connection: this.props.connection,
proximity: this.props.proximity,
selectedContact: this.props.selectedContact,
mute: false,
menuVisible: false,
accountId: this.props.accountId,
account: this.props.account,
displayName: displayName,
myDisplayName: this.props.myDisplayName,
email: this.props.email,
organization: organization,
publicKey: this.props.publicKey,
showPublicKey: false,
messages: this.props.messages,
userClosed: false,
blockedUris: this.props.blockedUris,
ssiRequired: this.props.ssiRequired,
filteredMessageIds: this.props.filteredMessageIds,
contentTypes: this.props.contentTypes
}
this.menuRef = React.createRef();
}
//getDerivedStateFromProps(nextProps, state) {
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.account !== null && nextProps.account.id !== this.state.accountId) {
this.setState({accountId: nextProps.accountId});
}
let displayName = nextProps.selectedContact ? nextProps.selectedContact.name : nextProps.displayName;
let organization = nextProps.selectedContact ? nextProps.selectedContact.organization : nextProps.organization;
this.setState({registrationState: nextProps.registrationState,
connection: nextProps.connection,
syncConversations: nextProps.syncConversations,
contactsLoaded: nextProps.contactsLoaded,
displayName: displayName,
myDisplayName: nextProps.myDisplayName,
appStoreVersion: nextProps.appStoreVersion,
showExportPrivateKeyModal: nextProps.showExportPrivateKeyModal,
email: nextProps.email,
organization: organization,
proximity: nextProps.proximity,
account: nextProps.account,
userClosed: true,
inCall: nextProps.inCall,
publicKey: nextProps.publicKey,
showDeleteHistoryModal: nextProps.showDeleteHistoryModal,
showGenerateKeysModal: nextProps.showGenerateKeysModal,
selectedContact: nextProps.selectedContact,
messages: nextProps.messages,
showCallMeMaybeModal: nextProps.showCallMeMaybeModal,
blockedUris: nextProps.blockedUris,
ssiRequired: nextProps.ssiRequired,
filteredMessageIds: nextProps.filteredMessageIds,
contentTypes: nextProps.contentTypes
});
if (nextProps.menuVisible) {
this.setState({menuVisible: nextProps.menuVisible});
console.log('Next menu visible', nextProps.menuVisible);
}
}
handleMenu(event) {
this.callUrl = `${config.publicUrl}/call/${this.state.accountId}`;
switch (event) {
case 'about':
this.toggleAboutModal();
break;
case 'callMeMaybe':
this.props.toggleCallMeMaybeModal();
break;
case 'shareConferenceLinkModal':
this.showConferenceLinkModal();
break;
case 'displayName':
this.toggleEditContactModal();
break;
case 'speakerphone':
this.props.toggleSpeakerPhone();
break;
case 'proximity':
this.props.toggleProximity();
break;
case 'ssi':
this.props.toggleSSIFunc();
break;
case 'logOut':
this.props.logout();
break;
case 'logs':
this.props.showLogs();
break;
case 'refetchMessages':
this.props.refetchMessages(this.state.selectedContact);
break;
case 'deleteSsiCredential':
this.props.deleteSsiCredential(this.state.selectedContact);
break;
case 'deleteSsiConnection':
this.props.deleteSsiConnection(this.state.selectedContact);
break;
case 'preview':
this.props.preview();
break;
case 'audio':
this.audioCall();
break;
case 'video':
this.videoCall();
break;
case 'resumeTransfers':
this.resumeTransfers();
break;
case 'conference':
this.conferenceCall();
break;
case 'addContact':
this.toggleAddContactModal();
break;
case 'editContact':
if (this.state.selectedContact && this.state.selectedContact.uri.indexOf('@videoconference') > -1) {
this.setState({showEditConferenceModal: true});
} else {
this.setState({showEditContactModal: true});
}
break;
case 'deleteMessages':
this.setState({showDeleteHistoryModal: true});
break;
case 'generatePrivateKey':
this.setState({showGenerateKeysModal: true});
break;
case 'toggleFavorite':
this.props.toggleFavorite(this.state.selectedContact.uri);
break;
case 'toggleBlocked':
this.props.toggleBlocked(this.state.selectedContact.uri);
break;
case 'sendPublicKey':
this.props.sendPublicKey(this.state.selectedContact.uri);
break;
case 'exportPrivateKey':
if (this.state.publicKey) {
this.showExportPrivateKeyModal();
} else {
this.props.showImportModal(true);
}
break;
case 'showPublicKey':
this.setState({showEditContactModal: !this.state.showEditContactModal, showPublicKey: true});
break;
case 'checkUpdate':
if (Platform.OS === 'android') {
Linking.openURL('https://play.google.com/store/apps/details?id=com.agprojects.sylk');
} else {
Linking.openURL('https://apps.apple.com/us/app/id1489960733');
}
break;
case 'settings':
Linking.openURL(config.serverSettingsUrl);
break;
default:
break;
}
this.setState({menuVisible: false});
}
saveContact(displayName, organization='', email='') {
if (!displayName) {
return;
}
if (this.state.selectedContact && this.state.selectedContact.uri !== this.state.accountId) {
this.props.saveContact(this.state.selectedContact.uri, displayName, organization);
} else {
this.setState({displayName: displayName});
this.props.saveContact(this.state.accountId, displayName, organization, email);
}
}
toggleMute() {
this.setState(prevState => ({mute: !prevState.mute}));
this.props.toggleMute();
}
toggleAboutModal() {
this.setState({showAboutModal: !this.state.showAboutModal});
}
showConferenceLinkModal() {
this.setState({showConferenceLinkModal: true});
}
hideConferenceLinkModal() {
this.setState({showConferenceLinkModal: false});
}
audioCall() {
let uri = this.state.selectedContact.uri;
this.props.startCall(uri, {audio: true, video: false});
}
videoCall() {
let uri = this.state.selectedContact.uri;
this.props.startCall(uri, {audio: true, video: true});
}
resumeTransfers() {
this.props.resumeTransfers();
}
conferenceCall() {
this.props.showConferenceModalFunc();
}
toggleAddContactModal() {
this.setState({showAddContactModal: !this.state.showAddContactModal});
}
closeDeleteHistoryModal() {
this.setState({showDeleteHistoryModal: false});
}
hideGenerateKeysModal() {
this.setState({showGenerateKeysModal: false});
}
showEditContactModal() {
this.setState({showEditContactModal: true,
showPublicKey: false});
}
hideEditContactModal() {
this.setState({showEditContactModal: false,
showPublicKey: false,
userClosed: true});
}
saveConference(room, participants, displayName=null) {
this.props.saveConference(room, participants, displayName);
this.setState({showEditConferenceModal: false});
}
toggleEditContactModal() {
if (this.state.showEditContactModal) {
this.hideEditContactModal();
} else {
this.showEditContactModal();
};
}
closeEditConferenceModal() {
this.setState({showEditConferenceModal: false});
}
showExportPrivateKeyModal() {
const password = Math.random().toString().substr(2, 6);
this.setState({privateKeyPassword: password});
this.props.showExportPrivateKeyModalFunc()
}
render() {
const muteIcon = this.state.mute ? 'bell-off' : 'bell';
if (this.state.menuVisible && !this.state.appStoreVersion) {
this.props.checkVersionFunc()
}
let subtitleStyle = this.props.isTablet ? styles.tabletSubtitle: styles.subtitle;
let titleStyle = this.props.isTablet ? styles.tabletTitle: styles.title;
let statusIcon = null;
let statusColor = 'green';
let tags = [];
statusIcon = 'check-circle';
if (!this.state.connection || this.state.connection.state !== 'ready') {
statusIcon = 'alert-circle';
statusColor = 'red';
} else if (this.state.registrationState !== 'registered') {
statusIcon = 'alert-circle';
statusColor = 'orange';
}
let callUrl = callUrl = config.publicUrl + "/call/" + this.state.accountId;
let subtitle = 'Signed in as ' + this.state.accountId;
let proximityTitle = this.state.proximity ? 'No proximity sensor' : 'Proximity sensor';
let proximityIcon = this.state.proximity ? 'ear-hearing-off' : 'ear-hearing';
let isConference = false;
let hasMessages = true; // allow user to select this after local messages were removed, to delete them remotely
if (this.state.selectedContact) {
if (Object.keys(this.state.messages).indexOf(this.state.selectedContact.uri) > -1 && this.state.messages[this.state.selectedContact.uri].length > 0) {
hasMessages = true;
}
tags = this.state.selectedContact.tags;
isConference = this.state.selectedContact.conference || tags.indexOf('conference') > -1;
}
let favoriteTitle = (this.state.selectedContact && tags && tags.indexOf('favorite') > -1) ? 'Unfavorite' : 'Favorite';
let favoriteIcon = (this.state.selectedContact && tags && tags.indexOf('favorite') > -1) ? 'flag-minus' : 'flag';
let extraMenu = false;
let importKeyLabel = this.state.publicKey ? "Export private key...": "Import private key...";
let showEditModal = false;
if (this.state.selectedContact) {
showEditModal = this.state.showEditContactModal && !this.state.syncConversations;
} else {
showEditModal = !this.state.syncConversations && this.state.contactsLoaded &&
(this.state.showEditContactModal || (!this.state.displayName && this.state.publicKey !== null && !this.state.userClosed))
|| false;
}
let hasUpdate = this.state.appStoreVersion && this.state.appStoreVersion.version > VersionNumber.appVersion;
let updateTitle = hasUpdate ? 'Update Sylk...' : 'Check for updates...';
let isAnonymous = this.state.selectedContact && (this.state.selectedContact.uri.indexOf('@guest.') > -1 || this.state.selectedContact.uri.indexOf('anonymous@') > -1);
let isCallableUri = !isConference && !this.state.inCall && !isAnonymous && tags.indexOf('ssi') === -1;
let blockedTitle = (this.state.selectedContact && tags && tags.indexOf('blocked') > -1) ? 'Unblock' : isAnonymous ? 'Block anonymous callers': 'Block';
if (isAnonymous && this.state.blockedUris.indexOf('anonymous@anonymous.invalid') > -1) {
blockedTitle = 'Allow anonymous callers';
}
let ssiTitle = this.state.ssiRequired ? 'Disable SSI' : 'Enable SSI';
+ let enableSsi = false;
return (
{this.state.selectedContact?
{this.props.goBackFunc()}} />
: }
{this.props.isTablet?
{subtitle}
: null}
{statusColor == 'green' ?
: null }
{ this.state.selectedContact ?
:
}
);
}
}
NavigationBar.propTypes = {
notificationCenter : PropTypes.func.isRequired,
logout : PropTypes.func.isRequired,
preview : PropTypes.func.isRequired,
toggleSpeakerPhone : PropTypes.func.isRequired,
toggleProximity : PropTypes.func.isRequired,
showLogs : PropTypes.func.isRequired,
inCall : PropTypes.bool,
contactsLoaded : PropTypes.bool,
proximity : PropTypes.bool,
displayName : PropTypes.string,
myDisplayName : PropTypes.string,
email : PropTypes.string,
organization : PropTypes.string,
account : PropTypes.object,
accountId : PropTypes.string,
connection : PropTypes.object,
toggleMute : PropTypes.func,
orientation : PropTypes.string,
isTablet : PropTypes.bool,
selectedContact : PropTypes.object,
goBackFunc : PropTypes.func,
replicateKey : PropTypes.func,
publicKeyHash : PropTypes.string,
publicKey : PropTypes.string,
deleteMessages : PropTypes.func,
toggleBlocked : PropTypes.func,
toggleFavorite : PropTypes.func,
saveConference : PropTypes.func,
defaultDomain : PropTypes.string,
favoriteUris : PropTypes.array,
startCall : PropTypes.func,
startConference : PropTypes.func,
saveContact : PropTypes.func,
addContact : PropTypes.func,
deleteContact : PropTypes.func,
removeContact : PropTypes.func,
deletePublicKey : PropTypes.func,
sendPublicKey : PropTypes.func,
messages : PropTypes.object,
showImportModal : PropTypes.func,
syncConversations : PropTypes.bool,
showCallMeMaybeModal: PropTypes.bool,
toggleCallMeMaybeModal : PropTypes.func,
showConferenceModalFunc : PropTypes.func,
appStoreVersion : PropTypes.object,
checkVersionFunc: PropTypes.func,
toggleSSIFunc: PropTypes.func,
ssiRequired: PropTypes.bool,
refetchMessages: PropTypes.func,
showExportPrivateKeyModal: PropTypes.bool,
showExportPrivateKeyModalFunc: PropTypes.func,
hideExportPrivateKeyModalFunc: PropTypes.func,
blockedUris: PropTypes.array,
myuuid: PropTypes.string,
deleteSsiCredential: PropTypes.func,
resumeTransfers: PropTypes.func,
generateKeysFunc: PropTypes.func,
deleteSsiConnection: PropTypes.func,
filteredMessageIds: PropTypes.array,
contentTypes: PropTypes.object,
canSend: PropTypes.func
};
export default NavigationBar;
diff --git a/app/utils.js b/app/utils.js
index b54fbab..d3590d2 100644
--- a/app/utils.js
+++ b/app/utils.js
@@ -1,760 +1,760 @@
import uuidv4 from 'uuid/v4';
import SillyNames from './SillyNames';
import MaterialColors from './MaterialColors';
import { Clipboard, Dimensions } from 'react-native';
import Contacts from 'react-native-contacts';
import xss from 'xss';
import {decode as atob, encode as btoa} from 'base-64';
const RNFS = require('react-native-fs');
const logfile = RNFS.DocumentDirectoryPath + '/logs.txt';
let HUGE_FILE_SIZE = 15 * 1000 * 1000;
let ENCRYPTABLE_FILE_SIZE = 10 * 1000 * 1000;
let polycrc = require('polycrc');
function log2file(text) {
// append to logfile
RNFS.appendFile(logfile, text + '\r\n', 'utf8')
.then((success) => {
console.log(text);
})
.catch((err) => {
console.log(err.message);
});
}
function isAnonymous(uri) {
if (uri.indexOf('@guest.') > -1 || uri.indexOf('@anonymous.') > -1) {
return true
}
if (uri.indexOf('@192.168.') > -1) {
return true;
}
if (uri.indexOf('@10.') > -1) {
return true;
}
return false;
}
function appendLeadingZeroes(n){
if (n <= 9) {
return "0" + n;
}
return n;
}
function timestampedLog() {
let current_datetime = new Date();
let formatted_date = current_datetime.getFullYear() + "-" + appendLeadingZeroes(current_datetime.getMonth() + 1) + "-" + appendLeadingZeroes(current_datetime.getDate()) + " " + appendLeadingZeroes(current_datetime.getHours()) + ":" + appendLeadingZeroes(current_datetime.getMinutes()) + ":" + appendLeadingZeroes(current_datetime.getSeconds());
let message = formatted_date;
for (var i = 0; i < arguments.length; i++) {
let txt = arguments[i] ? arguments[i].toString() : '';
message = message + ' ' + txt;
}
log2file(message);
//console.log(message);
}
function generateUniqueId() {
const uniqueId = uuidv4().replace(/-/g, '').slice(0, 16);
return uniqueId;
}
function sylk2GiftedChat(sylkMessage, decryptedBody=null, direction='incoming') {
direction = direction || sylkMessage.direction;
let encrypted = decryptedBody ? 2 : 0;
let system = false;
let image;
let video;
let audio;
let text;
let metadata = {};
let content = decryptedBody || sylkMessage.content;
let file_transfer;
if (content.indexOf('Welcome!') > -1) {
system = true;
}
if (sylkMessage.contentType === 'text/html') {
text = html2text(content);
} else if (sylkMessage.contentType === 'text/plain') {
text = content;
} else if (sylkMessage.contentType === 'application/sylk-file-transfer') {
try {
metadata = JSON.parse(content);
let file_name = metadata.filename;
let encrypted = file_name.endsWith('.asc');
let decrypted_file_name = encrypted ? file_name.slice(0, -4) : file_name;
text = beautyFileNameForBubble(metadata);
if (metadata.local_url && metadata.error != 'decryption failed') {
if (isImage(decrypted_file_name)) {
image = Platform.OS === "android" ? 'file://'+ metadata.local_url : metadata.local_url;
} else if (isAudio(decrypted_file_name)) {
audio = Platform.OS === "android" ? 'file://'+ metadata.local_url : metadata.local_url;
} else if (isVideo(decrypted_file_name, metadata)) {
video = Platform.OS === "android" ? 'file://'+ metadata.local_url : metadata.local_url;
}
}
} catch (e) {
console.log("Error decoding json in sylk message:", e);
}
} else if (sylkMessage.contentType.indexOf('image/') > -1) {
image = `data:${sylkMessage.contentType};base64,${btoa(content)}`
text = 'Image';
} else {
text = 'Unknown message received ' + sylkMessage.contentType;
}
let g_id = sylkMessage.id;
let msg = {
_id: g_id,
key: g_id,
text: text,
image: image,
video: video,
audio: audio,
metadata: metadata,
contentType: sylkMessage.contentType,
pinned: false,
createdAt: sylkMessage.timestamp,
received: direction === 'incoming',
direction: direction,
system: system,
user: direction === 'incoming' ? {_id: sylkMessage.sender.uri, name: sylkMessage.sender.toString()} : {}
}
return msg;
}
function sql2GiftedChat(item, content, filter={}) {
let msg;
let image;
let video;
let audio;
let metadata = {};
let category;
if ('category' in filter && filter['category']) {
category = filter['category'];
}
//console.log('--- sql2GiftedChat', item, filter);
let timestamp = new Date(item.unix_timestamp * 1000);
let text = content || item.content
let failed = (item.received === 0) ? true: false;
let received = item.received === 1 ? true : false;
let sent = item.sent === 1 ? true : false;
let pending = item.pending === 1 ? true : false;
let from_uri = item.sender ? item.sender : item.from_uri;
if (item.content_type === 'application/sylk-file-transfer') {
let sql_metadata = item.metadata || text;
try {
let metadata_obj = JSON.parse(sql_metadata);
Object.assign(metadata, metadata_obj);
} catch (e) {
console.log("Error decoding file transfer json from sql: ", e, item.content);
return;
}
}
let must_check_category = true;
if (category && category !== 'text' && !metadata.filename) {
return null;
}
if (metadata && metadata.filename) {
if (category === 'paused') {
if (!metadata.paused) {
return null;
}
must_check_category = false;
}
if (category === 'failed') {
if (!metadata.failed) {
return null;
}
must_check_category = false;
}
if (category === 'text') {
return null;
}
if (category === 'large') {
if (metadata.filesize && metadata.filesize < HUGE_FILE_SIZE) {
return null;
}
must_check_category = false;
}
let file_name = metadata.filename;
text = beautyFileNameForBubble(metadata);
if (metadata.local_url && !metadata.local_url.startsWith(RNFS.DocumentDirectoryPath)) {
metadata.local_url = null;
}
if (metadata.local_url) {
if (metadata.error != 'decryption_failed') {
if (isImage(file_name)) {
if (metadata.b64) {
image = `data:${metadata.filetype};base64,${metadata.b64}`;
} else {
image = Platform.OS === "android" ? 'file://'+ metadata.local_url : metadata.local_url;
}
if (must_check_category && category && category !== 'image') {
return null;
}
} else if (isAudio(file_name)) {
audio = Platform.OS === "android" ? 'file://'+ metadata.local_url : metadata.local_url;
if (must_check_category && category && category !== 'audio') {
return null;
}
} else if (isVideo(file_name, metadata)) {
video = Platform.OS === "android" ? 'file://'+ metadata.local_url : metadata.local_url;
if (must_check_category && category && category !== 'video') {
return null;
}
} else {
if (must_check_category && category) {
return null;
}
}
}
} else {
if (isImage(file_name) && must_check_category && category && category !== 'image') {
return null;
} else if (isAudio(file_name && must_check_category && category && category !== 'audio')) {
return null;
} else if (isVideo(file_name, metadata) && must_check_category && category && category !== 'video') {
return null;
} else {
if (must_check_category && category) {
return null;
}
}
}
if (metadata.error) {
failed = true;
text = text + ' - ' + metadata.error;
}
} else {
if (item.image) {
image = item.image;
}
}
msg = {
_id: item.msg_id,
key: item.msg_id,
direction: item.direction,
audio: audio,
image: image,
video: video,
metadata: metadata,
contentType: item.content_type,
text: text,
createdAt: timestamp,
sent: sent,
direction: item.direction,
received: received,
pending: pending,
system: item.system === 1 ? true : false,
failed: failed,
pinned: (item.pinned === 1) ? true: false,
user: item.direction == 'incoming' ? {_id: from_uri, name: from_uri} : {}
}
return msg;
}
function beautyFileNameForBubble(metadata, lastMessage=false) {
let text = metadata.filename;
let file_name = metadata.filename;
- //console.log('beautyFileNameForBubble', metadata.progress, metadata.error);
+ //console.log('beautyFileNameForBubble', metadata);
let prefix = (metadata.direction && metadata.direction === 'outgoing') ? '' : 'Press to download';
- if (metadata.progress !== null && metadata.progress !== 100) {
+ if ('progress' in metadata && metadata.progress !== null && metadata.progress !== 100) {
prefix = metadata.direction === 'outgoing' ? 'Uploading' : 'Downloading';
}
let encrypted = metadata.filename.endsWith('.asc');
let decrypted_file_name = encrypted ? file_name.slice(0, -4) : file_name;
if (metadata.preview) {
return metadata.duration? 'Movie preview' : 'Photo preview';
}
if (isImage(decrypted_file_name)) {
if (metadata.local_url || lastMessage) {
text = 'Photo';
} else {
text = prefix + ' photo';
}
} else if (isAudio(decrypted_file_name)) {
if (metadata.local_url || lastMessage) {
text = 'Audio message';
} else {
text = prefix + ' audio message';
if (metadata.filesize > 10000000) {
text = text + 'of ' + beautySize(metadata.filesize);
}
}
} else if (isVideo(decrypted_file_name, metadata)) {
if (metadata.local_url || lastMessage) {
text = 'Video';
} else {
text = prefix + ' video of ' + beautySize(metadata.filesize);
}
} else {
if (lastMessage) {
text = decrypted_file_name;
} else {
if (metadata.local_url) {
if (encrypted) {
if (metadata.local_url && !metadata.local_url.endsWith('.asc')) {
text = decrypted_file_name;
} else {
text = 'Decrypt ' + decrypted_file_name;
}
} else {
if (metadata.failed && metadata.direction === "outgoing") {
text = 'Upload ' + file_name;
} else {
text = file_name;
}
}
} else {
text = prefix + ' ' + file_name + ' of ' + beautySize(metadata.filesize);
}
}
}
//console.log(text);
return text;
// + '\n' + RNFS.DocumentDirectoryPath + '\n' + metadata.local_url;
}
function html2text(content) {
content = xss(content, {
whiteList: [], // empty, means filter out all tags
stripIgnoreTag: true, // filter out all HTML not in the whitelist
stripIgnoreTagBody: ["script", "style"] // the script tag is a special case, we need
// to filter out its content
});
return content.replace(/ /g, ' ');
}
function normalizeUri(uri, defaultDomain) {
let targetUri = uri;
let idx = targetUri.indexOf('@');
let username;
let domain;
if (idx !== -1) {
username = targetUri.substring(0, idx);
domain = targetUri.substring(idx + 1);
} else {
username = targetUri;
domain = defaultDomain;
}
username = username.replace(/[<>\s()\[\]\'\"\~\!\%\&\*\{\}\|\\]/g, '');
return `${username}@${domain}`;
}
function copyToClipboard(text) {
Clipboard.setString(text);
return true;
}
function findContact(uri) {
return new Promise((resolve, reject) => {
//console.log('findContact')
Contacts.checkPermission((err, permission) => {
if (err) {
//log the error
console.log(err);
return reject(err);
}
if (permission === 'authorized') {
//console.log('HELLO', uri);
Contacts.getContactsByEmailAddress(uri, (err, contacts) => {
if (err) {
console.log('error getting contacts by email')
return reject(err);
}
if (contacts) {
return resolve(contacts)
}
Contacts.getContactsMatchingString(uri, (err2, contacts2) => {
if (err2) {
console.log('error matching string')
return reject(err2);
}
console.log(contacts);
resolve(contacts2)
});
})
} else {
console.log('not authortised')
reject(new Error('Not Authorised'))
}
})
})
}
function generateSillyName() {
const adjective = SillyNames.randomAdjective();
const number = Math.floor(Math.random() * 10);
const noun1 = SillyNames.randomNoun();
const noun2 = SillyNames.randomNoun();
return adjective + noun1 + noun2 + number;
}
function generateMaterialColor(text) {
return MaterialColors.generateColor(text);
}
function generateVideoTrack(stream, width = 640, height = 480) {
// const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
// const analyser = audioCtx.createAnalyser();
// const source = audioCtx.createMediaStreamSource(stream);
// source.connect(analyser);
// analyser.fftSize = 256;
// const bufferLength = analyser.frequencyBinCount;
// const dataArray = new Uint8Array(bufferLength);
// const canvas = Object.assign(document.createElement('canvas'), {width, height});
// const ctx = canvas.getContext('2d');
// const img = new Image();
// const blinkLogo = new Image();
// img.addEventListener('load', () => {
// draw();
// });
// const draw = () => {
// if (stream.active) {
// const drawVisual = requestAnimationFrame(draw);
// }
// analyser.getByteFrequencyData(dataArray);
// ctx.fillStyle = 'rgb(35, 35, 35)';
// ctx.fillRect(0, 0, width, height);
// ctx.filter = 'grayscale(100%) brightness(90%)';
// ctx.drawImage(blinkLogo, (width / 2) - 150, (height / 2) - 150, 300, 300);
// ctx.filter = 'none';
// ctx.drawImage(img, (width / 2) - 45 , height / 3, 90, 90);
// const barWidth = (width / bufferLength) * 2.5;
// let barHeight;
// let x = 0;
// for(var i = 0; i < bufferLength; i++) {
// barHeight = dataArray[i] / 2;
// ctx.fillStyle = 'rgb(' + (barHeight + 100) + ', 50, 50)';
// ctx.fillRect(x, 2 * height / 3 - barHeight / 2, barWidth, barHeight);
// x += barWidth + 1;
// }
// };
// img.src = 'assets/images/video-camera-slash.png';
// blinkLogo.src = 'assets/images/blink-white-big.png';
// const canvasStream = canvas.captureStream();
return Object.assign(stream.getVideoTracks()[0], {enabled: true});
}
function getWindowHeight() {
return Dimensions.get('window').height;
}
function escapeHtml(text) {
var map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
}
function isPhoneNumber(uri) {
let username = uri;
if (uri.indexOf('@') > -1) {
username = uri.split('@')[0].trim();
}
return username.match(/^(\+|0)([\d|\-\(\)]+)$/);
}
function isEmailAddress(uri) {
uri = uri.trim().toLowerCase();
let email_reg = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,})+$/;
return email_reg.test(uri);
}
function isImage(filename) {
if (!filename || typeof filename !== 'string') {
return false;
}
filename = filename.endsWith('.asc') ? filename.slice(0, -4) : filename;
if (filename.toLowerCase().endsWith('.png')) {
return true
} else if (filename.toLowerCase().endsWith('.jpg')) {
return true
} else if (filename.toLowerCase().endsWith('.jpeg')) {
return true
} else if (filename.toLowerCase().endsWith('.gif')) {
return true
} else if (filename.toLowerCase().endsWith('.tiff')) {
return true
} else if (filename.toLowerCase().endsWith('.tif')) {
return true
}
return false;
}
function isAudio(filename) {
if (!filename || typeof filename !== 'string') {
return false;
}
filename = filename.endsWith('.asc') ? filename.slice(0, -4) : filename;
if (filename.toLowerCase().endsWith('.mp3')) {
return true;
}
if (filename.toLowerCase().endsWith('.opus')) {
return true
}
if (filename.toLowerCase().endsWith('.wav')) {
return true
}
if (filename.toLowerCase().startsWith('sylk-audio-recording')) {
return true
}
return false;
}
function isVideo(filename, metadata=null) {
if (!filename || typeof filename !== 'string') {
return false;
}
if (metadata) {
if (metadata.filetype && metadata.filetype.startsWith('video/')) {
return true;
}
if (metadata.duration) {
return true;
}
}
if (filename.toLowerCase().startsWith('sylk-audio-recording')) {
return false
}
if (filename.toLowerCase().endsWith('.mpeg')) {
return true;
} else if (filename.toLowerCase().endsWith('.mp4')) {
return true;
} else if (filename.toLowerCase().endsWith('.webm')) {
return true;
} else if (filename.toLowerCase().endsWith('.ogg')) {
return true;
} else if (filename.toLowerCase().endsWith('.mpg')) {
return true;
} else if (filename.toLowerCase().endsWith('.mov')) {
return true;
}
return false;
}
function titleCase(str) {
return str.replace(
/\w\S*/g,
function(txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
}
);
}
function beautySize(fsize) {
let size = fsize + + " B";
if (fsize > 1024 * 1024) {
size = Math.ceil(fsize/1024/1024) + " MB";
} else if (fsize < 1024 * 1024) {
size = Math.ceil(fsize/1024) + " KB";
}
return size;
}
/* OpenPGP radix-64/base64 string encoding/decoding
* Copyright 2005 Herbert Hanewinkel, www.haneWIN.de
* version 1.0, check www.haneWIN.de for the latest version
*
* This software is provided as-is, without express or implied warranty.
* Permission to use, copy, modify, distribute or sell this software, with or
* without fee, for any purpose and by any individual or organization, is hereby
* granted, provided that the above copyright notice and this paragraph appear
* in all copies. Distribution as a part of an application or binary must
* include the above copyright notice in the documentation and/or other materials
* provided with the application or distribution.
*/
var b64s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
function radix64(t) {
var a, c, n;
var r = '', l = 0, s = 0;
var tl = t.length;
for (n = 0; n < tl; n++) {
c = t.charCodeAt(n);
if (s == 0) {
r += b64s.charAt((c >> 2) & 63);
a = (c & 3) << 4;
} else if (s == 1) {
r += b64s.charAt((a | (c >> 4) & 15));
a = (c & 15) << 2;
} else if (s == 2) {
r += b64s.charAt(a | ((c >> 6) & 3));
l += 1;
if ((l % 60) == 0)
r += "\n";
r += b64s.charAt(c & 63);
}
l += 1;
if ((l % 60) == 0)
r += "\n";
s += 1;
if (s == 3)
s = 0;
}
if (s > 0) {
r += b64s.charAt(a);
l += 1;
if ((l % 60) == 0)
r += "\n";
r += '=';
l += 1;
}
if (s == 1) {
if ((l % 60) == 0)
r += "\n";
r += '=';
}
return r;
}
function isFileEncryptable(file_transfer) {
if (file_transfer.filesize > ENCRYPTABLE_FILE_SIZE) {
return false;
}
if (isVideo(file_transfer)) {
return false;
}
return true;
}
function base64ToArrayBuffer(base64) {
var binaryString = atob(base64);
var bytes = new Uint8Array(binaryString.length);
for (var i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
/**
* Calculates a checksum over the given data and returns it base64 encoded
* @param data [String] data to create a CRC-24 checksum for
* @return [String] base64 encoded checksum
* http://www.faqs.org/rfcs/rfc4880.html
*/
function getPGPCheckSum(base64_content) {
let buffer = base64ToArrayBuffer(base64_content);
let crc24 = polycrc.crc24;
let checksum = crc24(buffer);
var str = "" + String.fromCharCode(checksum >> 16)+
String.fromCharCode((checksum >> 8) & 0xFF)+
String.fromCharCode(checksum & 0xFF);
return radix64(str);
}
exports.copyToClipboard = copyToClipboard;
exports.normalizeUri = normalizeUri;
exports.generateSillyName = generateSillyName;
exports.timestampedLog = timestampedLog;
exports.appendLeadingZeroes = appendLeadingZeroes;
exports.generateUniqueId = generateUniqueId;
exports.generateMaterialColor = generateMaterialColor;
exports.generateVideoTrack = generateVideoTrack;
exports.getWindowHeight = getWindowHeight;
exports.findContact = findContact;
exports.sylk2GiftedChat = sylk2GiftedChat;
exports.sql2GiftedChat = sql2GiftedChat;
exports.isAnonymous = isAnonymous;
exports.html2text = html2text;
exports.isEmailAddress = isEmailAddress;
exports.isPhoneNumber = isPhoneNumber;
exports.isImage = isImage;
exports.isAudio = isAudio;
exports.isVideo = isVideo;
exports.titleCase = titleCase;
exports.beautyFileNameForBubble = beautyFileNameForBubble;
exports.beautySize = beautySize;
exports.HUGE_FILE_SIZE = HUGE_FILE_SIZE;
exports.getPGPCheckSum = getPGPCheckSum;
exports.isFileEncryptable = isFileEncryptable;