Merge pull request #1718 from warrickct/audio-messages-play-all

Audio messages play one after another setting
pull/1728/head
Audric Ackermann 4 years ago committed by GitHub
commit ef347115e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -408,6 +408,8 @@
"linkVisitWarningTitle": "Open this link in your browser?",
"linkVisitWarningMessage": "Are you sure you want to open $url$ in your browser?",
"open": "Open",
"audioMessageAutoplayTitle": "Audio Message Autoplay",
"audioMessageAutoplayDescription": "Automatically play consecutively sent audio messages",
"clickToTrustContact": "Click to download media",
"trustThisContactDialogTitle": "Trust $name$?",
"trustThisContactDialogDescription": "Are you sure you want to download media sent by $name$?"

@ -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",

@ -1,14 +1,19 @@
// 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';
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;
playNextMessage?: (index: number) => void;
playableMessageIndex?: number;
nextMessageToPlay?: number;
}) => {
const theme = useTheme();
const { urlToLoad } = useEncryptedFileFetch(props.src, props.contentType);
@ -22,6 +27,24 @@ export const AudioPlayerWithEncryptedFile = (props: {
}
}, [playbackSpeed]);
useEffect(() => {
if (props.playableMessageIndex === props.nextMessageToPlay) {
player.current?.audio.current?.play();
}
});
const onEnded = () => {
// 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 !== undefined
) {
props.playNextMessage(props.playableMessageIndex);
}
};
return (
<H5AudioPlayer
src={urlToLoad}
@ -30,6 +53,7 @@ export const AudioPlayerWithEncryptedFile = (props: {
showJumpControls={false}
showDownloadProgress={false}
listenInterval={100}
onEnded={onEnded}
ref={player}
customIcons={{
play: (

@ -208,6 +208,9 @@ class MessageInner extends React.PureComponent<MessageRegularProps, State> {
playbackSpeed={this.state.playbackSpeed}
src={firstAttachment.url}
contentType={firstAttachment.contentType}
playNextMessage={this.props.playNextMessage}
playableMessageIndex={this.props.playableMessageIndex}
nextMessageToPlay={this.props.nextMessageToPlay}
/>
</div>
);

@ -13,11 +13,16 @@ 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';
import { SessionMainPanel } from '../SessionMainPanel';
// tslint:disable-next-line: no-submodule-imports
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
@ -53,13 +58,17 @@ export class SessionInboxView extends React.Component<any, State> {
return <></>;
}
const persistor = persistStore(this.store);
return (
<Provider store={this.store}>
<div className="gutter">
<div className="network-status-container" />
{this.renderLeftPane()}
</div>
<SessionMainPanel />
<PersistGate loading={null} persistor={persistor}>
<div className="gutter">
<div className="network-status-container" />
{this.renderLeftPane()}
</div>
<SessionMainPanel />
</PersistGate>
</Provider>
);
}
@ -96,6 +105,7 @@ export class SessionInboxView extends React.Component<any, State> {
mentionsInput: initialMentionsState,
onionPaths: initialOnionPathState,
modals: initialModalState,
userConfig: initialUserConfigState,
};
this.store = createStore(initialState);

@ -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<Props, State> {
this.state = {
showScrollButton: false,
nextMessageToPlay: null,
};
autoBind(this);
@ -196,6 +198,7 @@ export class SessionMessagesList extends React.Component<Props, State> {
const { conversation, ourPrimary, selectedMessages } = this.props;
const multiSelectMode = Boolean(selectedMessages.length);
let currentMessageIndex = 0;
let playableMessageIndex = 0;
const displayUnreadBannerIndex = this.displayUnreadBannerIndex(messages);
return (
@ -269,6 +272,13 @@ export class SessionMessagesList extends React.Component<Props, State> {
return;
}
if (messageProps) {
messageProps.nextMessageToPlay = this.state.nextMessageToPlay;
messageProps.playableMessageIndex = playableMessageIndex;
messageProps.playNextMessage = this.playNextMessage;
}
playableMessageIndex++;
if (messageProps.conversationType === ConversationTypeEnum.GROUP) {
messageProps.weAreAdmin = conversation.groupAdmins?.includes(ourPrimary);
}
@ -373,6 +383,37 @@ export class SessionMessagesList extends React.Component<Props, State> {
}
}
/**
* Sets the targeted index for the next
* @param index index of message that just completed
*/
private readonly 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 ~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

@ -4,18 +4,18 @@ 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 { getConversationController } 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';
import { SessionConfirmDialogProps } from '../SessionConfirm';
import { mapDispatchToProps } from '../../../state/actions';
import { unblockConvoById } from '../../../interactions/conversationInteractions';
import { toggleAudioAutoplay } from '../../../state/ducks/userConfig';
import { sessionPassword } from '../../../state/ducks/modalDialog';
import { PasswordAction } from '../SessionPasswordModal';
@ -264,10 +264,12 @@ class SettingsViewInner extends React.Component<SettingsViewProps, State> {
});
}
/**
* 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);
@ -350,6 +352,23 @@ class SettingsViewInner extends React.Component<SettingsViewProps, State> {
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: {
defaultValue: window.inboxStore?.getState().userConfig.audioAutoplay,
},
comparisonValue: undefined,
onClick: undefined,
confirmationDialogParams: undefined,
},
{
id: 'notification-setting',
title: window.i18n('notificationSettingsDialog'),

@ -253,4 +253,8 @@ export interface MessageRegularProps {
onShowDetail: () => void;
markRead: (readAt: number) => Promise<void>;
theme: DefaultTheme;
playableMessageIndex?: number;
nextMessageToPlay?: number;
playNextMessage?: (value: number) => any;
}

@ -2,6 +2,10 @@ 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';
// tslint:disable-next-line: no-submodule-imports match-default-export-name
import storage from 'redux-persist/lib/storage';
// @ts-ignore
const env = window.getEnvironment();
@ -22,13 +26,22 @@ 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] : [promise, logger];
export const createStore = (initialState: any) =>
configureStore({
reducer: allReducers,
// reducer: allReducers,
reducer: persistedReducer,
preloadedState: initialState,
middleware: (getDefaultMiddleware: any) => getDefaultMiddleware().concat(middlewareList),
});

@ -0,0 +1,27 @@
/**
* 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: {
toggleAudioAutoplay: state => {
state.audioAutoplay = !state.audioAutoplay;
},
},
});
const { actions, reducer } = userConfigSlice;
export const { toggleAudioAutoplay } = actions;
export const userConfigReducer = reducer;

@ -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

@ -0,0 +1,10 @@
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
);

@ -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"

Loading…
Cancel
Save