diff --git a/js/background.js b/js/background.js index 859909b61..d8aba5f6e 100644 --- a/js/background.js +++ b/js/background.js @@ -1056,8 +1056,11 @@ }; window.toggleMediaPermissions = () => { - const mediaPermissions = window.getMediaPermissions(); - window.setMediaPermissions(!mediaPermissions); + // eslint-disable-next-line more/no-then + window.getMediaPermissions().then(value => { + window.setMediaPermissions(!value); + }); + }; // attempts a connection to an open group server diff --git a/package.json b/package.json index 2db553450..78019ed96 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "@sindresorhus/is": "0.8.0", "@types/dompurify": "^2.0.0", "@types/rc-slider": "^8.6.5", + "@types/react-mic": "^12.4.1", "backbone": "1.3.3", "blob-util": "1.3.0", "blueimp-canvas-to-blob": "3.14.0", @@ -114,6 +115,7 @@ "react-autosize-textarea": "^7.0.0", "react-contextmenu": "2.11.0", "react-dom": "16.8.3", + "react-mic": "^12.4.1", "react-portal": "^4.2.0", "react-qr-svg": "^2.2.1", "react-redux": "6.0.1", diff --git a/preload.js b/preload.js index 40c512c44..6083dcc04 100644 --- a/preload.js +++ b/preload.js @@ -205,29 +205,6 @@ ipc.on('remove-dark-overlay', () => { } }); -window.getSettingValue = (settingID, comparisonValue = null) => { - // Comparison value allows you to pull boolean values from any type. - // Eg. window.getSettingValue('theme', 'light') - // returns 'false' when the value is 'dark'. - - if (settingID === 'media-permissions') { - let permissionValue; - // eslint-disable-next-line more/no-then - window.getMediaPermissions().then(value => { - permissionValue = value; - }); - - return permissionValue; - } - - const settingVal = window.storage.get(settingID); - return comparisonValue ? !!settingVal === comparisonValue : settingVal; -}; - -window.setSettingValue = (settingID, value) => { - window.storage.put(settingID, value); -}; - installGetter('device-name', 'getDeviceName'); installGetter('theme-setting', 'getThemeSetting'); @@ -260,6 +237,7 @@ installSetter('spell-check', 'setSpellCheck'); installGetter('media-permissions', 'getMediaPermissions'); installGetter('media-permissions', 'setMediaPermissions'); + window.getMediaPermissions = () => new Promise((resolve, reject) => { ipc.once('get-success-media-permissions', (_event, error, value) => { @@ -272,6 +250,30 @@ window.getMediaPermissions = () => ipc.send('get-media-permissions'); }); +window.setMediaPermissions = value => { + ipc.send('set-media-permissions', value); +}; + +window.getSettingValue = (settingID, comparisonValue = null) => { + // Comparison value allows you to pull boolean values from any type. + // Eg. window.getSettingValue('theme', 'light') + // returns 'false' when the value is 'dark'. + + if (settingID === 'media-permissions') { + // This must be done asynchronously. Call + // await window.getMediaPermissions(); + return null; + } + + const settingVal = window.storage.get(settingID); + return comparisonValue ? !!settingVal === comparisonValue : settingVal; +}; + + +window.setSettingValue = (settingID, value) => { + window.storage.put(settingID, value); +}; + installGetter('is-primary', 'isPrimary'); installGetter('sync-request', 'getSyncRequest'); installGetter('sync-time', 'getLastSyncTime'); diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index 95d9b150d..d11fbc47f 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -107,7 +107,7 @@ export interface Props { onClickAttachment?: (attachment: AttachmentType) => void; onClickLinkPreview?: (url: string) => void; onCopyText?: () => void; - onSelectMessage: () => void; + onSelectMessage: (messageId: string) => void; onSelectMessageUnchecked: () => void; onReply?: () => void; onRetrySend?: () => void; diff --git a/ts/components/session/LeftPaneMessageSection.tsx b/ts/components/session/LeftPaneMessageSection.tsx index c68c604bd..8fea380f9 100644 --- a/ts/components/session/LeftPaneMessageSection.tsx +++ b/ts/components/session/LeftPaneMessageSection.tsx @@ -209,7 +209,7 @@ export class LeftPaneMessageSection extends React.Component { ); } - public renderConversations() { + public renderConversations() {\ return (
{this.state.shouldRenderMessageOnboarding ? ( diff --git a/ts/components/session/conversation/SessionCompositionBox.tsx b/ts/components/session/conversation/SessionCompositionBox.tsx index a5ae64bcd..a1d12f2bc 100644 --- a/ts/components/session/conversation/SessionCompositionBox.tsx +++ b/ts/components/session/conversation/SessionCompositionBox.tsx @@ -16,6 +16,7 @@ interface Props { interface State { message: string; isRecording: boolean; + mediaSetting: boolean | null; showEmojiPanel: boolean; } @@ -28,19 +29,30 @@ export class SessionCompositionBox extends React.Component { this.state = { message: '', - isRecording: true, + isRecording: false, + mediaSetting: null, showEmojiPanel: false, }; this.textarea = React.createRef(); this.fileInput = React.createRef(); - this.onKeyDown = this.onKeyDown.bind(this); + this.toggleEmojiPanel = this.toggleEmojiPanel.bind(this); + this.renderRecordingView = this.renderRecordingView.bind(this); + this.renderCompositionView = this.renderCompositionView.bind(this); + + this.onKeyDown = this.onKeyDown.bind(this); + this.onStartRecording = this.onStartRecording.bind(this); + this.onStopRecording = this.onStopRecording.bind(this); + this.onSendMessage = this.onSendMessage.bind(this); this.onChooseAttachment = this.onChooseAttachment.bind(this); - this.toggleEmojiPanel = this.toggleEmojiPanel.bind(this); - this.onSendMessage = this.onSendMessage.bind(this); + } + + public async componentWillMount() { + const mediaSetting = await window.getMediaPermissions(); + this.setState({mediaSetting}); } public componentWillReceiveProps(){ @@ -48,66 +60,15 @@ export class SessionCompositionBox extends React.Component { } render() { - const { placeholder } = this.props; - const { showEmojiPanel } = this.state; + const { isRecording } = this.state; return (
- { this.state.isRecording ? ( - + { isRecording ? ( + <>{this.renderRecordingView()} ) : ( - <> - - - - - - -
- -
- - -
- -
- - {showEmojiPanel && } - + <>{this.renderCompositionView()} )} -
); } @@ -118,7 +79,72 @@ export class SessionCompositionBox extends React.Component { }); } + private renderRecordingView() { + return ( + + ); + } + + private renderCompositionView() { + const { placeholder } = this.props; + const { showEmojiPanel } = this.state; + + return ( + <> + + + + + { this.state.mediaSetting && ( + + )} + +
+ +
+ + +
+ +
+ {showEmojiPanel && } + + ); + } + private onChooseAttachment() { this.fileInput.current?.click(); } @@ -143,7 +169,6 @@ export class SessionCompositionBox extends React.Component { console.log(`[vince][msg] Message:`, messagePlaintext); console.log(`[vince][msg] Attachments:`, attachments); - if (false){ this.props.sendMessage(); } @@ -151,6 +176,9 @@ export class SessionCompositionBox extends React.Component { private onStartRecording(){ // Do stuff for component, then run callback to SessionConversation + this.setState({ + isRecording: true, + }); this.props.onStartedRecording(); } @@ -161,4 +189,5 @@ export class SessionCompositionBox extends React.Component { this.props.onStoppedRecording(); } + } diff --git a/ts/components/session/conversation/SessionConversation.tsx b/ts/components/session/conversation/SessionConversation.tsx index b338de989..09302c513 100644 --- a/ts/components/session/conversation/SessionConversation.tsx +++ b/ts/components/session/conversation/SessionConversation.tsx @@ -23,6 +23,7 @@ interface State { conversationKey: string; unreadCount: number; messages: Array; + selectedMessages: Array; isScrolledToBottom: boolean; doneInitialScroll: boolean; messageFetchTimestamp: number; @@ -45,6 +46,7 @@ export class SessionConversation extends React.Component { conversationKey, unreadCount, messages: [], + selectedMessages: [], isScrolledToBottom: !unreadCount, doneInitialScroll: false, messageFetchTimestamp: 0, @@ -99,7 +101,7 @@ export class SessionConversation extends React.Component { render() { console.log(`[vince][info] Props`, this.props); - const { messages, conversationKey, doneInitialScroll } = this.state; + const { messages, conversationKey, doneInitialScroll, isRecording } = this.state; const loading = !doneInitialScroll || messages.length === 0; const conversation = this.props.conversations.conversationLookup[conversationKey]; @@ -129,7 +131,9 @@ export class SessionConversation extends React.Component {
-
+ { isRecording && ( +
+ )} { !isRss && ( @@ -262,7 +266,7 @@ export class SessionConversation extends React.Component { onDownload = {messageProps?.onDownload} onReply = {messageProps?.onReply} onRetrySend = {messageProps?.onRetrySend} - onSelectMessage = {messageProps?.onSelectMessage} + onSelectMessage = {messageId => this.onSelectMessage(messageId)} onSelectMessageUnchecked = {messageProps?.onSelectMessageUnchecked} onShowDetail = {messageProps?.onShowDetail} onShowUserDetails = {messageProps?.onShowUserDetails} @@ -399,7 +403,7 @@ export class SessionConversation extends React.Component { const { messages, unreadCount } = this.state; const message = messages[(messages.length - 1) - unreadCount]; - this.scrollToMessage(message.id); + message.id && this.scrollToMessage(message.id); } public scrollToMessage(messageId: string) { @@ -535,6 +539,14 @@ export class SessionConversation extends React.Component { }; }; + public onSelectMessage(messageId: string) { + const selectedMessages = !this.state.selectedMessages.includes(messageId) + ? [...this.state.selectedMessages, messageId] : []; + + selectedMessages && this.setState({ selectedMessages }); + console.log(`[vince] SelectedMessages: `, selectedMessages); + } + public getGroupSettingsProps() { const {conversationKey} = this.state; const conversation = window.getConversationByKey[conversationKey]; diff --git a/ts/components/session/conversation/SessionRecording.tsx b/ts/components/session/conversation/SessionRecording.tsx index 4d1226eea..e55b190aa 100644 --- a/ts/components/session/conversation/SessionRecording.tsx +++ b/ts/components/session/conversation/SessionRecording.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import {ReactMic} from 'react-mic'; + import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon'; import { SessionButton, SessionButtonType, SessionButtonColor } from '../SessionButton'; @@ -25,7 +27,6 @@ export class SessionRecording extends React.Component { isPaused: false, actionHover: false, }; - this.handleHoverActions = this.handleHoverActions.bind(this); this.handleUnhoverActions = this.handleUnhoverActions.bind(this); @@ -39,6 +40,8 @@ export class SessionRecording extends React.Component { public componentWillReceiveProps(){ console.log(`[vince][mic] Here are my composition props: `, this.props); + + console.log(`[vince][mic] Permissions: `, navigator.getUserMedia({ audio: true }, () => null, error => alert(error))); } render() { @@ -78,6 +81,15 @@ export class SessionRecording extends React.Component { )} + {/* null} + onData= {(data: any) => console.log(`[vince][mic] Data:`, data)} + strokeColor={'#00F480'} + /> */} +
{ }); } - navigator.getUserMedia(); } private handleUnhoverActions() { diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 692a0efbd..6dd3e9e52 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -32,6 +32,7 @@ export interface SettingsViewProps { interface State { hasPassword: boolean | null; pwdLockError: string | null; + mediaSetting: boolean | null; shouldLockSettings: boolean | null; linkedPubKeys: Array; } @@ -41,6 +42,7 @@ interface LocalSettingType { description: string | undefined; comparisonValue: string | undefined; id: any; + value?: any; content: any | undefined; hidden: any; title: string; @@ -59,6 +61,7 @@ export class SettingsView extends React.Component { this.state = { hasPassword: null, pwdLockError: null, + mediaSetting: null, shouldLockSettings: true, linkedPubKeys: new Array(), }; @@ -74,6 +77,11 @@ export class SettingsView extends React.Component { window.addEventListener('keyup', this.onKeyUp); } + public async componentWillMount() { + const mediaSetting = await window.getMediaPermissions(); + this.setState({mediaSetting}); + } + public componentDidMount() { setTimeout(() => $('#password-lock-input').focus(), 100); @@ -90,7 +98,7 @@ export class SettingsView extends React.Component { } /* tslint:disable-next-line:max-func-body-length */ - public renderSettingInCategory(): JSX.Element { + public renderSettingInCategory() { const { category } = this.props; let settings: Array; @@ -115,9 +123,15 @@ export class SettingsView extends React.Component { const description = setting.description || ''; const comparisonValue = setting.comparisonValue || null; - const value = + + let value; + if (setting.id === 'media-permissions'){ + value = this.state.mediaSetting; + } else { + value = window.getSettingValue(setting.id, comparisonValue) || (setting.content && setting.content.defaultValue); + } const sliderFn = setting.type === SessionSettingType.Slider @@ -416,19 +430,6 @@ export class SettingsView extends React.Component { }, confirmationDialogParams: undefined, }, - { - id: 'media-permissions', - title: window.i18n('mediaPermissionsTitle'), - description: window.i18n('mediaPermissionsDescription'), - hidden: false, - type: SessionSettingType.Toggle, - category: SessionSettingCategory.Permissions, - setFn: window.toggleMediaPermissions, - content: undefined, - comparisonValue: undefined, - onClick: undefined, - confirmationDialogParams: undefined, - }, { id: 'message-ttl', title: window.i18n('messageTTL'), @@ -444,6 +445,19 @@ export class SettingsView extends React.Component { }, confirmationDialogParams: undefined, }, + { + id: 'media-permissions', + title: window.i18n('mediaPermissionsTitle'), + description: window.i18n('mediaPermissionsDescription'), + hidden: false, + type: SessionSettingType.Toggle, + category: SessionSettingCategory.Privacy, + setFn: window.toggleMediaPermissions, + content: undefined, + comparisonValue: undefined, + onClick: undefined, + confirmationDialogParams: undefined, + }, { id: 'read-receipt-setting', title: window.i18n('readReceiptSettingTitle'), diff --git a/ts/global.d.ts b/ts/global.d.ts index 1b0aec703..d9d1a117e 100644 --- a/ts/global.d.ts +++ b/ts/global.d.ts @@ -11,6 +11,7 @@ interface Window { clearLocalData: any; getAccountManager: any; + getMediaPermissions: any; getConversations: any; getConversationByKey: any; getMessagesByKey: any; diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts index ac3ee43b8..e74da0fb0 100644 --- a/ts/state/ducks/search.ts +++ b/ts/state/ducks/search.ts @@ -157,36 +157,6 @@ const getMessageProps = (messages: Array) => { }); }; -async function doGetMessages( - query: string, - options: SearchOptions -): Promise { - const { regionCode } = options; - - const [discussions, messages] = await Promise.all([ - queryConversationsAndContacts(query, options), - queryMessages(query), - ]); - const { conversations, contacts } = discussions; - const filteredMessages = messages.filter(message => message !== undefined); - - let messageSet = []; - if (filteredMessages && !filteredMessages.length) { - messageSet = filteredMessages.map(message => { - const model = getMessageModel(message); - return model.propsForMessage; - }); - } - - return { - query, - normalizedPhoneNumber: normalize(query, { regionCode }), - conversations, - contacts, - messages: messageSet, - }; -} - async function queryMessages(query: string) { try { const normalized = cleanSearchTerm(query); diff --git a/yarn.lock b/yarn.lock index 7b1872633..d57583ff2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -333,6 +333,13 @@ dependencies: "@types/react" "*" +"@types/react-mic@^12.4.1": + version "12.4.1" + resolved "https://registry.yarnpkg.com/@types/react-mic/-/react-mic-12.4.1.tgz#7c261988c3be918b108df642a14101873b42846b" + integrity sha512-vlrXGS80ZEKI5d+cwWACaBSEFInBiNPl34BlVS3D0B6rTr6g3RhBCoR6gHZfO8VWP27wx7uVDcyUabTs17f8fg== + dependencies: + "@types/react" "*" + "@types/react-portal@^4.0.2": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/react-portal/-/react-portal-4.0.2.tgz#57a7f4c8ad48097c5a2d0cbbd09187831b91afdf" @@ -8458,6 +8465,11 @@ react-error-overlay@^4.0.1: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.1.tgz#417addb0814a90f3a7082eacba7cee588d00da89" integrity sha512-xXUbDAZkU08aAkjtUvldqbvI04ogv+a1XdHxvYuHPYKIVk/42BIOD0zSKTHAWV4+gDy3yGm283z2072rA2gdtw== +react-ga@^2.2.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-2.7.0.tgz#24328f157f31e8cffbf4de74a3396536679d8d7c" + integrity sha512-AjC7UOZMvygrWTc2hKxTDvlMXEtbmA0IgJjmkhgmQQ3RkXrWR11xEagLGFGaNyaPnmg24oaIiaNPnEoftUhfXA== + react-group@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/react-group/-/react-group-1.0.6.tgz#8dd7c00c3b35d05ce164021458bb07d580e3001a" @@ -8487,6 +8499,14 @@ react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== +react-mic@^12.4.1: + version "12.4.1" + resolved "https://registry.yarnpkg.com/react-mic/-/react-mic-12.4.1.tgz#6476a321ccd3babc61ebb12319b0fc0db4ca30c3" + integrity sha512-l580F9Mv6NRslSQj+80azx2rr/5OjWISEsjXx2GXBXcLvnT9vGeSYHzFVwDhGBjo/cj25AzXDg+rSp4Bz/qS2w== + dependencies: + prop-types "^15.5.10" + react-ga "^2.2.0" + react-portal@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/react-portal/-/react-portal-4.2.0.tgz#5400831cdb0ae64dccb8128121cf076089ab1afd"