From f381102860f31a56fab13f22ab37622e68ae22aa Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Tue, 22 Jun 2021 17:04:57 +1000 Subject: [PATCH 01/13] WIP: autoplay consecutive messages. Setting state done. --- _locales/en/messages.json | 4 ++- ts/components/session/SessionInboxView.tsx | 2 ++ .../session/settings/SessionSettings.tsx | 28 +++++++++++++--- ts/state/ducks/userConfig.tsx | 33 +++++++++++++++++++ ts/state/reducer.ts | 3 ++ ts/state/selectors/userConfig.ts | 4 +++ 6 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 ts/state/ducks/userConfig.tsx create mode 100644 ts/state/selectors/userConfig.ts diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 251f6ffae..cd2ad0701 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -407,5 +407,7 @@ "playAtCustomSpeed": "Play at $multipler$x speed", "linkVisitWarningTitle": "Open this link in your browser?", "linkVisitWarningMessage": "Are you sure you want to open $url$ in your browser?", - "open": "Open" + "open": "Open", + "audioMessageAutoplayTitle": "Audio Message Autoplay", + "audioMessageAutoplayDescription": "Automatically play consecutively sent audio messages" } diff --git a/ts/components/session/SessionInboxView.tsx b/ts/components/session/SessionInboxView.tsx index 9eb184d83..fdc97520b 100644 --- a/ts/components/session/SessionInboxView.tsx +++ b/ts/components/session/SessionInboxView.tsx @@ -13,6 +13,7 @@ import { initialOnionPathState } from '../../state/ducks/onion'; import { initialSearchState } from '../../state/ducks/search'; import { initialSectionState } from '../../state/ducks/section'; import { initialThemeState } from '../../state/ducks/theme'; +import { initialUserConfigState } from '../../state/ducks/userConfig'; import { StateType } from '../../state/reducer'; import { makeLookup } from '../../util'; import { LeftPane } from '../LeftPane'; @@ -96,6 +97,7 @@ export class SessionInboxView extends React.Component { mentionsInput: initialMentionsState, onionPaths: initialOnionPathState, modals: initialModalState, + userConfig: initialUserConfigState, }; this.store = createStore(initialState); diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 2d3edaa94..ddff44e87 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -4,12 +4,11 @@ import { SettingsHeader } from './SessionSettingsHeader'; import { SessionSettingListItem } from './SessionSettingListItem'; import { SessionButton, SessionButtonColor, SessionButtonType } from '../SessionButton'; import { BlockedNumberController, PasswordUtil } from '../../../util'; -import { ToastUtils } from '../../../session/utils'; import { ConversationLookupType } from '../../../state/ducks/conversations'; import { StateType } from '../../../state/reducer'; import { ConversationController } from '../../../session/conversations'; import { getConversationLookup } from '../../../state/selectors/conversations'; -import { connect } from 'react-redux'; +import { connect, useSelector } from 'react-redux'; import { getPasswordHash } from '../../../../ts/data/data'; import { SpacerLG, SpacerXS } from '../../basic/Text'; import { shell } from 'electron'; @@ -17,6 +16,8 @@ import { PasswordAction, SessionPasswordModal } from '../SessionPasswordModal'; import { SessionConfirmDialogProps } from '../SessionConfirm'; import { mapDispatchToProps } from '../../../state/actions'; import { unblockConvoById } from '../../../interactions/conversationInteractions'; +import { getUserConfig } from '../../../state/selectors/userConfig'; +import { toggleAudioAutoplay, updateUserConfig } from '../../../state/ducks/userConfig'; export enum SessionSettingCategory { Appearance = 'appearance', @@ -271,10 +272,12 @@ class SettingsViewInner extends React.Component { }); } + /** + * If there's a custom afterClick function, execute it instead of automatically updating settings + * @param item setting item + * @param value new value to set + */ public updateSetting(item: any, value?: string) { - // If there's a custom afterClick function, - // execute it instead of automatically updating settings - if (item.setFn) { if (value) { item.setFn(value); @@ -357,6 +360,21 @@ class SettingsViewInner extends React.Component { okTheme: SessionButtonColor.Danger, }, }, + { + id: 'audio-message-autoplay-setting', + title: window.i18n('audioMessageAutoplayTitle'), + description: window.i18n('audioMessageAutoplayDescription'), + hidden: false, + type: SessionSettingType.Toggle, + category: SessionSettingCategory.Appearance, + setFn: () => { + window.inboxStore?.dispatch(toggleAudioAutoplay()); + }, + content: undefined, + comparisonValue: undefined, + onClick: undefined, + confirmationDialogParams: undefined, + }, { id: 'notification-setting', title: window.i18n('notificationSettingsDialog'), diff --git a/ts/state/ducks/userConfig.tsx b/ts/state/ducks/userConfig.tsx new file mode 100644 index 000000000..e414df806 --- /dev/null +++ b/ts/state/ducks/userConfig.tsx @@ -0,0 +1,33 @@ +/** + * This slice is intended for the user configurable settings for the client such as appearance, autoplaying of links etc. + * Anything setting under the cog wheel tab. + */ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +export interface UserConfigState { + audioAutoplay: boolean; +} + +export const initialUserConfigState = { + audioAutoplay: false +}; + +const userConfigSlice = createSlice({ + name: 'userConfig', + initialState: initialUserConfigState, + reducers: { + updateUserConfig(state: UserConfigState, action: PayloadAction) { + return { + ...state, + audioAutoplay: true + } + }, + toggleAudioAutoplay: (state) => { + state.audioAutoplay = !state.audioAutoplay + } + }, +}) + +const { actions, reducer } = userConfigSlice; +export const { updateUserConfig, toggleAudioAutoplay } = actions; +export const userConfigReducer = reducer; \ No newline at end of file diff --git a/ts/state/reducer.ts b/ts/state/reducer.ts index ef0947b09..75584ab30 100644 --- a/ts/state/reducer.ts +++ b/ts/state/reducer.ts @@ -12,6 +12,7 @@ import { } from './ducks/mentionsInput'; import { defaultOnionReducer as onionPaths, OnionState } from './ducks/onion'; import { modalReducer as modals, ModalState } from './ducks/modalDialog'; +import { userConfigReducer as userConfig, UserConfigState } from './ducks/userConfig'; export type StateType = { search: SearchStateType; @@ -23,6 +24,7 @@ export type StateType = { mentionsInput: MentionsInputState; onionPaths: OnionState; modals: ModalState; + userConfig: UserConfigState; }; export const reducers = { @@ -35,6 +37,7 @@ export const reducers = { mentionsInput, onionPaths, modals, + userConfig }; // Making this work would require that our reducer signature supported AnyAction, not diff --git a/ts/state/selectors/userConfig.ts b/ts/state/selectors/userConfig.ts new file mode 100644 index 000000000..af6a9eaa6 --- /dev/null +++ b/ts/state/selectors/userConfig.ts @@ -0,0 +1,4 @@ +import { StateType } from '../reducer'; +import { UserConfigState } from "../ducks/userConfig"; + +export const getUserConfig = (state: StateType): UserConfigState => state.userConfig; From d35f3f9e620a59c7c85585d2af0819864e82db29 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Thu, 24 Jun 2021 13:43:52 +1000 Subject: [PATCH 02/13] WIP: audio autoplay working with consecutive messages. Next step store state on shutdown and rename variables. --- ts/components/conversation/H5AudioPlayer.tsx | 19 ++++++++++++++++++ ts/components/conversation/Message.tsx | 4 ++++ .../conversation/SessionMessagesList.tsx | 20 +++++++++++++++++++ .../session/settings/SessionSettings.tsx | 3 +-- ts/models/messageType.ts | 5 +++++ ts/state/selectors/userConfig.ts | 7 +++++++ 6 files changed, 56 insertions(+), 2 deletions(-) diff --git a/ts/components/conversation/H5AudioPlayer.tsx b/ts/components/conversation/H5AudioPlayer.tsx index 04d4187f2..bd673b1c8 100644 --- a/ts/components/conversation/H5AudioPlayer.tsx +++ b/ts/components/conversation/H5AudioPlayer.tsx @@ -1,14 +1,20 @@ // Audio Player import React, { useEffect, useRef } from 'react'; import H5AudioPlayer from 'react-h5-audio-player'; +import { useSelector } from 'react-redux'; import { useTheme } from 'styled-components'; import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch'; +import { getAudioAutoplay } from '../../state/selectors/userConfig'; import { SessionIcon, SessionIconSize, SessionIconType } from '../session/icon'; export const AudioPlayerWithEncryptedFile = (props: { src: string; contentType: string; playbackSpeed: number; + changeTestList?: any; + shouldPlay?: boolean + index?: number + nextMessageToPlay?: number }) => { const theme = useTheme(); const { urlToLoad } = useEncryptedFileFetch(props.src, props.contentType); @@ -22,6 +28,18 @@ export const AudioPlayerWithEncryptedFile = (props: { } }, [playbackSpeed]); + useEffect(() => { + if (props.index == props.nextMessageToPlay) { + player.current?.audio.current?.play(); + } + }) + + const onEnded = () => { + if (window.inboxStore?.getState().userConfig.audioAutoplay === true) { + props.changeTestList(props.index); + } + } + return ( { playbackSpeed={this.state.playbackSpeed} src={firstAttachment.url} contentType={firstAttachment.contentType} + changeTestList={this.props.changeTestList} + shouldPlay={this.props.shouldPlay} + index={this.props.index} + nextMessageToPlay={this.props.nextMessageToPlay} /> ); diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx index c2e127093..bff340673 100644 --- a/ts/components/session/conversation/SessionMessagesList.tsx +++ b/ts/components/session/conversation/SessionMessagesList.tsx @@ -25,6 +25,7 @@ import { DataExtractionNotification } from '../../conversation/DataExtractionNot interface State { showScrollButton: boolean; animateQuotedMessageId?: string; + nextMessageToPlay: number | null; } interface Props { @@ -68,6 +69,7 @@ export class SessionMessagesList extends React.Component { this.state = { showScrollButton: false, + nextMessageToPlay: null }; autoBind(this); @@ -196,6 +198,7 @@ export class SessionMessagesList extends React.Component { const { conversation, ourPrimary, selectedMessages } = this.props; const multiSelectMode = Boolean(selectedMessages.length); let currentMessageIndex = 0; + let playableMessageIndex = 0; const displayUnreadBannerIndex = this.displayUnreadBannerIndex(messages); return ( @@ -225,6 +228,7 @@ export class SessionMessagesList extends React.Component { /> ); + currentMessageIndex = currentMessageIndex + 1; if (groupNotificationProps) { @@ -269,6 +273,22 @@ export class SessionMessagesList extends React.Component { return; } + const changeTestList = (index: any) => { + index--; + if (messages[index]) { + this.setState({ + nextMessageToPlay: index + }) + } + } + + if (messageProps) { + messageProps.nextMessageToPlay = this.state.nextMessageToPlay + messageProps.index = playableMessageIndex; + messageProps.changeTestList = changeTestList; + } + playableMessageIndex++; + if (messageProps.conversationType === ConversationTypeEnum.GROUP) { messageProps.weAreAdmin = conversation.groupAdmins?.includes(ourPrimary); } diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index ddff44e87..b8958e8f7 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -16,8 +16,7 @@ import { PasswordAction, SessionPasswordModal } from '../SessionPasswordModal'; import { SessionConfirmDialogProps } from '../SessionConfirm'; import { mapDispatchToProps } from '../../../state/actions'; import { unblockConvoById } from '../../../interactions/conversationInteractions'; -import { getUserConfig } from '../../../state/selectors/userConfig'; -import { toggleAudioAutoplay, updateUserConfig } from '../../../state/ducks/userConfig'; +import { toggleAudioAutoplay } from '../../../state/ducks/userConfig'; export enum SessionSettingCategory { Appearance = 'appearance', diff --git a/ts/models/messageType.ts b/ts/models/messageType.ts index 08a05ce57..7ee9c1c76 100644 --- a/ts/models/messageType.ts +++ b/ts/models/messageType.ts @@ -252,4 +252,9 @@ export interface MessageRegularProps { onShowDetail: () => void; markRead: (readAt: number) => Promise; theme: DefaultTheme; + + shouldPlay?: boolean; + index?: number; + nextMessageToPlay?: number; + changeTestList: (value: any) => any; } diff --git a/ts/state/selectors/userConfig.ts b/ts/state/selectors/userConfig.ts index af6a9eaa6..32c0c2975 100644 --- a/ts/state/selectors/userConfig.ts +++ b/ts/state/selectors/userConfig.ts @@ -1,4 +1,11 @@ import { StateType } from '../reducer'; import { UserConfigState } from "../ducks/userConfig"; +import { createSelector } from 'reselect'; export const getUserConfig = (state: StateType): UserConfigState => state.userConfig; + + +export const getAudioAutoplay = createSelector( + getUserConfig, + (state: UserConfigState): boolean => state.audioAutoplay +); \ No newline at end of file From 856ced056a7d8579dd051adeac43094fab0eec41 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Thu, 24 Jun 2021 14:00:07 +1000 Subject: [PATCH 03/13] Minor tidying. --- ts/components/conversation/H5AudioPlayer.tsx | 17 +++++++++++------ ts/components/conversation/Message.tsx | 5 ++--- .../conversation/SessionMessagesList.tsx | 10 +++++++--- ts/models/messageType.ts | 5 ++--- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/ts/components/conversation/H5AudioPlayer.tsx b/ts/components/conversation/H5AudioPlayer.tsx index bd673b1c8..cf74a5ac0 100644 --- a/ts/components/conversation/H5AudioPlayer.tsx +++ b/ts/components/conversation/H5AudioPlayer.tsx @@ -11,9 +11,8 @@ export const AudioPlayerWithEncryptedFile = (props: { src: string; contentType: string; playbackSpeed: number; - changeTestList?: any; - shouldPlay?: boolean - index?: number + playNextMessage?: (index: number) => void; + playableMessageIndex?: number nextMessageToPlay?: number }) => { const theme = useTheme(); @@ -29,14 +28,20 @@ export const AudioPlayerWithEncryptedFile = (props: { }, [playbackSpeed]); useEffect(() => { - if (props.index == props.nextMessageToPlay) { + if (props.playableMessageIndex == props.nextMessageToPlay) { player.current?.audio.current?.play(); } }) const onEnded = () => { - if (window.inboxStore?.getState().userConfig.audioAutoplay === true) { - props.changeTestList(props.index); + // if audio autoplay is enabled, call method to start playing + // the next playable message + if ( + window.inboxStore?.getState().userConfig.audioAutoplay === true && + props.playNextMessage && + props.playableMessageIndex + ) { + props.playNextMessage(props.playableMessageIndex); } } diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index 91d7657cb..5c0605254 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -202,9 +202,8 @@ class MessageInner extends React.PureComponent { playbackSpeed={this.state.playbackSpeed} src={firstAttachment.url} contentType={firstAttachment.contentType} - changeTestList={this.props.changeTestList} - shouldPlay={this.props.shouldPlay} - index={this.props.index} + playNextMessage={this.props.playNextMessage} + playableMessageIndex={this.props.playableMessageIndex} nextMessageToPlay={this.props.nextMessageToPlay} /> diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx index bff340673..aad2e1d3a 100644 --- a/ts/components/session/conversation/SessionMessagesList.tsx +++ b/ts/components/session/conversation/SessionMessagesList.tsx @@ -273,7 +273,11 @@ export class SessionMessagesList extends React.Component { return; } - const changeTestList = (index: any) => { + /** + * Sets the targeted index for the next + * @param index index of message that just completed + */ + const playNextMessage = (index: any) => { index--; if (messages[index]) { this.setState({ @@ -284,8 +288,8 @@ export class SessionMessagesList extends React.Component { if (messageProps) { messageProps.nextMessageToPlay = this.state.nextMessageToPlay - messageProps.index = playableMessageIndex; - messageProps.changeTestList = changeTestList; + messageProps.playableMessageIndex = playableMessageIndex; + messageProps.playNextMessage = playNextMessage; } playableMessageIndex++; diff --git a/ts/models/messageType.ts b/ts/models/messageType.ts index 7ee9c1c76..7990dab8d 100644 --- a/ts/models/messageType.ts +++ b/ts/models/messageType.ts @@ -253,8 +253,7 @@ export interface MessageRegularProps { markRead: (readAt: number) => Promise; theme: DefaultTheme; - shouldPlay?: boolean; - index?: number; + playableMessageIndex?: number; nextMessageToPlay?: number; - changeTestList: (value: any) => any; + playNextMessage?: (value: number) => any; } From 3bd72df258ebb1bd392cd140748400cf5037a4d2 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Thu, 24 Jun 2021 14:13:45 +1000 Subject: [PATCH 04/13] linting and formatting. --- ts/components/conversation/H5AudioPlayer.tsx | 12 ++++++------ .../conversation/SessionMessagesList.tsx | 17 ++++++++--------- ts/state/ducks/userConfig.tsx | 16 ++++++++-------- ts/state/reducer.ts | 2 +- ts/state/selectors/userConfig.ts | 9 ++++----- 5 files changed, 27 insertions(+), 29 deletions(-) diff --git a/ts/components/conversation/H5AudioPlayer.tsx b/ts/components/conversation/H5AudioPlayer.tsx index cf74a5ac0..d23169984 100644 --- a/ts/components/conversation/H5AudioPlayer.tsx +++ b/ts/components/conversation/H5AudioPlayer.tsx @@ -12,8 +12,8 @@ export const AudioPlayerWithEncryptedFile = (props: { contentType: string; playbackSpeed: number; playNextMessage?: (index: number) => void; - playableMessageIndex?: number - nextMessageToPlay?: number + playableMessageIndex?: number; + nextMessageToPlay?: number; }) => { const theme = useTheme(); const { urlToLoad } = useEncryptedFileFetch(props.src, props.contentType); @@ -28,13 +28,13 @@ export const AudioPlayerWithEncryptedFile = (props: { }, [playbackSpeed]); useEffect(() => { - if (props.playableMessageIndex == props.nextMessageToPlay) { + if (props.playableMessageIndex === props.nextMessageToPlay) { player.current?.audio.current?.play(); } - }) + }); const onEnded = () => { - // if audio autoplay is enabled, call method to start playing + // if audio autoplay is enabled, call method to start playing // the next playable message if ( window.inboxStore?.getState().userConfig.audioAutoplay === true && @@ -43,7 +43,7 @@ export const AudioPlayerWithEncryptedFile = (props: { ) { props.playNextMessage(props.playableMessageIndex); } - } + }; return ( { this.state = { showScrollButton: false, - nextMessageToPlay: null + nextMessageToPlay: null, }; autoBind(this); @@ -228,7 +228,6 @@ export class SessionMessagesList extends React.Component { /> ); - currentMessageIndex = currentMessageIndex + 1; if (groupNotificationProps) { @@ -274,20 +273,20 @@ export class SessionMessagesList extends React.Component { } /** - * Sets the targeted index for the next + * Sets the targeted index for the next * @param index index of message that just completed */ const playNextMessage = (index: any) => { - index--; - if (messages[index]) { + const nextIndex = index - 1; + if (messages[nextIndex]) { this.setState({ - nextMessageToPlay: index - }) + nextMessageToPlay: nextIndex, + }); } - } + }; if (messageProps) { - messageProps.nextMessageToPlay = this.state.nextMessageToPlay + messageProps.nextMessageToPlay = this.state.nextMessageToPlay; messageProps.playableMessageIndex = playableMessageIndex; messageProps.playNextMessage = playNextMessage; } diff --git a/ts/state/ducks/userConfig.tsx b/ts/state/ducks/userConfig.tsx index e414df806..dd7001c8c 100644 --- a/ts/state/ducks/userConfig.tsx +++ b/ts/state/ducks/userConfig.tsx @@ -9,7 +9,7 @@ export interface UserConfigState { } export const initialUserConfigState = { - audioAutoplay: false + audioAutoplay: false, }; const userConfigSlice = createSlice({ @@ -19,15 +19,15 @@ const userConfigSlice = createSlice({ updateUserConfig(state: UserConfigState, action: PayloadAction) { return { ...state, - audioAutoplay: true - } + audioAutoplay: true, + }; + }, + toggleAudioAutoplay: state => { + state.audioAutoplay = !state.audioAutoplay; }, - toggleAudioAutoplay: (state) => { - state.audioAutoplay = !state.audioAutoplay - } }, -}) +}); const { actions, reducer } = userConfigSlice; export const { updateUserConfig, toggleAudioAutoplay } = actions; -export const userConfigReducer = reducer; \ No newline at end of file +export const userConfigReducer = reducer; diff --git a/ts/state/reducer.ts b/ts/state/reducer.ts index 75584ab30..e757789d8 100644 --- a/ts/state/reducer.ts +++ b/ts/state/reducer.ts @@ -37,7 +37,7 @@ export const reducers = { mentionsInput, onionPaths, modals, - userConfig + userConfig, }; // Making this work would require that our reducer signature supported AnyAction, not diff --git a/ts/state/selectors/userConfig.ts b/ts/state/selectors/userConfig.ts index 32c0c2975..084f8d9ac 100644 --- a/ts/state/selectors/userConfig.ts +++ b/ts/state/selectors/userConfig.ts @@ -1,11 +1,10 @@ import { StateType } from '../reducer'; -import { UserConfigState } from "../ducks/userConfig"; +import { UserConfigState } from '../ducks/userConfig'; import { createSelector } from 'reselect'; export const getUserConfig = (state: StateType): UserConfigState => state.userConfig; - export const getAudioAutoplay = createSelector( - getUserConfig, - (state: UserConfigState): boolean => state.audioAutoplay -); \ No newline at end of file + getUserConfig, + (state: UserConfigState): boolean => state.audioAutoplay +); From da00ac8d444f14cc274eb2773fc4c7ba134b6eec Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Thu, 24 Jun 2021 15:31:20 +1000 Subject: [PATCH 05/13] Saving userConfig redux slice to local storage. --- package.json | 1 + ts/components/session/SessionInboxView.tsx | 18 +++++++++++++----- .../session/settings/SessionSettings.tsx | 4 +++- ts/state/createStore.ts | 14 +++++++++++++- yarn.lock | 5 +++++ 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 258ec57cd..72a9eb215 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "read-last-lines": "1.3.0", "redux": "4.0.1", "redux-logger": "3.0.6", + "redux-persist": "^6.0.0", "redux-promise-middleware": "6.1.0", "reselect": "4.0.0", "rimraf": "2.6.2", diff --git a/ts/components/session/SessionInboxView.tsx b/ts/components/session/SessionInboxView.tsx index fdc97520b..8e925767b 100644 --- a/ts/components/session/SessionInboxView.tsx +++ b/ts/components/session/SessionInboxView.tsx @@ -19,6 +19,9 @@ import { makeLookup } from '../../util'; import { LeftPane } from '../LeftPane'; import { SessionMainPanel } from '../SessionMainPanel'; +import { PersistGate } from 'redux-persist/integration/react'; +import { persistStore } from 'redux-persist'; + // Workaround: A react component's required properties are filtering up through connect() // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363 @@ -54,13 +57,18 @@ export class SessionInboxView extends React.Component { return <>; } + let persistor = persistStore(this.store); + return ( -
-
- {this.renderLeftPane()} -
- + + +
+
+ {this.renderLeftPane()} +
+ + ); } diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index f13d9087e..6daf1e68d 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -362,7 +362,9 @@ class SettingsViewInner extends React.Component { setFn: () => { window.inboxStore?.dispatch(toggleAudioAutoplay()); }, - content: undefined, + content: { + defaultValue: window.inboxStore?.getState().userConfig.audioAutoplay + }, comparisonValue: undefined, onClick: undefined, confirmationDialogParams: undefined, diff --git a/ts/state/createStore.ts b/ts/state/createStore.ts index 8f936c93a..9ce6b5e98 100644 --- a/ts/state/createStore.ts +++ b/ts/state/createStore.ts @@ -2,6 +2,8 @@ import promise from 'redux-promise-middleware'; import { createLogger } from 'redux-logger'; import { configureStore } from '@reduxjs/toolkit'; import { reducer as allReducers } from './reducer'; +import { persistReducer } from "redux-persist"; +import storage from 'redux-persist/lib/storage'; // @ts-ignore const env = window.getEnvironment(); @@ -22,13 +24,23 @@ const logger = createLogger({ logger: directConsole, }); +const persistConfig = { + key: 'root', + storage, + whitelist: ['userConfig'] +} + +const persistedReducer = persistReducer(persistConfig, allReducers); + // Exclude logger if we're in production mode const disableLogging = env === 'production' || true; // ALWAYS TURNED OFF +// const middlewareList = disableLogging ? [promise, thunk] : [promise, logger, thunk ]; const middlewareList = disableLogging ? [promise] : [promise, logger]; export const createStore = (initialState: any) => configureStore({ - reducer: allReducers, + // reducer: allReducers, + reducer: persistedReducer, preloadedState: initialState, middleware: (getDefaultMiddleware: any) => getDefaultMiddleware().concat(middlewareList), }); diff --git a/yarn.lock b/yarn.lock index c7405d35e..e566548e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9660,6 +9660,11 @@ redux-logger@3.0.6: dependencies: deep-diff "^0.3.5" +redux-persist@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8" + integrity sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ== + redux-promise-middleware@6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/redux-promise-middleware/-/redux-promise-middleware-6.1.0.tgz#ecdb22488cdd673c1a3f0d278d82b48d92ca5d06" From 5a62fabd1cfcc7e33e2fbd74ba469977a8b2b1b6 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Thu, 24 Jun 2021 16:47:27 +1000 Subject: [PATCH 06/13] Doesn't autoplay new messages once if last message received was an autoplayed. --- ts/components/conversation/H5AudioPlayer.tsx | 4 ++-- .../session/conversation/SessionMessagesList.tsx | 13 ++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/ts/components/conversation/H5AudioPlayer.tsx b/ts/components/conversation/H5AudioPlayer.tsx index d23169984..80d62c9e1 100644 --- a/ts/components/conversation/H5AudioPlayer.tsx +++ b/ts/components/conversation/H5AudioPlayer.tsx @@ -1,5 +1,5 @@ // Audio Player -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import H5AudioPlayer from 'react-h5-audio-player'; import { useSelector } from 'react-redux'; import { useTheme } from 'styled-components'; @@ -39,7 +39,7 @@ export const AudioPlayerWithEncryptedFile = (props: { if ( window.inboxStore?.getState().userConfig.audioAutoplay === true && props.playNextMessage && - props.playableMessageIndex + props.playableMessageIndex !== undefined ) { props.playNextMessage(props.playableMessageIndex); } diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx index bcf6ad4b2..4a9d787f4 100644 --- a/ts/components/session/conversation/SessionMessagesList.tsx +++ b/ts/components/session/conversation/SessionMessagesList.tsx @@ -277,12 +277,15 @@ export class SessionMessagesList extends React.Component { * @param index index of message that just completed */ const playNextMessage = (index: any) => { - const nextIndex = index - 1; - if (messages[nextIndex]) { - this.setState({ - nextMessageToPlay: nextIndex, - }); + let nextIndex: number | null = index - 1; + + if (index <= 0 || messages.length < index - 1) { + nextIndex = null; } + + this.setState({ + nextMessageToPlay: nextIndex, + }); }; if (messageProps) { From be11c37a5954e37875dc48f64904a2ecf3a4ecd9 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Thu, 24 Jun 2021 17:06:21 +1000 Subject: [PATCH 07/13] Linting and formatting. --- ts/components/conversation/H5AudioPlayer.tsx | 2 +- ts/components/session/SessionInboxView.tsx | 4 ++-- ts/components/session/settings/SessionSettings.tsx | 2 +- ts/state/createStore.ts | 8 +++++--- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ts/components/conversation/H5AudioPlayer.tsx b/ts/components/conversation/H5AudioPlayer.tsx index 80d62c9e1..53c3bf8ed 100644 --- a/ts/components/conversation/H5AudioPlayer.tsx +++ b/ts/components/conversation/H5AudioPlayer.tsx @@ -39,7 +39,7 @@ export const AudioPlayerWithEncryptedFile = (props: { if ( window.inboxStore?.getState().userConfig.audioAutoplay === true && props.playNextMessage && - props.playableMessageIndex !== undefined + props.playableMessageIndex !== undefined ) { props.playNextMessage(props.playableMessageIndex); } diff --git a/ts/components/session/SessionInboxView.tsx b/ts/components/session/SessionInboxView.tsx index 8e925767b..cea0685ea 100644 --- a/ts/components/session/SessionInboxView.tsx +++ b/ts/components/session/SessionInboxView.tsx @@ -19,6 +19,7 @@ import { makeLookup } from '../../util'; import { LeftPane } from '../LeftPane'; import { SessionMainPanel } from '../SessionMainPanel'; +// tslint:disable-next-line: no-submodule-imports import { PersistGate } from 'redux-persist/integration/react'; import { persistStore } from 'redux-persist'; @@ -57,12 +58,11 @@ export class SessionInboxView extends React.Component { return <>; } - let persistor = persistStore(this.store); + const persistor = persistStore(this.store); return ( -
{this.renderLeftPane()} diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 6daf1e68d..25a6499aa 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -363,7 +363,7 @@ class SettingsViewInner extends React.Component { window.inboxStore?.dispatch(toggleAudioAutoplay()); }, content: { - defaultValue: window.inboxStore?.getState().userConfig.audioAutoplay + defaultValue: window.inboxStore?.getState().userConfig.audioAutoplay, }, comparisonValue: undefined, onClick: undefined, diff --git a/ts/state/createStore.ts b/ts/state/createStore.ts index 9ce6b5e98..b5352895b 100644 --- a/ts/state/createStore.ts +++ b/ts/state/createStore.ts @@ -2,7 +2,9 @@ import promise from 'redux-promise-middleware'; import { createLogger } from 'redux-logger'; import { configureStore } from '@reduxjs/toolkit'; import { reducer as allReducers } from './reducer'; -import { persistReducer } from "redux-persist"; +import { persistReducer } from 'redux-persist'; + +// tslint:disable-next-line: no-submodule-imports match-default-export-name import storage from 'redux-persist/lib/storage'; // @ts-ignore @@ -27,8 +29,8 @@ const logger = createLogger({ const persistConfig = { key: 'root', storage, - whitelist: ['userConfig'] -} + whitelist: ['userConfig'], +}; const persistedReducer = persistReducer(persistConfig, allReducers); From 281b72cca0228a4b8238bccdb2a947476b86db89 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Fri, 25 Jun 2021 09:47:54 +1000 Subject: [PATCH 08/13] delete unused reducer --- ts/state/ducks/userConfig.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/ts/state/ducks/userConfig.tsx b/ts/state/ducks/userConfig.tsx index dd7001c8c..c4f7d4568 100644 --- a/ts/state/ducks/userConfig.tsx +++ b/ts/state/ducks/userConfig.tsx @@ -16,12 +16,6 @@ const userConfigSlice = createSlice({ name: 'userConfig', initialState: initialUserConfigState, reducers: { - updateUserConfig(state: UserConfigState, action: PayloadAction) { - return { - ...state, - audioAutoplay: true, - }; - }, toggleAudioAutoplay: state => { state.audioAutoplay = !state.audioAutoplay; }, @@ -29,5 +23,5 @@ const userConfigSlice = createSlice({ }); const { actions, reducer } = userConfigSlice; -export const { updateUserConfig, toggleAudioAutoplay } = actions; +export const { toggleAudioAutoplay } = actions; export const userConfigReducer = reducer; From 184c4d5d3ef58758f8ec4b99a38ba58533c532f8 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Fri, 25 Jun 2021 10:18:53 +1000 Subject: [PATCH 09/13] Audio autoplay stop when sent from a different user. --- .../session/conversation/SessionMessagesList.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx index bfaa4d3fb..fff6a69a1 100644 --- a/ts/components/session/conversation/SessionMessagesList.tsx +++ b/ts/components/session/conversation/SessionMessagesList.tsx @@ -279,7 +279,21 @@ export class SessionMessagesList extends React.Component { const playNextMessage = (index: any) => { let nextIndex: number | null = index - 1; - if (index <= 0 || messages.length < index - 1) { + // to prevent autoplaying as soon as a message is received. + const latestMessagePlayed = index <= 0 || messages.length < index - 1; + if (latestMessagePlayed) { + nextIndex = null; + this.setState({ + nextMessageToPlay: nextIndex, + }); + return; + } + + // stop auto-playing when the audio messages change author. + const prevAuthorNumber = messages[index].propsForMessage.authorPhoneNumber; + const nextAuthorNumber = messages[index - 1].propsForMessage.authorPhoneNumber; + const differentAuthor = prevAuthorNumber !== nextAuthorNumber; + if (differentAuthor) { nextIndex = null; } From f2984d0b38ec0660506bec37ada824a72937c343 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Fri, 25 Jun 2021 14:02:04 +1000 Subject: [PATCH 10/13] removed comment. --- ts/state/createStore.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ts/state/createStore.ts b/ts/state/createStore.ts index b5352895b..949fb1de2 100644 --- a/ts/state/createStore.ts +++ b/ts/state/createStore.ts @@ -36,7 +36,6 @@ const persistedReducer = persistReducer(persistConfig, allReducers); // Exclude logger if we're in production mode const disableLogging = env === 'production' || true; // ALWAYS TURNED OFF -// const middlewareList = disableLogging ? [promise, thunk] : [promise, logger, thunk ]; const middlewareList = disableLogging ? [promise] : [promise, logger]; export const createStore = (initialState: any) => From c04d3cd7d12c92bf7585e27ce4f1eb5b5156e5a7 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Fri, 25 Jun 2021 14:47:42 +1000 Subject: [PATCH 11/13] minor move function to larger scope --- .../conversation/SessionMessagesList.tsx | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx index fff6a69a1..62c77438e 100644 --- a/ts/components/session/conversation/SessionMessagesList.tsx +++ b/ts/components/session/conversation/SessionMessagesList.tsx @@ -272,40 +272,11 @@ export class SessionMessagesList extends React.Component { return; } - /** - * Sets the targeted index for the next - * @param index index of message that just completed - */ - const playNextMessage = (index: any) => { - let nextIndex: number | null = index - 1; - - // to prevent autoplaying as soon as a message is received. - const latestMessagePlayed = index <= 0 || messages.length < index - 1; - if (latestMessagePlayed) { - nextIndex = null; - this.setState({ - nextMessageToPlay: nextIndex, - }); - return; - } - - // stop auto-playing when the audio messages change author. - const prevAuthorNumber = messages[index].propsForMessage.authorPhoneNumber; - const nextAuthorNumber = messages[index - 1].propsForMessage.authorPhoneNumber; - const differentAuthor = prevAuthorNumber !== nextAuthorNumber; - if (differentAuthor) { - nextIndex = null; - } - - this.setState({ - nextMessageToPlay: nextIndex, - }); - }; if (messageProps) { messageProps.nextMessageToPlay = this.state.nextMessageToPlay; messageProps.playableMessageIndex = playableMessageIndex; - messageProps.playNextMessage = playNextMessage; + messageProps.playNextMessage = this.playNextMessage; } playableMessageIndex++; @@ -413,6 +384,38 @@ export class SessionMessagesList extends React.Component { } } + + /** + * Sets the targeted index for the next + * @param index index of message that just completed + */ + private playNextMessage = (index: any) => { + const { messages } = this.props; + let nextIndex: number | null = index - 1; + + // to prevent autoplaying as soon as a message is received. + const latestMessagePlayed = index <= 0 || messages.length < index - 1; + if (latestMessagePlayed) { + nextIndex = null; + this.setState({ + nextMessageToPlay: nextIndex, + }); + return; + } + + // stop auto-playing when the audio messages change author. + const prevAuthorNumber = messages[index].propsForMessage.authorPhoneNumber; + const nextAuthorNumber = messages[index - 1].propsForMessage.authorPhoneNumber; + const differentAuthor = prevAuthorNumber !== nextAuthorNumber; + if (differentAuthor) { + nextIndex = null; + } + + this.setState({ + nextMessageToPlay: nextIndex, + }); + }; + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~ SCROLLING METHODS ~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 5b5cea68312a3f9444405ee34edc0654db761381 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Fri, 25 Jun 2021 15:16:37 +1000 Subject: [PATCH 12/13] minor formatting and linting changes. --- ts/components/session/conversation/SessionMessagesList.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx index 62c77438e..e3af18792 100644 --- a/ts/components/session/conversation/SessionMessagesList.tsx +++ b/ts/components/session/conversation/SessionMessagesList.tsx @@ -272,7 +272,6 @@ export class SessionMessagesList extends React.Component { return; } - if (messageProps) { messageProps.nextMessageToPlay = this.state.nextMessageToPlay; messageProps.playableMessageIndex = playableMessageIndex; @@ -384,12 +383,11 @@ export class SessionMessagesList extends React.Component { } } - /** * Sets the targeted index for the next * @param index index of message that just completed */ - private playNextMessage = (index: any) => { + private readonly playNextMessage = (index: any) => { const { messages } = this.props; let nextIndex: number | null = index - 1; From 9bdd6ad139644b0f811c0b20fadfdf4493e93c10 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 25 Jun 2021 16:19:16 +1000 Subject: [PATCH 13/13] add button to control speed playback on the player --- _locales/en/messages.json | 1 - stylesheets/_session.scss | 20 +++++++++++++ ts/components/conversation/H5AudioPlayer.tsx | 31 ++++++++++++++------ ts/components/conversation/Message.tsx | 17 ----------- 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index f11bd65ed..28d5b669c 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -404,7 +404,6 @@ "device": "Device", "destination": "Destination", "learnMore": "Learn more", - "playAtCustomSpeed": "Play at $multipler$x speed", "linkVisitWarningTitle": "Open this link in your browser?", "linkVisitWarningMessage": "Are you sure you want to open $url$ in your browser?", "open": "Open", diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index 622277f15..4fbf56b6e 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -1385,3 +1385,23 @@ input { padding: $session-margin-sm $session-margin-lg; border-radius: 4px; } + +.speedButton { + padding: $session-margin-xs; + opacity: 0.6; + transition: none; + &:hover { + @include themify($themes) { + opacity: 1; + } + } + .session-button { + transition: none; + + &:hover { + @include themify($themes) { + color: themed('textColorOpposite'); + } + } + } +} diff --git a/ts/components/conversation/H5AudioPlayer.tsx b/ts/components/conversation/H5AudioPlayer.tsx index 04d4187f2..480bee5ac 100644 --- a/ts/components/conversation/H5AudioPlayer.tsx +++ b/ts/components/conversation/H5AudioPlayer.tsx @@ -1,18 +1,16 @@ // Audio Player -import React, { useEffect, useRef } from 'react'; -import H5AudioPlayer from 'react-h5-audio-player'; +import React, { useEffect, useRef, useState } from 'react'; +import H5AudioPlayer, { RHAP_UI } from 'react-h5-audio-player'; import { useTheme } from 'styled-components'; import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch'; -import { SessionIcon, SessionIconSize, SessionIconType } from '../session/icon'; +import { SessionIcon, SessionIconButton, SessionIconSize, SessionIconType } from '../session/icon'; +import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton'; -export const AudioPlayerWithEncryptedFile = (props: { - src: string; - contentType: string; - playbackSpeed: number; -}) => { +export const AudioPlayerWithEncryptedFile = (props: { src: string; contentType: string }) => { const theme = useTheme(); + + const [playbackSpeed, setPlaybackSpeed] = useState(1.0); const { urlToLoad } = useEncryptedFileFetch(props.src, props.contentType); - const { playbackSpeed } = props; const player = useRef(null); useEffect(() => { @@ -31,6 +29,21 @@ export const AudioPlayerWithEncryptedFile = (props: { showDownloadProgress={false} listenInterval={100} ref={player} + customControlsSection={[ + RHAP_UI.MAIN_CONTROLS, + RHAP_UI.VOLUME, +
+ { + setPlaybackSpeed(playbackSpeed === 1 ? 1.5 : 1); + }} + buttonType={SessionButtonType.Simple} + buttonColor={SessionButtonColor.None} + /> +
, + ]} customIcons={{ play: ( { expiring: false, expired: false, imageBroken: false, - playbackSpeed: 1, }; this.ctxMenuID = `ctx-menu-message-${uuid()}`; } @@ -205,7 +203,6 @@ class MessageInner extends React.PureComponent { }} > @@ -573,11 +570,6 @@ class MessageInner extends React.PureComponent { ) : null} - {isAudio(attachments) ? ( - - {window.i18n('playAtCustomSpeed', this.state.playbackSpeed === 1 ? 2 : 1)} - - ) : null} { MessageInteraction.copyBodyToClipboard(text); @@ -824,15 +816,6 @@ class MessageInner extends React.PureComponent { ); } - /** - * Doubles / halves the playback speed based on the current playback speed. - */ - private updatePlaybackSpeed() { - this.setState(prevState => ({ - playbackSpeed: prevState.playbackSpeed === 1 ? 2 : 1, - })); - } - private handleContextMenu(e: any) { e.preventDefault(); e.stopPropagation();