Merge pull request #2869 from Bilb/feature/ses-476/remove-profile-picture

Feature/ses 476/remove profile picture
pull/2885/head
Audric Ackermann 9 months ago committed by GitHub
commit 70b220400c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -509,5 +509,6 @@
"reactionPopupThree": "$name$, $name2$ & $name3$",
"reactionPopupMany": "$name$, $name2$, $name3$ &",
"reactionListCountSingular": "And $otherSingular$ has reacted <span>$emoji$</span> to this message",
"reactionListCountPlural": "And $otherPlural$ have reacted <span>$emoji$</span> to this message"
"reactionListCountPlural": "And $otherPlural$ have reacted <span>$emoji$</span> to this message",
"setDisplayPicture": "Set Display Picture"
}

@ -6,6 +6,7 @@ from os import path, listdir
from glob import glob
import json
import sys
from collections import OrderedDict
LOCALES_FOLDER = './_locales'
@ -16,10 +17,10 @@ LOCALIZED_KEYS_FILE = './ts/types/LocalizerKeys.ts'
stringToWrite = "export type LocalizerKeys =\n | "
with open(EN_FILE,'r') as jsonFile:
data = json.load(jsonFile)
keys = data.keys()
data = json.loads(jsonFile.read(), object_pairs_hook=OrderedDict)
keys = sorted(list(data.keys()))
stringToWrite += json.dumps(list(keys), sort_keys=True).replace(',', '\n |').replace('"', '\'')[1:-1]
stringToWrite += json.dumps(keys, sort_keys=True).replace(',', '\n |').replace('"', '\'')[1:-1]
stringToWrite += ';\n'

