diff --git a/app/assets/styles/blink/_ContactCard.scss b/app/assets/styles/blink/_ContactCard.scss index 79f4e6f..4d6e03b 100644 --- a/app/assets/styles/blink/_ContactCard.scss +++ b/app/assets/styles/blink/_ContactCard.scss @@ -1,116 +1,116 @@ .containerPortrait { } .containerLandscape { } .cardPortraitContainer { margin-top: 0.5px; } .cardLandscapeContainer { flex: 1; margin-left: 1px; margin-top: 1px; border-radius: 2px; } .cardLandscapeTabletContainer { flex: 1; border: 1px; border-radius: 2px; } .cardPortraitTabletContainer { flex: 1; border: 1px; border-radius: 2px; } .rowContent { flex: 1; flex-direction: row; justify-content: space-between; } .cardContent { flex: 1; flex-direction: row; } .title { font-size: 18px; padding-bottom: 7px; flex: 1; } .subtitle { font-size:14px; margin-top: -5px; margin-bottom: 0px; flex: 1; } .description { font-size:12px; margin-bottom: 0px; flex: 1; } .avatarContent { margin-top: 5px; } .gravatar { - width: 60px; - height: 60px; + width: 50px; + height: 50px; border-width: 2px; border-color: white; border-radius: 50px; } .mainContent { margin-left: 10px; } .rightContent { margin-top: 10px; margin-left: 60px; margin-right: 10px; align-items: flex-end; border: 0px; } .badgeTextStyle { font-size: 12px; } .badgeContainer { position: absolute; bottom: 10; right: 0; size: 30; } .selectedContact { margin-top: 15px; } .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/components/ContactCard.js b/app/components/ContactCard.js index a607e5e..59bb62f 100644 --- a/app/components/ContactCard.js +++ b/app/components/ContactCard.js @@ -1,432 +1,432 @@ import React, { Component, Fragment} from 'react'; import { View, SafeAreaView, FlatList } from 'react-native'; import { Badge } from 'react-native-elements' import autoBind from 'auto-bind'; import PropTypes from 'prop-types'; import moment from 'moment'; import momentFormat from 'moment-duration-format'; import { Card, IconButton, Button, Caption, Title, Subheading, List, Text, Menu} from 'react-native-paper'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import uuid from 'react-native-uuid'; import styles from '../assets/styles/blink/_ContactCard.scss'; import UserIcon from './UserIcon'; import { GiftedChat } from 'react-native-gifted-chat' import {Gravatar, GravatarApi} from 'react-native-gravatar'; import 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, fontScale: this.props.fontScale, selectMode: this.props.selectMode } this.menuRef = React.createRef(); } UNSAFE_componentWillReceiveProps(nextProps) { this.setState({ id: nextProps.contact.id, contact: nextProps.contact, invitedParties: nextProps.invitedParties, isLandscape: nextProps.isLandscape, orientation: nextProps.orientation, favorite: (nextProps.contact.tags.indexOf('favorite') > -1)? true : false, blocked: (nextProps.contact.tags.indexOf('blocked') > -1)? true : false, chat: nextProps.chat, pinned: nextProps.pinned, messages: nextProps.messages, unread: nextProps.unread, fontScale: nextProps.fontScale, selectMode: nextProps.selectMode }); } shouldComponentUpdate(nextProps) { //https://medium.com/sanjagh/how-to-optimize-your-react-native-flatlist-946490c8c49b return true; } findObjectByKey(array, key, value) { for (var i = 0; i < array.length; i++) { if (array[i][key] === value) { return array[i]; } } return null; } toggleBlocked() { this.props.toggleBlocked(this.state.contact.uri); } setBlockedDomain() { let newBlockedState = this.props.toggleBlocked('@' + this.state.contact.uri.split('@')[1]); this.setState({blocked: newBlockedState}); } undo() { this.setState({confirmRemoveFavorite: false, confirmDeleteChat: false, action: null}); } onSendMessage(messages) { messages.forEach((message) => { // TODO send messages using account API }); this.setState({messages: GiftedChat.append(this.state.messages, messages)}); } setTargetUri(uri, contact) { this.props.setTargetUri(uri, this.state.contact); } renderChatComposer () { return null; } render () { let showActions = this.state.contact.showActions && this.state.contact.tags.indexOf('test') === -1 && this.state.contact.tags !== ["synthetic"]; let tags = this.state.contact ? this.state.contact.tags : []; let uri = this.state.contact.uri; let username = uri.split('@')[0]; let domain = uri.split('@')[1]; let isPhoneNumber = username.match(/^(\+|0)(\d+)$/); let name = this.state.contact.name; if (this.state.contact.organization) { name = name + ' - ' + this.state.contact.organization; } let showBlockButton = !this.state.contact.conference && !this.state.chat; let showBlockDomainButton = false; let blockTextbutton = 'Block'; let blockDomainTextbutton = 'Block domain'; let contact_ts = ''; let participantsData = []; if (this.state.favorite) { if (!this.state.blocked) { showBlockButton = false; } } if (tags.indexOf('test') > -1) { showBlockButton = false; } if (name === 'Myself') { showBlockButton = false; } if (this.state.blocked) { blockTextbutton = 'Unblock'; } let color = {}; let title = name || username; let subtitle = uri; let description = 'No calls or messages'; var todayStart = new Date(); todayStart.setHours(0,0,0,0); if (this.state.contact.timestamp) { description = moment(this.state.contact.timestamp).format('MMM D HH:mm'); if (this.state.contact.timestamp > todayStart) { contact_ts = moment(this.state.contact.timestamp).format('HH:mm'); } else { contact_ts = moment(this.state.contact.timestamp).format('DD-MM'); } } if (name === uri) { title = toTitleCase(username); } if (isPhoneNumber && isIp(domain)) { title = 'Tel ' + username; subtitle = 'From @' + domain; showBlockDomainButton = true; } if (utils.isAnonymous(uri)) { //uri = 'anonymous@anonymous.invalid'; if (uri.indexOf('@guest.') > -1) { subtitle = 'From the Web'; } else { name = 'Anonymous'; } showBlockDomainButton = true; if (!this.state.blocked) { showBlockButton = false; } blockDomainTextbutton = 'Block Web callers'; } if (!username || username.length === 0) { if (isIp(domain)) { title = 'IP domain'; } else if (domain.indexOf('guest.') > -1) { title = 'Calls from the Web'; } else { title = 'Domain'; } } let cardContainerClass = styles.portraitContainer; if (this.state.isTablet) { cardContainerClass = (this.state.orientation === 'landscape') ? styles.cardLandscapeTabletContainer : styles.cardPortraitTabletContainer; } else { cardContainerClass = (this.state.orientation === 'landscape') ? styles.cardLandscapeContainer : styles.cardPortraitContainer; } let cardHeight = this.state.fontScale <= 1 ? 75 : 70; let duration; if (this.state.contact.tags.indexOf('history') > -1) { duration = moment.duration(this.state.contact.lastCallDuration, 'seconds').format('HH:mm:ss', {trim: false}); if (this.state.contact.direction === 'incoming' && this.state.contact.lastCallDuration === 0) { duration = 'missed'; } else if (this.state.contact.direction === 'outgoing' && this.state.contact.lastCallDuration === 0) { duration = 'cancelled'; } } if (this.state.contact.conference) { let participants = this.state.contact.participants; if (this.state.invitedParties && this.state.invitedParties.length > 0 ) { participants = this.state.invitedParties; } if (participants && participants.length > 0) { const p_text = participants.length > 1 ? 'participants' : 'participant'; subtitle = 'With ' + participants.length + ' ' + p_text; let i = 1; let contact_obj; let dn; let _item; participants.forEach((participant) => { contact_obj = this.findObjectByKey(this.props.contacts, 'uri', participant); dn = contact_obj ? contact_obj.name : participant; _item = {nr: i, id: uuid.v4(), uri: participant, name: dn}; participantsData.push(_item); i = i + 1; }); } else { subtitle = 'With no participants'; } } if (!name) { title = uri; } if (duration === 'missed') { subtitle = 'Last call missed from ', + uri; } else if (duration === 'cancelled') { subtitle = 'Last call cancelled to ' + uri; } else { subtitle = uri; } if (subtitle !== uri) { subtitle = subtitle + ' ' + uri; } if (title.indexOf('@videoconference') > -1) { title = name || username; } if (uri === 'anonymous@anonymous.invalid') { title = 'Anonymous caller'; } else { let isAnonymous = uri.indexOf('@guest.') > -1; if (isAnonymous) { title = title + ' (Web call)'; } } if (duration && duration !== "00:00:00") { let media = 'Audio call'; if (this.state.contact.lastCallMedia.indexOf('video') > -1) { media = 'Video call'; } description = description + ' (' + media + ' ' + duration + ')'; } const container = this.state.isLandscape ? styles.containerLandscape : styles.containerPortrait; const chatContainer = this.state.isLandscape ? styles.chatLandscapeContainer : styles.chatPortraitContainer; let showSubtitle = (showActions || this.state.isTablet || !description); let label = this.state.contact.label ? (" (" + this.state.contact.label + ")" ) : ''; if (this.state.contact.lastMessage) { subtitle = this.state.contact.lastMessage.split("\n")[0]; //description = description + ': ' + this.state.contact.lastMessage; } else { subtitle = subtitle + label; } let unread = (this.state.contact && this.state.contact.unread && !this.state.selectMode) ? this.state.contact.unread.length : 0; let selectCircle = this.state.contact.selected ? 'check-circle' : 'circle-outline'; return ( {this.setTargetUri(uri, this.state.contact)}} > { this.state.contact.photo || ! this.state.contact.email ? : - + } {title} {subtitle} {this.state.fontScale <= 1 ? {this.state.contact.direction ? : null} {description} : null} {participantsData && participantsData.length && showActions && false? item.id} key={item => item.id} /> : null} { this.state.selectMode ? : {contact_ts} } {unread ? : 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, fontScale : PropTypes.number, selectMode : PropTypes.bool }; export default ContactCard; diff --git a/app/components/UserIcon.js b/app/components/UserIcon.js index f2b553d..d750a48 100644 --- a/app/components/UserIcon.js +++ b/app/components/UserIcon.js @@ -1,61 +1,61 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import utils from '../utils'; import { Text, View } from 'react-native' import { Avatar} from 'react-native-paper'; const UserIcon = (props) => { if (!props.identity) { return (null) } const name = props.identity.name || props.identity.uri; const photo = props.identity.photo; let initials = ''; if (name) { initials = name.split(' ', 2).map(x => x[0]).join(''); } const color = utils.generateMaterialColor(props.identity.uri)['300']; let avatarSize = props.large ? 130: 50; if (props.carousel === true) { - avatarSize = 60; + avatarSize = 50; } if (props.small) { avatarSize = 40; } if (photo) { return ( ); } if (props.identity.uri && props.identity.uri.search('anonymous') !== -1) { return ( ); } if (props.identity.uri && props.identity.uri.search('videoconference') !== -1) { return ( ); } return ( ); }; UserIcon.propTypes = { identity: PropTypes.object.isRequired, large: PropTypes.bool, carousel: PropTypes.bool }; export default UserIcon;