@ -1,5 +1,6 @@
import autoBind from 'auto-bind';
import React, { ChangeEvent, MouseEvent } from 'react';
import { useDispatch } from 'react-redux';
// eslint-disable-next-line import/no-named-default
import { ChangeEvent, MouseEvent, default as React, ReactElement, useState } from 'react';
import { QRCode } from 'react-qr-svg';
import styled from 'styled-components';
import { Avatar, AvatarSize } from '../avatar/Avatar';
@ -7,15 +8,12 @@ import { Avatar, AvatarSize } from '../avatar/Avatar';
import { SyncUtils, ToastUtils, UserUtils } from '../../session/utils';
import { YourSessionIDPill, YourSessionIDSelectable } from '../basic/YourSessionIDPill';
import { ConversationModel } from '../../models/conversation';
import { uploadOurAvatar } from '../../interactions/conversationInteractions';
import { useOurAvatarPath, useOurConversationUsername } from '../../hooks/useParamSelector';
import { ConversationTypeEnum } from '../../models/conversationAttributes';
import { MAX_USERNAME_BYTES } from '../../session/constants';
import { getConversationController } from '../../session/conversations';
import { sanitizeSessionUsername } from '../../session/utils/String';
import { editProfileModal } from '../../state/ducks/modalDialog';
import { pickFileForAvatar } from '../../types/attachments/VisualAttachment';
import { editProfileModal, updateEditProfilePictureModel } from '../../state/ducks/modalDialog';
import { saveQRCode } from '../../util/saveQRCode';
import { setLastProfileUpdateTimestamp } from '../../util/storage';
import { SessionWrapperModal } from '../SessionWrapperModal';
@ -50,311 +48,255 @@ const QRView = ({ sessionID }: { sessionID: string }) => {
);
};
interface State {
profileName: string;
updatedProfileName: string;
oldAvatarPath: string;
newAvatarObjectUrl: string | null;
mode: 'default' | 'edit' | 'qr';
loading: boolean;
}
export class EditProfileDialog extends React.Component<object, State> {
private readonly convo: ConversationModel;
constructor(props: object) {
super(props);
autoBind(this);
this.convo = getConversationController().get(UserUtils.getOurPubKeyStrFromCache());
this.state = {
profileName: this.convo.getRealSessionUsername() || '',
updatedProfileName: this.convo.getRealSessionUsername() || '',
oldAvatarPath: this.convo.getAvatarPath() || '',
newAvatarObjectUrl: null,
mode: 'default',
loading: false,
};
}
public componentDidMount() {
window.addEventListener('keyup', this.onKeyUp);
}
const updateDisplayName = async (newName: string) => {
const ourNumber = UserUtils.getOurPubKeyStrFromCache();
const conversation = await getConversationController().getOrCreateAndWait(
ourNumber,
ConversationTypeEnum.PRIVATE
);
conversation.setSessionDisplayNameNoCommit(newName);
public componentWillUnmount() {
window.removeEventListener('keyup', this.onKeyUp);
}
// might be good to not trigger a sync if the name did not change
await conversation.commit();
await setLastProfileUpdateTimestamp(Date.now());
await SyncUtils.forceSyncConfigurationNowIfNeeded(true);
};
public render() {
const i18n = window.i18n;
type ProfileAvatarProps = {
avatarPath: string | null;
newAvatarObjectUrl?: string | null;
profileName: string | undefined;
ourId: string;
};
const viewDefault = this.state.mode === 'default';
const viewEdit = this.state.mode === 'edit';
const viewQR = this.state.mode === 'qr';
export const ProfileAvatar = (props: ProfileAvatarProps): ReactElement => {
const { newAvatarObjectUrl, avatarPath, profileName, ourId } = props;
return (
<Avatar
forcedAvatarPath={newAvatarObjectUrl || avatarPath}
forcedName={profileName || ourId}
size={AvatarSize.XL}
pubkey={ourId}
/>
);
};
const sessionID = UserUtils.getOurPubKeyStrFromCache();
type ProfileHeaderProps = ProfileAvatarProps & {
onClick: () => void;
setMode: (mode: ProfileDialogModes) => void;
};
const backButton =
viewEdit || viewQR
? [
{
iconType: 'chevron',
iconRotation: 90,
onClick: () => {
this.setState({ mode: 'default' });
},
},
]
: undefined;
const ProfileHeader = (props: ProfileHeaderProps): ReactElement => {
const { avatarPath, profileName, ourId, onClick, setMode } = props;
return (
<div className="edit-profile-dialog" data-testid="edit-profile-dialog">
<SessionWrapperModal
title={i18n('editProfileModalTitle')}
onClose={this.closeDialog}
headerIconButtons={backButton}
showExitIcon={true}
return (
<div className="avatar-center">
<div className="avatar-center-inner">
<ProfileAvatar avatarPath={avatarPath} profileName={profileName} ourId={ourId} />
<div
className="image-upload-section"
role="button"
onClick={onClick}
data-testid="image-upload-section"
/>
<div
className="qr-view-button"
onClick={() => {
setMode('qr');
}}
role="button"
>
{viewQR && <QRView sessionID={sessionID} />}
{viewDefault && this.renderDefaultView()}
{viewEdit && this.renderEditView()}
<div className="session-id-section">
<YourSessionIDPill />
<YourSessionIDSelectable />
<SessionSpinner loading={this.state.loading} />
{viewDefault || viewQR ? (
<SessionButton
text={window.i18n('editMenuCopy')}
buttonType={SessionButtonType.Simple}
onClick={() => {
window.clipboard.writeText(sessionID);
ToastUtils.pushCopiedToClipBoard();
}}
dataTestId="copy-button-profile-update"
/>
) : (
!this.state.loading && (
<SessionButton
text={window.i18n('save')}
buttonType={SessionButtonType.Simple}
onClick={this.onClickOK}
disabled={this.state.loading}
dataTestId="save-button-profile-update"
/>
)
)}
</div>
</SessionWrapperModal>
</div>
);
}
private renderProfileHeader() {
return (
<>
<div className="avatar-center">
<div className="avatar-center-inner">
{this.renderAvatar()}
<div
className="image-upload-section"
role="button"
// eslint-disable-next-line @typescript-eslint/no-misused-promises
onClick={this.fireInputEvent}
data-testid="image-upload-section"
/>
<div
className="qr-view-button"
onClick={() => {
this.setState(state => ({ ...state, mode: 'qr' }));
}}
role="button"
>
<SessionIconButton iconType="qr" iconSize="small" iconColor="var(--black-color)" />
</div>
</div>
<SessionIconButton iconType="qr" iconSize="small" iconColor="var(--black-color)" />
</div>
</>
);
}
private async fireInputEvent() {
const scaledAvatarUrl = await pickFileForAvatar();
</div>
</div>
);
};
if (scaledAvatarUrl) {
this.setState({
newAvatarObjectUrl: scaledAvatarUrl,
mode: 'edit',
});
}
}
type ProfileDialogModes = 'default' | 'edit' | 'qr';
// tslint:disable-next-line: max-func-body-length
export const EditProfileDialog = (): ReactElement => {
const dispatch = useDispatch();
private renderDefaultView() {
const name = this.state.updatedProfileName || this.state.profileName;
return (
<>
{this.renderProfileHeader()}
const _profileName = useOurConversationUsername() || '';
const [profileName, setProfileName] = useState(_profileName);
const [updatedProfileName, setUpdateProfileName] = useState(profileName);
const avatarPath = useOurAvatarPath() || '';
const ourId = UserUtils.getOurPubKeyStrFromCache();
<div className="profile-name-uneditable">
<p data-testid="your-profile-name">{name}</p>
<SessionIconButton
iconType="pencil"
iconSize="medium"
onClick={() => {
this.setState({ mode: 'edit' });
}}
dataTestId="edit-profile-icon"
/>
</div>
</>
);
}
const [mode, setMode] = useState<ProfileDialogModes>('default');
const [loading, setLoading] = useState(false);
private renderEditView() {
const placeholderText = window.i18n('displayName');
const closeDialog = () => {
window.removeEventListener('keyup', handleOnKeyUp);
window.inboxStore?.dispatch(editProfileModal(null));
};
const backButton =
mode === 'edit' || mode === 'qr'
? [
{
iconType: 'chevron',
iconRotation: 90,
onClick: () => {
setMode('default');
},
},
]
: undefined;
const onClickOK = async () => {
/**
* Tidy the profile name input text and save the new profile name and avatar
*/
try {
const newName = profileName ? profileName.trim() : '';
return (
<>
{this.renderProfileHeader()}
<div className="profile-name">
<input
type="text"
className="profile-name-input"
value={this.state.profileName}
placeholder={placeholderText}
onChange={this.onNameEdited}
maxLength={MAX_USERNAME_BYTES}
tabIndex={0}
required={true}
aria-required={true}
data-testid="profile-name-input"
/>
</div>
</>
);
}
if (newName.length === 0 || newName.length > MAX_USERNAME_BYTES) {
return;
}
private renderAvatar() {
const { oldAvatarPath, newAvatarObjectUrl, profileName } = this.state;
const userName = profileName || this.convo.id;
// this throw if the length in bytes is too long
const sanitizedName = sanitizeSessionUsername(newName);
const trimName = sanitizedName.trim();
return (
<Avatar
forcedAvatarPath={newAvatarObjectUrl || oldAvatarPath}
forcedName={userName}
size={AvatarSize.XL}
pubkey={this.convo.id}
/>
);
}
setUpdateProfileName(trimName);
setLoading(true);
private onNameEdited(event: ChangeEvent<HTMLInputElement>) {
const displayName = event.target.value;
try {
const newName = sanitizeSessionUsername(displayName);
this.setState({
profileName: newName,
});
await updateDisplayName(newName);
setMode('default');
setUpdateProfileName(profileName);
setLoading(false);
} catch (e) {
this.setState({
profileName: displayName,
});
ToastUtils.pushToastError('nameTooLong', window.i18n('displayNameTooLong'));
}
}
};
private onKeyUp(event: any) {
const handleOnKeyUp = (event: any) => {
switch (event.key) {
case 'Enter':
if (this.state.mode === 'edit') {
this.onClickOK();
if (mode === 'edit') {
void onClickOK();
}
break;
case 'Esc':
case 'Escape':
this.closeDialog();
closeDialog();
break;
default:
}
}
};
const handleProfileHeaderClick = () => {
closeDialog();
dispatch(
updateEditProfilePictureModel({
avatarPath,
profileName,
ourId,
})
);
};
/**
* Tidy the profile name input text and save the new profile name and avatar
*/
private onClickOK() {
const { newAvatarObjectUrl, profileName } = this.state;
const onNameEdited = (event: ChangeEvent<HTMLInputElement>) => {
const displayName = event.target.value;
try {
const newName = profileName ? profileName.trim() : '';
if (newName.length === 0 || newName.length > MAX_USERNAME_BYTES) {
return;
}
// this throw if the length in bytes is too long
const sanitizedName = sanitizeSessionUsername(newName);
const trimName = sanitizedName.trim();
this.setState(
{
profileName: trimName,
loading: true,
},
// eslint-disable-next-line @typescript-eslint/no-misused-promises
async () => {
await commitProfileEdits(newName, newAvatarObjectUrl);
this.setState({
loading: false,
mode: 'default',
updatedProfileName: this.state.profileName,
});
}
);
const newName = sanitizeSessionUsername(displayName);
setProfileName(newName);
} catch (e) {
setProfileName(displayName);
ToastUtils.pushToastError('nameTooLong', window.i18n('displayNameTooLong'));
}
}
};
private closeDialog() {
window.removeEventListener('keyup', this.onKeyUp);
window.inboxStore?.dispatch(editProfileModal(null));
}
}
return (
/* The <div> element has a child <input> element that allows keyboard interaction */
/* tslint:disable-next-line: react-a11y-event-has-role */
<div className="edit-profile-dialog" data-testid="edit-profile-dialog" onKeyUp={handleOnKeyUp}>
<SessionWrapperModal
title={window.i18n('editProfileModalTitle')}
onClose={closeDialog}
headerIconButtons={backButton}
showExitIcon={true}
>
{mode === 'qr' && <QRView sessionID={ourId} />}
{mode === 'default' && (
<>
<ProfileHeader
avatarPath={avatarPath}
profileName={profileName}
ourId={ourId}
onClick={handleProfileHeaderClick}
setMode={setMode}
/>
<div className="profile-name-uneditable">
<p data-testid="your-profile-name">{updatedProfileName || profileName}</p>
<SessionIconButton
iconType="pencil"
iconSize="medium"
onClick={() => {
setMode('edit');
}}
dataTestId="edit-profile-icon"
/>
</div>
</>
)}
{mode === 'edit' && (
<>
<ProfileHeader
avatarPath={avatarPath}
profileName={profileName}
ourId={ourId}
onClick={handleProfileHeaderClick}
setMode={setMode}
/>
<div className="profile-name">
<input
type="text"
className="profile-name-input"
value={profileName}
placeholder={window.i18n('displayName')}
onChange={onNameEdited}
maxLength={MAX_USERNAME_BYTES}
tabIndex={0}
required={true}
aria-required={true}
data-testid="profile-name-input"
/>
</div>
</>
)}
async function commitProfileEdits(newName: string, scaledAvatarUrl: string | null) {
const ourNumber = UserUtils.getOurPubKeyStrFromCache();
const conversation = await getConversationController().getOrCreateAndWait(
ourNumber,
ConversationTypeEnum.PRIVATE
);
<div className="session-id-section">
<YourSessionIDPill />
<YourSessionIDSelectable />
if (scaledAvatarUrl?.length) {
try {
const blobContent = await (await fetch(scaledAvatarUrl)).blob();
if (!blobContent || !blobContent.size) {
throw new Error('Failed to fetch blob content from scaled avatar');
}
await uploadOurAvatar(await blobContent.arrayBuffer());
} catch (error) {
if (error.message && error.message.length) {
ToastUtils.pushToastError('edit-profile', error.message);
}
window.log.error(
'showEditProfileDialog Error ensuring that image is properly sized:',
error && error.stack ? error.stack : error
);
}
return;
}
// do not update the avatar if it did not change
conversation.setSessionDisplayNameNoCommit(newName);
<SessionSpinner loading={loading} />
// might be good to not trigger a sync if the name did not change
await conversation.commit();
await setLastProfileUpdateTimestamp(Date.now());
await SyncUtils.forceSyncConfigurationNowIfNeeded(true);
}
{mode === 'default' || mode === 'qr' ? (
<SessionButton
text={window.i18n('editMenuCopy')}
buttonType={SessionButtonType.Simple}
onClick={() => {
window.clipboard.writeText(ourId);
ToastUtils.pushCopiedToClipBoard();
}}
dataTestId="copy-button-profile-update"
/>
) : (
!loading && (
<SessionButton
text={window.i18n('save')}
buttonType={SessionButtonType.Simple}
onClick={onClickOK}
disabled={loading}
dataTestId="save-button-profile-update"
/>
)
)}
</div>
</SessionWrapperModal>
</div>
);
};

@ -0,0 +1,164 @@
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import { clearOurAvatar, uploadOurAvatar } from '../../interactions/conversationInteractions';
import { ToastUtils } from '../../session/utils';
import { editProfileModal, updateEditProfilePictureModel } from '../../state/ducks/modalDialog';
import { pickFileForAvatar } from '../../types/attachments/VisualAttachment';
import { SessionWrapperModal } from '../SessionWrapperModal';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionSpinner } from '../basic/SessionSpinner';
import { SpacerLG } from '../basic/Text';
import { SessionIconButton } from '../icon';
import { ProfileAvatar } from './EditProfileDialog';
const StyledAvatarContainer = styled.div`
cursor: pointer;
`;
const UploadImageButton = () => {
return (
<div style={{ position: 'relative' }}>
<div style={{ borderRadius: '50%', overflow: 'hidden' }}>
<SessionIconButton
iconType="thumbnail"
iconSize="max"
iconPadding="16px"
backgroundColor="var(--chat-buttons-background-color)"
/>
</div>
<SessionIconButton
iconType="plusFat"
iconSize="medium"
iconColor="var(--modal-background-content-color)"
iconPadding="4.5px"
borderRadius="50%"
backgroundColor="var(--primary-color)"
style={{ position: 'absolute', bottom: 0, right: 0 }}
/>
</div>
);
};
const uploadProfileAvatar = async (scaledAvatarUrl: string | null) => {
if (scaledAvatarUrl?.length) {
try {
const blobContent = await (await fetch(scaledAvatarUrl)).blob();
if (!blobContent || !blobContent.size) {
throw new Error('Failed to fetch blob content from scaled avatar');
}
await uploadOurAvatar(await blobContent.arrayBuffer());
} catch (error) {
if (error.message && error.message.length) {
ToastUtils.pushToastError('edit-profile', error.message);
}
window.log.error(
'showEditProfileDialog Error ensuring that image is properly sized:',
error && error.stack ? error.stack : error
);
}
}
};
export type EditProfilePictureModalProps = {
avatarPath: string | null;
profileName: string | undefined;
ourId: string;
};
export const EditProfilePictureModal = (props: EditProfilePictureModalProps) => {
const dispatch = useDispatch();
const [newAvatarObjectUrl, setNewAvatarObjectUrl] = useState<string | null>(props.avatarPath);
const [loading, setLoading] = useState(false);
if (!props) {
return null;
}
const { avatarPath, profileName, ourId } = props;
const closeDialog = () => {
dispatch(updateEditProfilePictureModel(null));
dispatch(editProfileModal({}));
};
const handleAvatarClick = async () => {
const updatedAvatarObjectUrl = await pickFileForAvatar();
if (updatedAvatarObjectUrl) {
setNewAvatarObjectUrl(updatedAvatarObjectUrl);
}
};
const handleUpload = async () => {
setLoading(true);
if (newAvatarObjectUrl === avatarPath) {
window.log.debug('Avatar Object URL has not changed!');
return;
}
await uploadProfileAvatar(newAvatarObjectUrl);
setLoading(false);
dispatch(updateEditProfilePictureModel(null));
};
const handleRemove = async () => {
setLoading(true);
await clearOurAvatar();
setNewAvatarObjectUrl(null);
setLoading(false);
dispatch(updateEditProfilePictureModel(null));
};
return (
<SessionWrapperModal
title={window.i18n('setDisplayPicture')}
onClose={closeDialog}
showHeader={true}
showExitIcon={true}
>
<div
className="avatar-center"
role="button"
onClick={() => void handleAvatarClick()}
data-testid={'image-upload-click'}
>
<StyledAvatarContainer className="avatar-center-inner">
{newAvatarObjectUrl || avatarPath ? (
<ProfileAvatar
newAvatarObjectUrl={newAvatarObjectUrl}
avatarPath={avatarPath}
profileName={profileName}
ourId={ourId}
/>
) : (
<UploadImageButton />
)}
</StyledAvatarContainer>
</div>
{loading ? (
<SessionSpinner loading={loading} />
) : (
<>
<SpacerLG />
<div className="session-modal__button-group">
<SessionButton
text={window.i18n('save')}
buttonType={SessionButtonType.Simple}
onClick={handleUpload}
disabled={newAvatarObjectUrl === avatarPath}
dataTestId="save-button-profile-update"
/>
<SessionButton
text={window.i18n('remove')}
buttonColor={SessionButtonColor.Danger}
buttonType={SessionButtonType.Simple}
onClick={handleRemove}
disabled={!avatarPath}
/>
</div>
</>
)}
</SessionWrapperModal>
);
};

@ -8,6 +8,7 @@ import {
getConfirmModal,
getDeleteAccountModalState,
getEditProfileDialog,
getEditProfilePictureModalState,
getInviteContactModal,
getOnionPathDialog,
getReactClearAllDialog,
@ -36,6 +37,7 @@ import { SessionNicknameDialog } from './SessionNicknameDialog';
import { BanOrUnBanUserDialog } from './BanOrUnbanUserDialog';
import { ReactListModal } from './ReactListModal';
import { ReactClearAllModal } from './ReactClearAllModal';
import { EditProfilePictureModal } from './EditProfilePictureModal';
export const ModalContainer = () => {
const confirmModalState = useSelector(getConfirmModal);
@ -55,6 +57,7 @@ export const ModalContainer = () => {
const banOrUnbanUserModalState = useSelector(getBanOrUnbanUserModalState);
const reactListModalState = useSelector(getReactListDialog);
const reactClearAllModalState = useSelector(getReactClearAllDialog);
const editProfilePictureModalState = useSelector(getEditProfilePictureModalState);
return (
<>
@ -79,6 +82,9 @@ export const ModalContainer = () => {
{confirmModalState && <SessionConfirm {...confirmModalState} />}
{reactListModalState && <ReactListModal {...reactListModalState} />}
{reactClearAllModalState && <ReactClearAllModal {...reactClearAllModalState} />}
{editProfilePictureModalState && (
<EditProfilePictureModal {...editProfilePictureModalState} />
)}
</>
);
};

@ -66,6 +66,7 @@ export type SessionIconType =
| 'doubleCheckCircle'
| 'gallery'
| 'stop'
| 'thumbnail'
| 'timer00'
| 'timer05'
| 'timer10'
@ -83,7 +84,7 @@ export type SessionIconType =
export type SessionIconSize = 'tiny' | 'small' | 'medium' | 'large' | 'huge' | 'huge2' | 'max';
export const icons = {
export const icons: Record<string, { path: string; viewBox: string; ratio: number }> = {
addUser: {
path:
'M8.85,2.17c-1.73,0-3.12,1.4-3.12,3.12s1.4,3.12,3.12,3.12c1.73,0,3.13-1.4,3.13-3.12S10.58,2.17,8.85,2.17z M8.85,0.08c2.88,0,5.21,2.33,5.21,5.21s-2.33,5.21-5.21,5.21s-5.2-2.33-5.2-5.21C3.65,2.42,5.98,0.08,8.85,0.08z M20.83,5.29 c0.54,0,0.98,0.41,1.04,0.93l0.01,0.11v2.08h2.08c0.54,0,0.98,0.41,1.04,0.93v0.12c0,0.54-0.41,0.98-0.93,1.04l-0.11,0.01h-2.08 v2.08c0,0.58-0.47,1.04-1.04,1.04c-0.54,0-0.98-0.41-1.04-0.93l-0.01-0.11v-2.08h-2.08c-0.54,0-0.98-0.41-1.04-0.93l-0.01-0.11 c0-0.54,0.41-0.98,0.93-1.04l0.11-0.01h2.08V6.34C19.79,5.76,20.26,5.29,20.83,5.29z M12.5,12.58c2.8,0,5.09,2.21,5.2,4.99v0.22 v2.08c0,0.58-0.47,1.04-1.04,1.04c-0.54,0-0.98-0.41-1.04-0.93l-0.01-0.11v-2.08c0-1.67-1.3-3.03-2.95-3.12h-0.18H5.21 c-1.67,0-3.03,1.3-3.12,2.95v0.18v2.08c0,0.58-0.47,1.04-1.04,1.04c-0.54,0-0.98-0.41-1.04-0.93L0,19.88V17.8 c0-2.8,2.21-5.09,4.99-5.2h0.22h7.29V12.58z',
@ -475,6 +476,12 @@ export const icons = {
viewBox: '-1 -1 35 35',
ratio: 1,
},
thumbnail: {
path:
'm34.915 23.899-8.321-6.087a.308.308 0 0 0-.385 0l-7.461 6.177a.334.334 0 0 1-.398 0l-3.75-3.057a.308.308 0 0 0-.372 0L4.07 28.15a.335.335 0 0 0-.129.257v2.337a.745.745 0 0 0 .732.732h29.638a.732.732 0 0 0 .731-.732v-6.6a.281.281 0 0 0-.128-.244Zm-23.82-5.15a2.89 2.89 0 1 0 0-5.778 2.89 2.89 0 0 0 0 5.778ZM40.36 4.624 8.193.874a3.197 3.197 0 0 0-3.57 2.838L4.469 5.1h2.568l.128-1.091a.63.63 0 0 1 .244-.424.642.642 0 0 1 .386-.141h.064L22.15 5.099h13.534a5.137 5.137 0 0 1 4.071 2.042h.308a.642.642 0 0 1 .565.706l-.09.732a5.14 5.14 0 0 1 .283 1.605v18.312L43.185 8.18a3.223 3.223 0 0 0-2.825-3.557Zm-4.675 30.678H3.3a3.21 3.21 0 0 1-3.21-3.21v-21.83a3.21 3.21 0 0 1 3.21-3.21h32.385a3.21 3.21 0 0 1 3.21 3.21v21.83a3.21 3.21 0 0 1-3.21 3.21ZM3.3 9.594a.642.642 0 0 0-.642.642v21.83a.642.642 0 0 0 .642.642h32.385a.642.642 0 0 0 .643-.642v-21.83a.642.642 0 0 0-.643-.642H3.3Z',
viewBox: '0 0 44 36',
ratio: 1,
},
timer00: {
path:
'M11.428367,3.44328115 L10.5587469,3.94535651 C10.4906607,3.79477198 10.4145019,3.64614153 10.330127,3.5 C10.2457522,3.35385847 10.1551138,3.21358774 10.0587469,3.07933111 L10.928367,2.57725574 C11.0225793,2.71323387 11.1119641,2.85418158 11.1961524,3 C11.2803407,3.14581842 11.3577126,3.2937018 11.428367,3.44328115 Z M9.42274426,1.07163304 L8.92066889,1.94125309 C8.78641226,1.84488615 8.64614153,1.75424783 8.5,1.66987298 C8.35385847,1.58549813 8.20522802,1.50933927 8.05464349,1.44125309 L8.55671885,0.571633044 C8.7062982,0.642287382 8.85418158,0.719659271 9,0.803847577 C9.14581842,0.888035884 9.28676613,0.977420696 9.42274426,1.07163304 Z M11.9794631,6.5 L10.9753124,6.5 C10.9916403,6.33554688 11,6.1687497 11,6 C11,5.8312503 10.9916403,5.66445312 10.9753124,5.5 L11.9794631,5.5 C11.9930643,5.66486669 12,5.83162339 12,6 C12,6.16837661 11.9930643,6.33513331 11.9794631,6.5 Z M10.928367,9.42274426 L10.0587469,8.92066889 C10.1551138,8.78641226 10.2457522,8.64614153 10.330127,8.5 C10.4145019,8.35385847 10.4906607,8.20522802 10.5587469,8.05464349 L11.428367,8.55671885 C11.3577126,8.7062982 11.2803407,8.85418158 11.1961524,9 C11.1119641,9.14581842 11.0225793,9.28676613 10.928367,9.42274426 Z M8.55671885,11.428367 L8.05464349,10.5587469 C8.20522802,10.4906607 8.35385847,10.4145019 8.5,10.330127 C8.64614153,10.2457522 8.78641226,10.1551138 8.92066889,10.0587469 L9.42274426,10.928367 C9.28676613,11.0225793 9.14581842,11.1119641 9,11.1961524 C8.85418158,11.2803407 8.7062982,11.3577126 8.55671885,11.428367 Z M2.57725574,10.928367 L3.07933111,10.0587469 C3.21358774,10.1551138 3.35385847,10.2457522 3.5,10.330127 C3.64614153,10.4145019 3.79477198,10.4906607 3.94535651,10.5587469 L3.44328115,11.428367 C3.2937018,11.3577126 3.14581842,11.2803407 3,11.1961524 C2.85418158,11.1119641 2.71323387,11.0225793 2.57725574,10.928367 Z M5.5,11.9794631 L5.5,10.9753124 C5.66445312,10.9916403 5.8312503,11 6,11 C6.1687497,11 6.33554688,10.9916403 6.5,10.9753124 L6.5,11.9794631 C6.33513331,11.9930643 6.16837661,12 6,12 C5.83162339,12 5.66486669,11.9930643 5.5,11.9794631 Z M0.571633044,8.55671885 L1.44125309,8.05464349 C1.50933927,8.20522802 1.58549813,8.35385847 1.66987298,8.5 C1.75424783,8.64614153 1.84488615,8.78641226 1.94125309,8.92066889 L1.07163304,9.42274426 C0.977420696,9.28676613 0.888035884,9.14581842 0.803847577,9 C0.719659271,8.85418158 0.642287382,8.7062982 0.571633044,8.55671885 Z M0.0205368885,5.5 L1.02468762,5.5 C1.00835972,5.66445312 1,5.8312503 1,6 C1,6.1687497 1.00835972,6.33554688 1.02468762,6.5 L0.0205368885,6.5 C0.00693566443,6.33513331 -9.95062878e-13,6.16837661 -9.95093808e-13,6 C-9.95124738e-13,5.83162339 0.00693566443,5.66486669 0.0205368885,5.5 Z M1.07163304,2.57725574 L1.94125309,3.07933111 C1.84488615,3.21358774 1.75424783,3.35385847 1.66987298,3.5 C1.58549813,3.64614153 1.50933927,3.79477198 1.44125309,3.94535651 L0.571633044,3.44328115 C0.642287382,3.2937018 0.719659271,3.14581842 0.803847577,3 C0.888035884,2.85418158 0.977420696,2.71323387 1.07163304,2.57725574 Z M3.44328115,0.571633044 L3.94535651,1.44125309 C3.79477198,1.50933927 3.64614153,1.58549813 3.5,1.66987298 C3.35385847,1.75424783 3.21358774,1.84488615 3.07933111,1.94125309 L2.57725574,1.07163304 C2.71323387,0.977420696 2.85418158,0.888035884 3,0.803847577 C3.14581842,0.719659271 3.2937018,0.642287382 3.44328115,0.571633044 Z M6.5,0.0205368885 L6.5,7 L5.5,7 L5.5,0.0205368885 C5.66486669,0.00693566443 5.83162339,5.01e-14 6,5.01e-14 C6.16837661,5.01e-14 6.33513331,0.00693566443 6.5,0.0205368885 Z',
@ -536,10 +543,8 @@ export const icons = {
ratio: 1,
},
timer50: {
path: [
'M8.49999998,1.66987313 L8.99999998,0.80384773 C10.3298113,1.57161469 11.3667294,2.84668755 11.7955548,4.4470858 C12.6532057,7.64788242 10.7537107,10.9379042 7.5529141,11.795555 C4.35211748,12.6532059 1.06209574,10.753711 0.204444873,7.55291434 C-0.27253249,5.77281059 0.103264647,3.96510985 1.08141192,2.56355986 L1.94944135,3.0683506 L1.94144625,3.07944279 L6.25000012,5.56698754 L5.75000012,6.43301294 L1.44144624,3.9454682 C0.98322086,4.96059039 0.85962347,6.13437085 1.1703707,7.29409529 C1.88507976,9.96142581 4.62676454,11.5443383 7.29409506,10.8296292 C9.96142557,10.1149201 11.544338,7.37323536 10.829629,4.70590484 C10.4722744,3.37223964 9.60817605,2.30967894 8.49999998,1.66987313 Z',
'M6.00250506,1.00000061 L6,1 C5.8312503,1 5.66445312,1.00835972 5.5,1.02468762 L5.5,0.0205368885 C5.66486669,0.00693566443 5.83162339,0 6,0 L6.00250686,5.12480482e-07 C9.31506271,0.0013544265 12,2.68712686 12,6 L11,6 C11,3.23941132 8.76277746,1.00135396 6.00250506,1.00000061 Z M3.44328115,0.571633044 L3.94535651,1.44125309 C3.79477198,1.50933927 3.64614153,1.58549813 3.5,1.66987298 C3.35385847,1.75424783 3.21358774,1.84488615 3.07933111,1.94125309 L2.57725574,1.07163304 C2.71323387,0.977420696 2.85418158,0.888035884 3,0.803847577 C3.14581842,0.719659271 3.2937018,0.642287382 3.44328115,0.571633044 Z',
],
path:
'M8.49999998,1.66987313 L8.99999998,0.80384773 C10.3298113,1.57161469 11.3667294,2.84668755 11.7955548,4.4470858 C12.6532057,7.64788242 10.7537107,10.9379042 7.5529141,11.795555 C4.35211748,12.6532059 1.06209574,10.753711 0.204444873,7.55291434 C-0.27253249,5.77281059 0.103264647,3.96510985 1.08141192,2.56355986 L1.94944135,3.0683506 L1.94144625,3.07944279 L6.25000012,5.56698754 L5.75000012,6.43301294 L1.44144624,3.9454682 C0.98322086,4.96059039 0.85962347,6.13437085 1.1703707,7.29409529 C1.88507976,9.96142581 4.62676454,11.5443383 7.29409506,10.8296292 C9.96142557,10.1149201 11.544338,7.37323536 10.829629,4.70590484 C10.4722744,3.37223964 9.60817605,2.30967894 8.49999998,1.66987313 Z M6.00250506,1.00000061 L6,1 C5.8312503,1 5.66445312,1.00835972 5.5,1.02468762 L5.5,0.0205368885 C5.66486669,0.00693566443 5.83162339,0 6,0 L6.00250686,5.12480482e-07 C9.31506271,0.0013544265 12,2.68712686 12,6 L11,6 C11,3.23941132 8.76277746,1.00135396 6.00250506,1.00000061 Z M3.44328115,0.571633044 L3.94535651,1.44125309 C3.79477198,1.50933927 3.64614153,1.58549813 3.5,1.66987298 C3.35385847,1.75424783 3.21358774,1.84488615 3.07933111,1.94125309 L2.57725574,1.07163304 C2.71323387,0.977420696 2.85418158,0.888035884 3,0.803847577 C3.14581842,0.719659271 3.2937018,0.642287382 3.44328115,0.571633044 Z',
viewBox: '0 0 12 12',
ratio: 1,
},

@ -1,3 +1,4 @@
import { isNil } from 'lodash';
import {
ConversationNotificationSettingType,
ConversationTypeEnum,
@ -487,6 +488,37 @@ export async function uploadOurAvatar(newAvatarDecrypted?: ArrayBuffer) {
};
}
/**
* This function can be used for clearing our avatar.
*/
export async function clearOurAvatar(commit: boolean = true) {
const ourConvo = getConversationController().get(UserUtils.getOurPubKeyStrFromCache());
if (!ourConvo) {
window.log.warn('ourConvo not found... This is not a valid case');
return;
}
// return early if no change are needed at all
if (
isNil(ourConvo.get('avatarPointer')) &&
isNil(ourConvo.get('avatarInProfile')) &&
isNil(ourConvo.get('profileKey'))
) {
return;
}
ourConvo.set('avatarPointer', undefined);
ourConvo.set('avatarInProfile', undefined);
ourConvo.set('profileKey', undefined);
await setLastProfileUpdateTimestamp(Date.now());
if (commit) {
await ourConvo.commit();
await SyncUtils.forceSyncConfigurationNowIfNeeded(true);
}
}
export async function replyToMessage(messageId: string) {
const quotedMessageModel = await Data.getMessageById(messageId);
if (!quotedMessageModel) {

@ -1,6 +1,7 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { SessionConfirmDialogProps } from '../../components/dialog/SessionConfirm';
import { PasswordAction } from '../../components/dialog/SessionPasswordDialog';
import { EditProfilePictureModalProps } from '../../components/dialog/EditProfilePictureModal';
import { Noop } from '../../types/Util';
export type BanType = 'ban' | 'unban';
@ -36,6 +37,8 @@ export type ReactModalsState = {
messageId: string;
} | null;
export type EditProfilePictureModalState = EditProfilePictureModalProps | null;
export type ModalState = {
confirmModal: ConfirmModalState;
inviteContactModal: InviteContactModalState;
@ -54,6 +57,7 @@ export type ModalState = {
deleteAccountModal: DeleteAccountModalState;
reactListModalState: ReactModalsState;
reactClearAllModalState: ReactModalsState;
editProfilePictureModalState: EditProfilePictureModalState;
};
export const initialModalState: ModalState = {
@ -74,6 +78,7 @@ export const initialModalState: ModalState = {
deleteAccountModal: null,
reactListModalState: null,
reactClearAllModalState: null,
editProfilePictureModalState: null,
};
const ModalSlice = createSlice({
@ -131,6 +136,9 @@ const ModalSlice = createSlice({
updateReactClearAllModal(state, action: PayloadAction<ReactModalsState>) {
return { ...state, reactClearAllModalState: action.payload };
},
updateEditProfilePictureModel(state, action: PayloadAction<EditProfilePictureModalState>) {
return { ...state, editProfilePictureModalState: action.payload };
},
},
});
@ -153,5 +161,6 @@ export const {
updateBanOrUnbanUserModal,
updateReactListModal,
updateReactClearAllModal,
updateEditProfilePictureModel,
} = actions;
export const modalReducer = reducer;

@ -9,6 +9,7 @@ import {
ConfirmModalState,
DeleteAccountModalState,
EditProfileModalState,
EditProfilePictureModalState,
InviteContactModalState,
ModalState,
OnionPathModalState,
@ -109,3 +110,8 @@ export const getReactClearAllDialog = createSelector(
getModal,
(state: ModalState): ReactModalsState => state.reactClearAllModalState
);
export const getEditProfilePictureModalState = createSelector(
getModal,
(state: ModalState): EditProfilePictureModalState => state.editProfilePictureModalState
);

@ -1,512 +1,513 @@
export type LocalizerKeys =
| 'copyErrorAndQuit'
| 'unknown'
| 'databaseError'
| 'mainMenuFile'
| 'mainMenuEdit'
| 'mainMenuView'
| 'mainMenuWindow'
| 'mainMenuHelp'
| 'ByUsingThisService...'
| 'about'
| 'accept'
| 'activeMembers'
| 'add'
| 'addACaption'
| 'addAsModerator'
| 'addModerators'
| 'addingContacts'
| 'allUsersAreRandomly...'
| 'anonymous'
| 'answeredACall'
| 'appMenuHide'
| 'appMenuHideOthers'
| 'appMenuUnhide'
| 'appMenuQuit'
| 'editMenuUndo'
| 'editMenuRedo'
| 'editMenuCut'
| 'editMenuCopy'
| 'editMenuPaste'
| 'editMenuDeleteContact'
| 'editMenuDeleteGroup'
| 'editMenuSelectAll'
| 'windowMenuClose'
| 'windowMenuMinimize'
| 'windowMenuZoom'
| 'viewMenuResetZoom'
| 'viewMenuZoomIn'
| 'viewMenuZoomOut'
| 'viewMenuToggleFullScreen'
| 'viewMenuToggleDevTools'
| 'contextMenuNoSuggestions'
| 'openGroupInvitation'
| 'joinOpenGroupAfterInvitationConfirmationTitle'
| 'joinOpenGroupAfterInvitationConfirmationDesc'
| 'couldntFindServerMatching'
| 'enterSessionIDOrONSName'
| 'startNewConversationBy...'
| 'loading'
| 'done'
| 'youLeftTheGroup'
| 'youGotKickedFromGroup'
| 'unreadMessages'
| 'debugLogExplanation'
| 'reportIssue'
| 'markAllAsRead'
| 'incomingError'
| 'media'
| 'mediaEmptyState'
| 'document'
| 'documents'
| 'documentsEmptyState'
| 'today'
| 'yesterday'
| 'thisWeek'
| 'thisMonth'
| 'voiceMessage'
| 'stagedPreviewThumbnail'
| 'previewThumbnail'
| 'stagedImageAttachment'
| 'oneNonImageAtATimeToast'
| 'cannotMixImageAndNonImageAttachments'
| 'maximumAttachments'
| 'fileSizeWarning'
| 'unableToLoadAttachment'
| 'offline'
| 'debugLog'
| 'showDebugLog'
| 'shareBugDetails'
| 'goToReleaseNotes'
| 'goToSupportPage'
| 'about'
| 'show'
| 'sessionMessenger'
| 'noSearchResults'
| 'conversationsHeader'
| 'contactsHeader'
| 'messagesHeader'
| 'searchMessagesHeader'
| 'settingsHeader'
| 'typingAlt'
| 'contactAvatarAlt'
| 'downloadAttachment'
| 'replyToMessage'
| 'replyingToMessage'
| 'originalMessageNotFound'
| 'you'
| 'audioPermissionNeededTitle'
| 'audioPermissionNeeded'
| 'image'
| 'appMenuUnhide'
| 'appearanceSettingsTitle'
| 'areYouSureClearDevice'
| 'areYouSureDeleteDeviceOnly'
| 'areYouSureDeleteEntireAccount'
| 'audio'
| 'video'
| 'photo'
| 'audioMessageAutoplayDescription'
| 'audioMessageAutoplayTitle'
| 'audioNotificationsSettingsTitle'
| 'audioPermissionNeeded'
| 'audioPermissionNeededTitle'
| 'autoUpdateDownloadButtonLabel'
| 'autoUpdateDownloadInstructions'
| 'autoUpdateDownloadedMessage'
| 'autoUpdateLaterButtonLabel'
| 'autoUpdateNewVersionInstructions'
| 'autoUpdateNewVersionMessage'
| 'autoUpdateNewVersionTitle'
| 'autoUpdateRestartButtonLabel'
| 'autoUpdateSettingDescription'
| 'autoUpdateSettingTitle'
| 'banUser'
| 'banUserAndDeleteAll'
| 'beginYourSession'
| 'blindedMsgReqsSettingDesc'
| 'blindedMsgReqsSettingTitle'
| 'block'
| 'blocked'
| 'blockedSettingsTitle'
| 'callMediaPermissionsDescription'
| 'callMediaPermissionsDialogContent'
| 'callMediaPermissionsDialogTitle'
| 'callMediaPermissionsTitle'
| 'callMissed'
| 'callMissedCausePermission'
| 'callMissedNotApproved'
| 'callMissedTitle'
| 'cameraPermissionNeeded'
| 'cameraPermissionNeededTitle'
| 'cancel'
| 'cannotMixImageAndNonImageAttachments'
| 'cannotRemoveCreatorFromGroup'
| 'cannotRemoveCreatorFromGroupDesc'
| 'cannotUpdate'
| 'cannotUpdateDetail'
| 'ok'
| 'cancel'
| 'changeAccountPasswordDescription'
| 'changeAccountPasswordTitle'
| 'changeNickname'
| 'changeNicknameMessage'
| 'changePassword'
| 'changePasswordInvalid'
| 'changePasswordTitle'
| 'changePasswordToastDescription'
| 'chooseAnAction'
| 'classicDarkThemeTitle'
| 'classicLightThemeTitle'
| 'clear'
| 'clearAll'
| 'clearAllConfirmationBody'
| 'clearAllConfirmationTitle'
| 'clearAllData'
| 'clearAllReactions'
| 'clearDataSettingsTitle'
| 'clearDevice'
| 'clearNickname'
| 'clickToTrustContact'
| 'close'
| 'closedGroupInviteFailMessage'
| 'closedGroupInviteFailMessagePlural'
| 'closedGroupInviteFailTitle'
| 'closedGroupInviteFailTitlePlural'
| 'closedGroupInviteOkText'
| 'closedGroupInviteSuccessMessage'
| 'closedGroupInviteSuccessTitle'
| 'closedGroupInviteSuccessTitlePlural'
| 'closedGroupMaxSize'
| 'confirmNewPassword'
| 'confirmPassword'
| 'connectToServerFail'
| 'connectToServerSuccess'
| 'connectingToServer'
| 'contactAvatarAlt'
| 'contactsHeader'
| 'contextMenuNoSuggestions'
| 'continue'
| 'error'
| 'continueYourSession'
| 'conversationsHeader'
| 'conversationsSettingsTitle'
| 'copiedToClipboard'
| 'copyErrorAndQuit'
| 'copyMessage'
| 'copyOpenGroupURL'
| 'copySessionID'
| 'couldntFindServerMatching'
| 'create'
| 'createAccount'
| 'createClosedGroupNamePrompt'
| 'createClosedGroupPlaceholder'
| 'createConversationNewContact'
| 'createConversationNewGroup'
| 'createGroup'
| 'createPassword'
| 'createSessionID'
| 'databaseError'
| 'debugLog'
| 'debugLogExplanation'
| 'decline'
| 'declineRequestMessage'
| 'delete'
| 'messageDeletionForbidden'
| 'deleteJustForMe'
| 'deleteAccountFromLogin'
| 'deleteAccountWarning'
| 'deleteContactConfirmation'
| 'deleteConversation'
| 'deleteConversationConfirmation'
| 'deleteForEveryone'
| 'deleteMessagesQuestion'
| 'deleteJustForMe'
| 'deleteMessageQuestion'
| 'deleteMessages'
| 'deleteConversation'
| 'deleteMessagesQuestion'
| 'deleted'
| 'messageDeletedPlaceholder'
| 'from'
| 'to'
| 'sent'
| 'received'
| 'sendMessage'
| 'groupMembers'
| 'moreInformation'
| 'resend'
| 'deleteConversationConfirmation'
| 'clear'
| 'clearAllData'
| 'deleteAccountWarning'
| 'deleteAccountFromLogin'
| 'deleteContactConfirmation'
| 'quoteThumbnailAlt'
| 'imageAttachmentAlt'
| 'videoAttachmentAlt'
| 'lightboxImageAlt'
| 'imageCaptionIconAlt'
| 'addACaption'
| 'copySessionID'
| 'copyOpenGroupURL'
| 'save'
| 'saveLogToDesktop'
| 'saved'
| 'tookAScreenshot'
| 'savedTheFile'
| 'linkPreviewsTitle'
| 'linkPreviewDescription'
| 'linkPreviewsConfirmMessage'
| 'mediaPermissionsTitle'
| 'mediaPermissionsDescription'
| 'spellCheckTitle'
| 'spellCheckDescription'
| 'spellCheckDirty'
| 'readReceiptSettingDescription'
| 'readReceiptSettingTitle'
| 'typingIndicatorsSettingDescription'
| 'typingIndicatorsSettingTitle'
| 'zoomFactorSettingTitle'
| 'themesSettingTitle'
| 'primaryColor'
| 'primaryColorGreen'
| 'primaryColorBlue'
| 'primaryColorYellow'
| 'primaryColorPink'
| 'primaryColorPurple'
| 'primaryColorOrange'
| 'primaryColorRed'
| 'classicDarkThemeTitle'
| 'classicLightThemeTitle'
| 'oceanDarkThemeTitle'
| 'oceanLightThemeTitle'
| 'pruneSettingTitle'
| 'pruneSettingDescription'
| 'enable'
| 'keepDisabled'
| 'notificationSettingsDialog'
| 'nameAndMessage'
| 'noNameOrMessage'
| 'nameOnly'
| 'newMessage'
| 'createConversationNewContact'
| 'createConversationNewGroup'
| 'joinACommunity'
| 'chooseAnAction'
| 'newMessages'
| 'notificationMostRecentFrom'
| 'notificationFrom'
| 'notificationMostRecent'
| 'sendFailed'
| 'mediaMessage'
| 'messageBodyMissing'
| 'messageBody'
| 'unblockToSend'
| 'youChangedTheTimer'
| 'timerSetOnSync'
| 'theyChangedTheTimer'
| 'timerOption_0_seconds'
| 'timerOption_5_seconds'
| 'timerOption_10_seconds'
| 'timerOption_30_seconds'
| 'timerOption_1_minute'
| 'timerOption_5_minutes'
| 'timerOption_30_minutes'
| 'timerOption_1_hour'
| 'timerOption_6_hours'
| 'timerOption_12_hours'
| 'timerOption_1_day'
| 'timerOption_1_week'
| 'timerOption_2_weeks'
| 'destination'
| 'device'
| 'deviceOnly'
| 'dialogClearAllDataDeletionFailedDesc'
| 'dialogClearAllDataDeletionFailedMultiple'
| 'dialogClearAllDataDeletionFailedTitle'
| 'dialogClearAllDataDeletionFailedTitleQuestion'
| 'dialogClearAllDataDeletionQuestion'
| 'disabledDisappearingMessages'
| 'disappearingMessages'
| 'changeNickname'
| 'clearNickname'
| 'nicknamePlaceholder'
| 'changeNicknameMessage'
| 'timerOption_0_seconds_abbreviated'
| 'timerOption_5_seconds_abbreviated'
| 'timerOption_10_seconds_abbreviated'
| 'timerOption_30_seconds_abbreviated'
| 'timerOption_1_minute_abbreviated'
| 'timerOption_5_minutes_abbreviated'
| 'timerOption_30_minutes_abbreviated'
| 'timerOption_1_hour_abbreviated'
| 'timerOption_6_hours_abbreviated'
| 'timerOption_12_hours_abbreviated'
| 'timerOption_1_day_abbreviated'
| 'timerOption_1_week_abbreviated'
| 'timerOption_2_weeks_abbreviated'
| 'disappearingMessagesDisabled'
| 'disabledDisappearingMessages'
| 'youDisabledDisappearingMessages'
| 'timerSetTo'
| 'noteToSelf'
| 'hideMenuBarTitle'
| 'displayName'
| 'displayNameEmpty'
| 'displayNameTooLong'
| 'document'
| 'documents'
| 'documentsEmptyState'
| 'done'
| 'downloadAttachment'
| 'editGroup'
| 'editGroupName'
| 'editMenuCopy'
| 'editMenuCut'
| 'editMenuDeleteContact'
| 'editMenuDeleteGroup'
| 'editMenuPaste'
| 'editMenuRedo'
| 'editMenuSelectAll'
| 'editMenuUndo'
| 'editProfileModalTitle'
| 'emptyGroupNameError'
| 'enable'
| 'endCall'
| 'enterAnOpenGroupURL'
| 'enterDisplayName'
| 'enterNewPassword'
| 'enterPassword'
| 'enterRecoveryPhrase'
| 'enterSessionID'
| 'enterSessionIDOfRecipient'
| 'enterSessionIDOrONSName'
| 'entireAccount'
| 'error'
| 'establishingConnection'
| 'expandedReactionsText'
| 'failedResolveOns'
| 'failedToAddAsModerator'
| 'failedToRemoveFromModerator'
| 'faq'
| 'fileSizeWarning'
| 'from'
| 'getStarted'
| 'goToReleaseNotes'
| 'goToSupportPage'
| 'groupMembers'
| 'groupNamePlaceholder'
| 'helpSettingsTitle'
| 'helpUsTranslateSession'
| 'hideBanner'
| 'hideMenuBarDescription'
| 'startConversation'
| 'hideMenuBarTitle'
| 'hideRequestBanner'
| 'hideRequestBannerDescription'
| 'iAmSure'
| 'image'
| 'imageAttachmentAlt'
| 'imageCaptionIconAlt'
| 'incomingCallFrom'
| 'incomingError'
| 'invalidGroupNameTooLong'
| 'invalidGroupNameTooShort'
| 'invalidNumberError'
| 'failedResolveOns'
| 'autoUpdateSettingTitle'
| 'autoUpdateSettingDescription'
| 'autoUpdateNewVersionTitle'
| 'autoUpdateNewVersionMessage'
| 'autoUpdateNewVersionInstructions'
| 'autoUpdateRestartButtonLabel'
| 'autoUpdateLaterButtonLabel'
| 'autoUpdateDownloadButtonLabel'
| 'autoUpdateDownloadedMessage'
| 'autoUpdateDownloadInstructions'
| 'leftTheGroup'
| 'multipleLeftTheGroup'
| 'updatedTheGroup'
| 'titleIsNow'
| 'invalidOldPassword'
| 'invalidOpenGroupUrl'
| 'invalidPassword'
| 'invalidPubkeyFormat'
| 'invalidSessionId'
| 'inviteContacts'
| 'join'
| 'joinACommunity'
| 'joinOpenGroup'
| 'joinOpenGroupAfterInvitationConfirmationDesc'
| 'joinOpenGroupAfterInvitationConfirmationTitle'
| 'joinedTheGroup'
| 'multipleJoinedTheGroup'
| 'keepDisabled'
| 'kickedFromTheGroup'
| 'multipleKickedFromTheGroup'
| 'block'
| 'unblock'
| 'unblocked'
| 'blocked'
| 'blockedSettingsTitle'
| 'conversationsSettingsTitle'
| 'unbanUser'
| 'userUnbanned'
| 'userUnbanFailed'
| 'banUser'
| 'banUserAndDeleteAll'
| 'userBanned'
| 'userBanFailed'
| 'leaveGroup'
| 'learnMore'
| 'leaveAndRemoveForEveryone'
| 'leaveGroup'
| 'leaveGroupConfirmation'
| 'leaveGroupConfirmationAdmin'
| 'cannotRemoveCreatorFromGroup'
| 'cannotRemoveCreatorFromGroupDesc'
| 'noContactsForGroup'
| 'failedToAddAsModerator'
| 'failedToRemoveFromModerator'
| 'copyMessage'
| 'selectMessage'
| 'editGroup'
| 'editGroupName'
| 'updateGroupDialogTitle'
| 'showRecoveryPhrase'
| 'yourSessionID'
| 'setAccountPasswordTitle'
| 'setAccountPasswordDescription'
| 'changeAccountPasswordTitle'
| 'changeAccountPasswordDescription'
| 'removeAccountPasswordTitle'
| 'removeAccountPasswordDescription'
| 'enterPassword'
| 'confirmPassword'
| 'enterNewPassword'
| 'confirmNewPassword'
| 'showRecoveryPhrasePasswordRequest'
| 'recoveryPhraseSavePromptMain'
| 'invalidOpenGroupUrl'
| 'copiedToClipboard'
| 'passwordViewTitle'
| 'password'
| 'setPassword'
| 'changePassword'
| 'createPassword'
| 'removePassword'
| 'leftTheGroup'
| 'lightboxImageAlt'
| 'linkDevice'
| 'linkPreviewDescription'
| 'linkPreviewsConfirmMessage'
| 'linkPreviewsTitle'
| 'linkVisitWarningMessage'
| 'linkVisitWarningTitle'
| 'loading'
| 'mainMenuEdit'
| 'mainMenuFile'
| 'mainMenuHelp'
| 'mainMenuView'
| 'mainMenuWindow'
| 'markAllAsRead'
| 'markUnread'
| 'maxPasswordAttempts'
| 'typeInOldPassword'
| 'invalidOldPassword'
| 'invalidPassword'
| 'noGivenPassword'
| 'passwordsDoNotMatch'
| 'setPasswordInvalid'
| 'changePasswordInvalid'
| 'removePasswordInvalid'
| 'setPasswordTitle'
| 'changePasswordTitle'
| 'removePasswordTitle'
| 'setPasswordToastDescription'
| 'changePasswordToastDescription'
| 'removePasswordToastDescription'
| 'publicChatExists'
| 'connectToServerFail'
| 'connectingToServer'
| 'connectToServerSuccess'
| 'setPasswordFail'
| 'passwordLengthError'
| 'passwordTypeError'
| 'passwordCharacterError'
| 'remove'
| 'invalidSessionId'
| 'invalidPubkeyFormat'
| 'emptyGroupNameError'
| 'editProfileModalTitle'
| 'groupNamePlaceholder'
| 'inviteContacts'
| 'addModerators'
| 'removeModerators'
| 'addAsModerator'
| 'removeFromModerators'
| 'add'
| 'addingContacts'
| 'noContactsToAdd'
| 'noMembersInThisGroup'
| 'noModeratorsToRemove'
| 'onlyAdminCanRemoveMembers'
| 'onlyAdminCanRemoveMembersDesc'
| 'createAccount'
| 'startInTrayTitle'
| 'startInTrayDescription'
| 'yourUniqueSessionID'
| 'allUsersAreRandomly...'
| 'getStarted'
| 'createSessionID'
| 'recoveryPhrase'
| 'enterRecoveryPhrase'
| 'displayName'
| 'anonymous'
| 'removeResidueMembers'
| 'enterDisplayName'
| 'continueYourSession'
| 'linkDevice'
| 'restoreUsingRecoveryPhrase'
| 'or'
| 'ByUsingThisService...'
| 'beginYourSession'
| 'welcomeToYourSession'
| 'searchFor...'
| 'searchForContactsOnly'
| 'enterSessionID'
| 'enterSessionIDOfRecipient'
| 'message'
| 'appearanceSettingsTitle'
| 'privacySettingsTitle'
| 'notificationsSettingsTitle'
| 'audioNotificationsSettingsTitle'
| 'notificationsSettingsContent'
| 'notificationPreview'
| 'recoveryPhraseEmpty'
| 'displayNameEmpty'
| 'displayNameTooLong'
| 'maximumAttachments'
| 'media'
| 'mediaEmptyState'
| 'mediaMessage'
| 'mediaPermissionsDescription'
| 'mediaPermissionsTitle'
| 'members'
| 'activeMembers'
| 'join'
| 'joinOpenGroup'
| 'createGroup'
| 'create'
| 'createClosedGroupNamePrompt'
| 'createClosedGroupPlaceholder'
| 'openGroupURL'
| 'enterAnOpenGroupURL'
| 'message'
| 'messageBody'
| 'messageBodyMissing'
| 'messageDeletedPlaceholder'
| 'messageDeletionForbidden'
| 'messageRequestAccepted'
| 'messageRequestAcceptedOurs'
| 'messageRequestAcceptedOursNoName'
| 'messageRequestPending'
| 'messageRequests'
| 'messagesHeader'
| 'moreInformation'
| 'multipleJoinedTheGroup'
| 'multipleKickedFromTheGroup'
| 'multipleLeftTheGroup'
| 'mustBeApproved'
| 'nameAndMessage'
| 'nameOnly'
| 'newMessage'
| 'newMessages'
| 'next'
| 'invalidGroupNameTooShort'
| 'invalidGroupNameTooLong'
| 'pickClosedGroupMember'
| 'closedGroupMaxSize'
| 'nicknamePlaceholder'
| 'noAudioInputFound'
| 'noAudioOutputFound'
| 'noBlockedContacts'
| 'userAddedToModerators'
| 'userRemovedFromModerators'
| 'orJoinOneOfThese'
| 'helpUsTranslateSession'
| 'closedGroupInviteFailTitle'
| 'closedGroupInviteFailTitlePlural'
| 'closedGroupInviteFailMessage'
| 'closedGroupInviteFailMessagePlural'
| 'closedGroupInviteOkText'
| 'closedGroupInviteSuccessTitlePlural'
| 'closedGroupInviteSuccessTitle'
| 'closedGroupInviteSuccessMessage'
| 'noCameraFound'
| 'noContactsForGroup'
| 'noContactsToAdd'
| 'noGivenPassword'
| 'noMediaUntilApproved'
| 'noMembersInThisGroup'
| 'noMessageRequestsPending'
| 'noMessagesInBlindedDisabledMsgRequests'
| 'noMessagesInEverythingElse'
| 'noMessagesInNoteToSelf'
| 'noMessagesInReadOnly'
| 'noModeratorsToRemove'
| 'noNameOrMessage'
| 'noSearchResults'
| 'noteToSelf'
| 'notificationForConvo'
| 'notificationForConvo_all'
| 'notificationForConvo_disabled'
| 'notificationForConvo_mentions_only'
| 'onionPathIndicatorTitle'
| 'notificationFrom'
| 'notificationMostRecent'
| 'notificationMostRecentFrom'
| 'notificationPreview'
| 'notificationSettingsDialog'
| 'notificationSubtitle'
| 'notificationsSettingsContent'
| 'notificationsSettingsTitle'
| 'oceanDarkThemeTitle'
| 'oceanLightThemeTitle'
| 'offline'
| 'ok'
| 'oneNonImageAtATimeToast'
| 'onionPathIndicatorDescription'
| 'unknownCountry'
| 'device'
| 'destination'
| 'learnMore'
| 'linkVisitWarningTitle'
| 'linkVisitWarningMessage'
| 'onionPathIndicatorTitle'
| 'onlyAdminCanRemoveMembers'
| 'onlyAdminCanRemoveMembersDesc'
| 'open'
| 'audioMessageAutoplayTitle'
| 'audioMessageAutoplayDescription'
| 'clickToTrustContact'
| 'trustThisContactDialogTitle'
| 'trustThisContactDialogDescription'
| 'openGroupInvitation'
| 'openGroupURL'
| 'openMessageRequestInbox'
| 'openMessageRequestInboxDescription'
| 'or'
| 'orJoinOneOfThese'
| 'originalMessageNotFound'
| 'otherPlural'
| 'otherSingular'
| 'password'
| 'passwordCharacterError'
| 'passwordLengthError'
| 'passwordTypeError'
| 'passwordViewTitle'
| 'passwordsDoNotMatch'
| 'permissionsSettingsTitle'
| 'photo'
| 'pickClosedGroupMember'
| 'pinConversation'
| 'unpinConversation'
| 'markUnread'
| 'showUserDetails'
| 'sendRecoveryPhraseTitle'
| 'sendRecoveryPhraseMessage'
| 'dialogClearAllDataDeletionFailedTitle'
| 'dialogClearAllDataDeletionFailedDesc'
| 'dialogClearAllDataDeletionFailedTitleQuestion'
| 'dialogClearAllDataDeletionFailedMultiple'
| 'dialogClearAllDataDeletionQuestion'
| 'clearDevice'
| 'tryAgain'
| 'areYouSureClearDevice'
| 'deviceOnly'
| 'entireAccount'
| 'areYouSureDeleteDeviceOnly'
| 'areYouSureDeleteEntireAccount'
| 'iAmSure'
| 'recoveryPhraseSecureTitle'
| 'recoveryPhraseRevealMessage'
| 'pleaseWaitOpenAndOptimizeDb'
| 'previewThumbnail'
| 'primaryColor'
| 'primaryColorBlue'
| 'primaryColorGreen'
| 'primaryColorOrange'
| 'primaryColorPink'
| 'primaryColorPurple'
| 'primaryColorRed'
| 'primaryColorYellow'
| 'privacySettingsTitle'
| 'pruneSettingDescription'
| 'pruneSettingTitle'
| 'publicChatExists'
| 'quoteThumbnailAlt'
| 'rateLimitReactMessage'
| 'reactionListCountPlural'
| 'reactionListCountSingular'
| 'reactionNotification'
| 'reactionPopup'
| 'reactionPopupMany'
| 'reactionPopupOne'
| 'reactionPopupThree'
| 'reactionPopupTwo'
| 'readReceiptSettingDescription'
| 'readReceiptSettingTitle'
| 'received'
| 'recoveryPhrase'
| 'recoveryPhraseEmpty'
| 'recoveryPhraseRevealButtonText'
| 'notificationSubtitle'
| 'surveyTitle'
| 'faq'
| 'support'
| 'clearAll'
| 'clearDataSettingsTitle'
| 'messageRequests'
| 'blindedMsgReqsSettingTitle'
| 'blindedMsgReqsSettingDesc'
| 'requestsSubtitle'
| 'recoveryPhraseRevealMessage'
| 'recoveryPhraseSavePromptMain'
| 'recoveryPhraseSecureTitle'
| 'remove'
| 'removeAccountPasswordDescription'
| 'removeAccountPasswordTitle'
| 'removeFromModerators'
| 'removeModerators'
| 'removePassword'
| 'removePasswordInvalid'
| 'removePasswordTitle'
| 'removePasswordToastDescription'
| 'removeResidueMembers'
| 'replyToMessage'
| 'replyingToMessage'
| 'reportIssue'
| 'requestsPlaceholder'
| 'hideRequestBannerDescription'
| 'incomingCallFrom'
| 'requestsSubtitle'
| 'resend'
| 'respondingToRequestWarning'
| 'restoreUsingRecoveryPhrase'
| 'ringing'
| 'establishingConnection'
| 'accept'
| 'decline'
| 'endCall'
| 'permissionsSettingsTitle'
| 'helpSettingsTitle'
| 'cameraPermissionNeededTitle'
| 'cameraPermissionNeeded'
| 'unableToCall'
| 'unableToCallTitle'
| 'callMissed'
| 'callMissedTitle'
| 'noCameraFound'
| 'noAudioInputFound'
| 'noAudioOutputFound'
| 'callMediaPermissionsTitle'
| 'callMissedCausePermission'
| 'callMissedNotApproved'
| 'callMediaPermissionsDescription'
| 'callMediaPermissionsDialogContent'
| 'callMediaPermissionsDialogTitle'
| 'save'
| 'saveLogToDesktop'
| 'saved'
| 'savedTheFile'
| 'searchFor...'
| 'searchForContactsOnly'
| 'searchMessagesHeader'
| 'selectMessage'
| 'sendFailed'
| 'sendMessage'
| 'sendRecoveryPhraseMessage'
| 'sendRecoveryPhraseTitle'
| 'sent'
| 'sessionMessenger'
| 'setAccountPasswordDescription'
| 'setAccountPasswordTitle'
| 'setDisplayPicture'
| 'setPassword'
| 'setPasswordFail'
| 'setPasswordInvalid'
| 'setPasswordTitle'
| 'setPasswordToastDescription'
| 'settingsHeader'
| 'shareBugDetails'
| 'show'
| 'showDebugLog'
| 'showRecoveryPhrase'
| 'showRecoveryPhrasePasswordRequest'
| 'showUserDetails'
| 'someOfYourDeviceUseOutdatedVersion'
| 'spellCheckDescription'
| 'spellCheckDirty'
| 'spellCheckTitle'
| 'stagedImageAttachment'
| 'stagedPreviewThumbnail'
| 'startConversation'
| 'startInTrayDescription'
| 'startInTrayTitle'
| 'startNewConversationBy...'
| 'startedACall'
| 'answeredACall'
| 'support'
| 'surveyTitle'
| 'themesSettingTitle'
| 'theyChangedTheTimer'
| 'thisMonth'
| 'thisWeek'
| 'timerOption_0_seconds'
| 'timerOption_0_seconds_abbreviated'
| 'timerOption_10_seconds'
| 'timerOption_10_seconds_abbreviated'
| 'timerOption_12_hours'
| 'timerOption_12_hours_abbreviated'
| 'timerOption_1_day'
| 'timerOption_1_day_abbreviated'
| 'timerOption_1_hour'
| 'timerOption_1_hour_abbreviated'
| 'timerOption_1_minute'
| 'timerOption_1_minute_abbreviated'
| 'timerOption_1_week'
| 'timerOption_1_week_abbreviated'
| 'timerOption_2_weeks'
| 'timerOption_2_weeks_abbreviated'
| 'timerOption_30_minutes'
| 'timerOption_30_minutes_abbreviated'
| 'timerOption_30_seconds'
| 'timerOption_30_seconds_abbreviated'
| 'timerOption_5_minutes'
| 'timerOption_5_minutes_abbreviated'
| 'timerOption_5_seconds'
| 'timerOption_5_seconds_abbreviated'
| 'timerOption_6_hours'
| 'timerOption_6_hours_abbreviated'
| 'timerSetOnSync'
| 'timerSetTo'
| 'titleIsNow'
| 'to'
| 'today'
| 'tookAScreenshot'
| 'trimDatabase'
| 'trimDatabaseDescription'
| 'trimDatabaseConfirmationBody'
| 'pleaseWaitOpenAndOptimizeDb'
| 'messageRequestPending'
| 'messageRequestAccepted'
| 'messageRequestAcceptedOurs'
| 'messageRequestAcceptedOursNoName'
| 'declineRequestMessage'
| 'respondingToRequestWarning'
| 'hideRequestBanner'
| 'openMessageRequestInbox'
| 'noMessageRequestsPending'
| 'noMediaUntilApproved'
| 'mustBeApproved'
| 'trimDatabaseDescription'
| 'trustThisContactDialogDescription'
| 'trustThisContactDialogTitle'
| 'tryAgain'
| 'typeInOldPassword'
| 'typingAlt'
| 'typingIndicatorsSettingDescription'
| 'typingIndicatorsSettingTitle'
| 'unableToCall'
| 'unableToCallTitle'
| 'unableToLoadAttachment'
| 'unbanUser'
| 'unblock'
| 'unblockToSend'
| 'unblocked'
| 'unknown'
| 'unknownCountry'
| 'unpinConversation'
| 'unreadMessages'
| 'updateGroupDialogTitle'
| 'updatedTheGroup'
| 'userAddedToModerators'
| 'userBanFailed'
| 'userBanned'
| 'userRemovedFromModerators'
| 'userUnbanFailed'
| 'userUnbanned'
| 'video'
| 'videoAttachmentAlt'
| 'viewMenuResetZoom'
| 'viewMenuToggleDevTools'
| 'viewMenuToggleFullScreen'
| 'viewMenuZoomIn'
| 'viewMenuZoomOut'
| 'voiceMessage'
| 'welcomeToYourSession'
| 'windowMenuClose'
| 'windowMenuMinimize'
| 'windowMenuZoom'
| 'yesterday'
| 'you'
| 'youChangedTheTimer'
| 'youDisabledDisappearingMessages'
| 'youGotKickedFromGroup'
| 'youHaveANewFriendRequest'
| 'clearAllConfirmationTitle'
| 'clearAllConfirmationBody'
| 'noMessagesInReadOnly'
| 'noMessagesInBlindedDisabledMsgRequests'
| 'noMessagesInNoteToSelf'
| 'noMessagesInEverythingElse'
| 'hideBanner'
| 'someOfYourDeviceUseOutdatedVersion'
| 'openMessageRequestInboxDescription'
| 'clearAllReactions'
| 'expandedReactionsText'
| 'reactionNotification'
| 'rateLimitReactMessage'
| 'otherSingular'
| 'otherPlural'
| 'reactionPopup'
| 'reactionPopupOne'
| 'reactionPopupTwo'
| 'reactionPopupThree'
| 'reactionPopupMany'
| 'reactionListCountSingular'
| 'reactionListCountPlural';
| 'youLeftTheGroup'
| 'yourSessionID'
| 'yourUniqueSessionID'
| 'zoomFactorSettingTitle';

Loading…
Cancel
Save