Merge remote-tracking branch 'yougotwill/userconfig_disappearingmessage' into closed-group-chunk2

pull/2963/head
Audric Ackermann 1 year ago
commit 8b7ce7f3be

@ -246,8 +246,11 @@
"nicknamePlaceholder": "New Nickname",
"changeNicknameMessage": "Enter a nickname for this user",
"noteToSelf": "Note to Self",
"savedMessages": "Saved Messages",
"hideMenuBarTitle": "Hide Menu Bar",
"hideMenuBarDescription": "Toggle system menu bar visibility.",
"matchThemeSystemSettingTitle": "Auto dark-mode",
"matchThemeSystemSettingDescription": "Match system settings",
"startConversation": "Start New Conversation",
"invalidNumberError": "Please check the Session ID or ONS name and try again",
"failedResolveOns": "Failed to resolve ONS name",
@ -462,6 +465,10 @@
"open": "Open",
"audioMessageAutoplayTitle": "Autoplay Audio Messages",
"audioMessageAutoplayDescription": "Autoplay consecutive audio messages.",
"enterKeySettingTitle": "Enter Key",
"enterKeySettingDescription": "Function of the enter key when typing in a conversation.",
"enterSendNewMessageDescription": "ENTER sends a message, SHIFT + ENTER starts a new line",
"enterNewLineDescription": "SHIFT + ENTER sends a message, ENTER starts a new line",
"clickToTrustContact": "Click to download media",
"trustThisContactDialogTitle": "Trust $name$?",
"trustThisContactDialogDescription": "Are you sure you want to download media sent by $name$?",

@ -2,7 +2,7 @@
"name": "session-desktop",
"productName": "Session",
"description": "Private messaging from your desktop",
"version": "1.11.4",
"version": "1.11.5",
"license": "GPL-3.0",
"author": {
"name": "Oxen Labs",

@ -811,8 +811,8 @@
font-size: 20px;
font-weight: normal;
letter-spacing: 0;
text-align: center;
background-color: rgba(0, 0, 0, 0.7);
}
.module-image__close-button {

@ -141,12 +141,12 @@ const GroupStatusText = ({ groupPk, pubkey }: { pubkey: PubkeyType; groupPk: Gro
const statusText = groupPromotionFailed
? window.i18n('promotionFailed')
: groupInviteFailed
? window.i18n('inviteFailed')
: groupInvitePending
? window.i18n('invitePending')
: groupPromotionPending
? window.i18n('promotionPending')
: null;
? window.i18n('inviteFailed')
: groupInvitePending
? window.i18n('invitePending')
: groupPromotionPending
? window.i18n('promotionPending')
: null;
if (!statusText) {
return null;

@ -82,8 +82,8 @@ const StyledButton = styled.div<{
props.buttonType === SessionButtonType.Solid
? 'background-color: var(--button-solid-disabled-color)'
: props.buttonType === SessionButtonType.Outline
? 'border: 1px solid var(--button-outline-disabled-color)'
: ''};
? 'border: 1px solid var(--button-outline-disabled-color)'
: ''};
color: ${props =>
props.buttonType === SessionButtonType.Solid
? 'var(--button-solid-text-color)'

@ -18,8 +18,8 @@ const StyledInput = styled.input<{
props.disabled
? 'var(--disabled-color)'
: props.selectedColor
? props.selectedColor
: 'var(--primary-color)'};
? props.selectedColor
: 'var(--primary-color)'};
}
`;
@ -116,8 +116,8 @@ const StyledLabelOutlineSelected = styled(StyledLabel)<{ selectedColor: string }
props.disabled
? 'var(--disabled-color)'
: props.selectedColor
? props.selectedColor
: 'var(--primary-color)'};
? props.selectedColor
: 'var(--primary-color)'};
outline: 1px solid transparent; /* CSS variables don't work here */
}
`;

@ -8,7 +8,7 @@ interface Props {
initialItem: string;
items: Array<{ value: string; label: string }>;
group: string;
onClick: (selectedValue: string) => any;
onClick: (selectedValue: string) => void;
style?: CSSProperties;
}

@ -42,23 +42,23 @@ const SpacerStyled = styled.div<SpacerProps>`
props.size === 'xl'
? 'var(--margins-xl)'
: props.size === 'lg'
? 'var(--margins-lg)'
: props.size === 'md'
? 'var(--margins-md)'
: props.size === 'sm'
? 'var(--margins-sm)'
: 'var(--margins-xs)'};
? 'var(--margins-lg)'
: props.size === 'md'
? 'var(--margins-md)'
: props.size === 'sm'
? 'var(--margins-sm)'
: 'var(--margins-xs)'};
width: ${props =>
props.size === 'xl'
? 'var(--margins-xl)'
: props.size === 'lg'
? 'var(--margins-lg)'
: props.size === 'md'
? 'var(--margins-md)'
: props.size === 'sm'
? 'var(--margins-sm)'
: 'var(--margins-xs)'};
? 'var(--margins-lg)'
: props.size === 'md'
? 'var(--margins-md)'
: props.size === 'sm'
? 'var(--margins-sm)'
: 'var(--margins-xs)'};
`;
const Spacer = (props: SpacerProps) => {

@ -70,14 +70,14 @@ function renderSubtitleText(
return quoteText && quoteText !== ''
? quoteText
: hasAudioAttachment
? window.i18n('audio')
: isGenericFile
? window.i18n('document')
: isVideo
? window.i18n('video')
: isImage
? window.i18n('image')
: null;
? window.i18n('audio')
: isGenericFile
? window.i18n('document')
: isVideo
? window.i18n('video')
: isImage
? window.i18n('image')
: null;
}
export const SessionQuotedMessageComposition = () => {

@ -144,8 +144,8 @@ function useTextToRender(props: PropsForExpirationTimer) {
const mode = isLegacyDisappearingModeEnabled(expirationMode)
? null
: expirationMode === 'deleteAfterRead'
? window.i18n('timerModeRead')
: window.i18n('timerModeSent');
? window.i18n('timerModeRead')
: window.i18n('timerModeSent');
switch (type) {
case 'fromOther':
return disabled
@ -154,12 +154,12 @@ function useTextToRender(props: PropsForExpirationTimer) {
[contact, timespanText]
)
: mode
? window.i18n(ownSideOnly ? 'theySetTheirDisappearingMessages' : 'theyChangedTheTimer', [
contact,
timespanText,
mode,
])
: window.i18n('theyChangedTheTimerLegacy', [contact, timespanText]);
? window.i18n(ownSideOnly ? 'theySetTheirDisappearingMessages' : 'theyChangedTheTimer', [
contact,
timespanText,
mode,
])
: window.i18n('theyChangedTheTimerLegacy', [contact, timespanText]);
case 'fromMe':
case 'fromSync':
return disabled
@ -167,11 +167,11 @@ function useTextToRender(props: PropsForExpirationTimer) {
ownSideOnly ? 'youDisabledYourDisappearingMessages' : 'youDisabledDisappearingMessages'
)
: mode
? window.i18n(ownSideOnly ? 'youSetYourDisappearingMessages' : 'youChangedTheTimer', [
timespanText,
mode,
])
: window.i18n('youChangedTheTimerLegacy', [timespanText]);
? window.i18n(ownSideOnly ? 'youSetYourDisappearingMessages' : 'youChangedTheTimer', [
timespanText,
mode,
])
: window.i18n('youChangedTheTimerLegacy', [timespanText]);
default:
assertUnreachable(type, `TimerNotification: Missing case error "${type}"`);
}

@ -63,8 +63,8 @@ const StyledTypingDot = styled.div<{ index: number }>`
props.index === 0
? 'typing-animation-first'
: props.index === 1
? 'typing-animation-second'
: 'typing-animation-third'}
? 'typing-animation-second'
: 'typing-animation-third'}
1600ms ease infinite;
`;

@ -836,8 +836,19 @@ class CompositionBoxInner extends React.Component<Props, State> {
}
private async onKeyDown(event: any) {
if (event.key === 'Enter' && !event.shiftKey && !event.nativeEvent.isComposing) {
// If shift, newline. If in IME composing mode, leave it to IME. Else send message.
const isEnter = event.key === 'Enter';
const isShiftEnter = event.shiftKey && isEnter;
const isShiftSendEnabled = window.getSettingValue(SettingsKey.hasShiftSendEnabled) as boolean;
const isNotComposing = !event.nativeEvent.isComposing;
if (isShiftSendEnabled && isEnter && isNotComposing) {
event.preventDefault();
if (isShiftEnter) {
await this.onSendMessage();
} else {
this.insertNewLine();
}
} else if (isEnter && !event.shiftKey && isNotComposing) {
event.preventDefault();
await this.onSendMessage();
} else if (event.key === 'Escape' && this.state.showEmojiPanel) {
@ -849,6 +860,34 @@ class CompositionBoxInner extends React.Component<Props, State> {
}
}
private insertNewLine() {
const messageBox = this.textarea.current;
if (!messageBox) {
return;
}
const { draft } = this.state;
const { selectedConversationKey } = this.props;
if (!selectedConversationKey) {
return; // add this check to prevent undefined from being used
}
const currentSelectionStart = Number(messageBox.selectionStart);
const realSelectionStart = getSelectionBasedOnMentions(draft, currentSelectionStart);
const before = draft.slice(0, realSelectionStart);
const after = draft.slice(realSelectionStart);
const updatedDraft = `${before}\n${after}`;
this.setState({ draft: updatedDraft });
updateDraftForConversation({
conversationKey: selectedConversationKey,
draft: updatedDraft,
});
}
private async onKeyUp() {
if (!this.props.selectedConversationKey) {
throw new Error('selectedConversationKey is needed');

@ -32,8 +32,8 @@ export const MessageText = (props: Props) => {
const contents = isDeleted
? window.i18n('messageDeletedPlaceholder')
: direction === 'incoming' && status === 'error'
? window.i18n('incomingError')
: text?.trim();
? window.i18n('incomingError')
: text?.trim();
if (!contents) {
return null;

@ -69,10 +69,10 @@ function changeOfMembersV2({
sortedWithUsFirst.length === 1 && sortedWithUsFirst[0].sessionId === us
? 'You'
: sortedWithUsFirst.length === 1
? 'One'
: sortedWithUsFirst.length === 2
? 'Two'
: 'Others';
? 'One'
: sortedWithUsFirst.length === 2
? 'Two'
: 'Others';
const action =
type === 'added' ? 'Joined' : type === 'promoted' ? 'Promoted' : ('Removed' as const);

@ -111,8 +111,8 @@ const Contacts = (contacts: Array<string>, count: number) => {
reactors === 1
? 'reactionPopupOne'
: reactors === 2
? 'reactionPopupTwo'
: 'reactionPopupThree',
? 'reactionPopupTwo'
: 'reactionPopupThree',
contacts
)}{' '}
<span>{window.i18n('reactionPopup')}</span>

@ -269,10 +269,10 @@ export const OverlayRightPanelSettings = () => {
const leaveGroupString = isPublic
? window.i18n('leaveGroup')
: isKickedFromGroup
? window.i18n('youGotKickedFromGroup')
: left
? window.i18n('youLeftTheGroup')
: window.i18n('leaveGroup');
? window.i18n('youGotKickedFromGroup')
: left
? window.i18n('youLeftTheGroup')
: window.i18n('leaveGroup');
const showUpdateGroupNameButton = isGroup && weAreAdmin && !commonNoShow; // legacy groups non-admin cannot change groupname anymore
const showAddRemoveModeratorsButton = weAreAdmin && !commonNoShow && isPublic;

@ -42,19 +42,19 @@ export const DisappearingModes = (props: DisappearingModesProps) => {
mode === 'legacy'
? window.i18n('disappearingMessagesModeLegacy')
: mode === 'deleteAfterRead'
? window.i18n('disappearingMessagesModeAfterRead')
: mode === 'deleteAfterSend'
? window.i18n('disappearingMessagesModeAfterSend')
: window.i18n('disappearingMessagesModeOff');
? window.i18n('disappearingMessagesModeAfterRead')
: mode === 'deleteAfterSend'
? window.i18n('disappearingMessagesModeAfterSend')
: window.i18n('disappearingMessagesModeOff');
const subtitleI18n =
mode === 'legacy'
? window.i18n('disappearingMessagesModeLegacySubtitle')
: mode === 'deleteAfterRead'
? window.i18n('disappearingMessagesModeAfterReadSubtitle')
: mode === 'deleteAfterSend'
? window.i18n('disappearingMessagesModeAfterSendSubtitle')
: undefined;
? window.i18n('disappearingMessagesModeAfterReadSubtitle')
: mode === 'deleteAfterSend'
? window.i18n('disappearingMessagesModeAfterSendSubtitle')
: undefined;
return (
<PanelRadioButton

@ -173,8 +173,8 @@ export const OverlayDisappearingMessages = () => {
singleMode === 'deleteAfterRead'
? window.i18n('disappearingMessagesModeAfterReadSubtitle')
: singleMode === 'deleteAfterSend'
? window.i18n('disappearingMessagesModeAfterSendSubtitle')
: window.i18n('settingAppliesToYourMessages')
? window.i18n('disappearingMessagesModeAfterSendSubtitle')
: window.i18n('settingAppliesToYourMessages')
}
/>
<DisappearingModes
@ -195,8 +195,8 @@ export const OverlayDisappearingMessages = () => {
singleMode
? disappearingModeOptions[singleMode]
: modeSelected
? disappearingModeOptions[modeSelected]
: undefined
? disappearingModeOptions[modeSelected]
: undefined
}
/>
</>
@ -217,8 +217,8 @@ export const OverlayDisappearingMessages = () => {
singleMode
? disappearingModeOptions[singleMode]
: modeSelected
? disappearingModeOptions[modeSelected]
: undefined
? disappearingModeOptions[modeSelected]
: undefined
}
dataTestId={'disappear-set-button'}
>

@ -82,10 +82,10 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
passwordAction === 'change'
? 'changePassword'
: passwordAction === 'remove'
? 'removePassword'
: passwordAction === 'enter'
? 'passwordViewTitle'
: 'setPassword';
? 'removePassword'
: passwordAction === 'enter'
? 'passwordViewTitle'
: 'setPassword';
return (
<SessionWrapperModal title={window.i18n(localizedKeyAction)} onClose={this.closeDialog}>

@ -18,14 +18,14 @@ const StyledRoundedButton = styled.div<{ isFullScreen: boolean; isMuted: boolean
props.isMuted
? 'var(--call-buttons-background-disabled-color)'
: props.isFullScreen
? 'var(--call-buttons-action-background-color)'
: 'var(--call-buttons-background-color)'};
? 'var(--call-buttons-action-background-color)'
: 'var(--call-buttons-background-color)'};
color: ${props =>
props.isMuted
? 'var(--call-buttons-icon-disabled-color)'
: props.isFullScreen
? 'var(--call-buttons-action-icon-color)'
: 'var(--call-buttons-icon-color)'};
? 'var(--call-buttons-action-icon-color)'
: 'var(--call-buttons-icon-color)'};
${props => props.isFullScreen && 'opacity: 0.4;'}
&:hover {

@ -44,8 +44,9 @@ import {
import { UserSync } from '../../session/utils/job_runners/jobs/UserSyncJob';
import { forceSyncConfigurationNowIfNeeded } from '../../session/utils/sync/syncUtils';
import { isDarkTheme } from '../../state/selectors/theme';
import { ThemeStateType } from '../../themes/constants/colors';
import { ensureThemeConsistency } from '../../themes/SessionTheme';
import { switchThemeTo } from '../../themes/switchTheme';
import { getOppositeTheme } from '../../util/theme';
import { ReleasedFeatures } from '../../util/releaseFeature';
@ -63,11 +64,8 @@ const Section = (props: { type: SectionType }) => {
if (type === SectionType.Profile) {
dispatch(editProfileModal({}));
} else if (type === SectionType.ColorMode) {
const currentTheme = String(window.Events.getThemeSetting());
const newTheme = (
isDarkMode ? currentTheme.replace('dark', 'light') : currentTheme.replace('light', 'dark')
) as ThemeStateType;
const currentTheme = window.Events.getThemeSetting();
const newTheme = getOppositeTheme(currentTheme);
// We want to persist the primary color when using the color mode button
void switchThemeTo({
theme: newTheme,
@ -151,14 +149,26 @@ const cleanUpMediasInterval = DURATION.MINUTES * 60;
const fetchReleaseFromFileServerInterval = 1000 * 60; // try to fetch the latest release from the fileserver every minute
const setupTheme = async () => {
const shouldFollowSystemTheme = window.getSettingValue(SettingsKey.hasFollowSystemThemeEnabled);
const theme = window.Events.getThemeSetting();
// We don't want to reset the primary color on startup
await switchThemeTo({
const themeConfig = {
theme,
mainWindow: true,
usePrimaryColor: true,
dispatch: window?.inboxStore?.dispatch || undefined,
});
};
if (shouldFollowSystemTheme) {
// Check if system theme matches currently set theme, if not switch it and return true, if matching return false
const wasThemeSwitched = await ensureThemeConsistency();
if (!wasThemeSwitched) {
// if theme wasn't switched them set theme to default
await switchThemeTo(themeConfig);
}
return;
}
await switchThemeTo(themeConfig);
};
// Do this only if we created a new Session ID, or if we already received the initial configuration message

@ -28,8 +28,8 @@ export const UserItem = () => {
const displayName = isMe
? window.i18n('noteToSelf')
: isSearchResultsMode && hasNickname && realName
? `${realName} (${username})`
: username;
? `${realName} (${username})`
: username;
let shouldShowPubkey = false;
if (isEmpty(username) && isEmpty(displayName)) {

@ -589,8 +589,8 @@ export const NotificationForConvoMenuItem = (): JSX.Element | null => {
n === 'all' || !n
? 'notificationForConvo_all'
: n === 'disabled'
? 'notificationForConvo_disabled'
: 'notificationForConvo_mentions_only';
? 'notificationForConvo_disabled'
: 'notificationForConvo_mentions_only';
return { value: n, name: window.i18n(keyToUse) };
});

@ -3,13 +3,15 @@ import React from 'react';
import useUpdate from 'react-use/lib/useUpdate';
import { SettingsKey } from '../../../data/settings-key';
import { isHideMenuBarSupported } from '../../../types/Settings';
import { useHasFollowSystemThemeEnabled } from '../../../state/selectors/settings';
import { ensureThemeConsistency } from '../../../themes/SessionTheme';
import { SessionToggleWithDescription } from '../SessionSettingListItem';
import { SettingsThemeSwitcher } from '../SettingsThemeSwitcher';
import { ZoomingSessionSlider } from '../ZoomingSessionSlider';
export const SettingsCategoryAppearance = (props: { hasPassword: boolean | null }) => {
const forceUpdate = useUpdate();
const isFollowSystemThemeEnabled = useHasFollowSystemThemeEnabled();
if (props.hasPassword !== null) {
const isHideMenuBarActive =
@ -32,6 +34,20 @@ export const SettingsCategoryAppearance = (props: { hasPassword: boolean | null
active={isHideMenuBarActive}
/>
)}
<SessionToggleWithDescription
// eslint-disable-next-line @typescript-eslint/no-misused-promises
onClickToggle={async () => {
const toggledValue = !isFollowSystemThemeEnabled;
await window.setSettingValue(SettingsKey.hasFollowSystemThemeEnabled, toggledValue);
if (!isFollowSystemThemeEnabled) {
await ensureThemeConsistency();
}
}}
title={window.i18n('matchThemeSystemSettingTitle')}
description={window.i18n('matchThemeSystemSettingDescription')}
active={isFollowSystemThemeEnabled}
dataTestId="enable-follow-system-theme"
/>
</>
);
}

@ -6,10 +6,13 @@ import { SettingsKey } from '../../../data/settings-key';
import { ToastUtils } from '../../../session/utils';
import { toggleAudioAutoplay } from '../../../state/ducks/userConfig';
import { getAudioAutoplay } from '../../../state/selectors/userConfig';
import { SessionRadioGroup } from '../../basic/SessionRadioGroup';
import { BlockedContactsList } from '../BlockedList';
import { SessionToggleWithDescription } from '../SessionSettingListItem';
import {
SessionSettingsItemWrapper,
SessionToggleWithDescription,
} from '../SessionSettingListItem';
import { useHasEnterSendEnabled } from '../../../state/selectors/settings';
async function toggleCommunitiesPruning() {
try {
@ -81,13 +84,49 @@ const AudioMessageAutoPlaySetting = () => {
);
};
const EnterKeyFunctionSetting = () => {
const initialSetting = useHasEnterSendEnabled();
const selectedWithSettingTrue = 'enterForNewLine';
const items = [
{
label: window.i18n('enterSendNewMessageDescription'),
value: 'enterForSend',
},
{
label: window.i18n('enterNewLineDescription'),
value: selectedWithSettingTrue,
},
];
return (
<SessionSettingsItemWrapper
title={window.i18n('enterKeySettingTitle')}
description={window.i18n('enterKeySettingDescription')}
inline={false}
>
<SessionRadioGroup
initialItem={initialSetting ? 'enterForNewLine' : 'enterForSend'}
group={SettingsKey.hasShiftSendEnabled} // make sure to define this key in your SettingsKey enum
items={items}
onClick={(selectedRadioValue: string) => {
void window.setSettingValue(
SettingsKey.hasShiftSendEnabled,
selectedRadioValue === selectedWithSettingTrue
);
}}
/>
</SessionSettingsItemWrapper>
);
};
export const CategoryConversations = () => {
return (
<>
<CommunitiesPruningSetting />
<SpellCheckSetting />
<AudioMessageAutoPlaySetting />
<EnterKeyFunctionSetting />
<BlockedContactsList />
</>
);

@ -1,7 +1,7 @@
const settingsReadReceipt = 'read-receipt-setting';
const settingsTypingIndicator = 'typing-indicators-setting';
const settingsAutoUpdate = 'auto-update';
const hasShiftSendEnabled = 'hasShiftSendEnabled';
const settingsMenuBar = 'hide-menu-bar';
const settingsSpellCheck = 'spell-check';
const settingsLinkPreview = 'link-preview-setting';
@ -14,6 +14,7 @@ const someDeviceOutdatedSyncing = 'someDeviceOutdatedSyncing';
const hasSyncedInitialConfigurationItem = 'hasSyncedInitialConfigurationItem';
const lastAvatarUploadTimestamp = 'lastAvatarUploadTimestamp';
const hasLinkPreviewPopupBeenDisplayed = 'hasLinkPreviewPopupBeenDisplayed';
const hasFollowSystemThemeEnabled = 'hasFollowSystemThemeEnabled';
// user config tracking timestamps (to discard incoming messages which would make a change we reverted in the last config message we merged)
const latestUserProfileEnvelopeTimestamp = 'latestUserProfileEnvelopeTimestamp';
@ -24,6 +25,7 @@ export const SettingsKey = {
settingsReadReceipt,
settingsTypingIndicator,
settingsAutoUpdate,
hasShiftSendEnabled,
settingsMenuBar,
settingsSpellCheck,
settingsLinkPreview,
@ -39,6 +41,7 @@ export const SettingsKey = {
latestUserProfileEnvelopeTimestamp,
latestUserGroupEnvelopeTimestamp,
latestUserContactsEnvelopeTimestamp,
hasFollowSystemThemeEnabled,
} as const;
export const KNOWN_BLINDED_KEYS_ITEM = 'KNOWN_BLINDED_KEYS_ITEM';

@ -452,8 +452,8 @@ export function useQuoteAuthorName(authorId?: string): {
const authorName = isMe
? window.i18n('you')
: convoProps?.nickname || convoProps?.isPrivate
? convoProps?.displayNameInProfile
: undefined;
? convoProps?.displayNameInProfile
: undefined;
return { authorName, isMe };
}

@ -10,6 +10,7 @@ import {
dialog,
ipcMain as ipc,
Menu,
nativeTheme,
protocol as electronProtocol,
screen,
shell,
@ -1119,6 +1120,15 @@ ipc.on('set-auto-update-setting', async (_event, enabled) => {
}
});
ipc.on('get-native-theme', event => {
event.sender.send('send-native-theme', nativeTheme.shouldUseDarkColors);
});
nativeTheme.on('updated', () => {
// Inform all renderer processes of the theme change
mainWindow?.webContents.send('native-theme-update', nativeTheme.shouldUseDarkColors);
});
async function getThemeFromMainWindow() {
return new Promise(resolve => {
ipc.once('get-success-theme-setting', (_event, value) => {

@ -3,7 +3,9 @@ import _, { toPairs } from 'lodash';
import ReactDOM from 'react-dom';
import nativeEmojiData from '@emoji-mart/data';
import React from 'react';
import { ipcRenderer } from 'electron';
// eslint-disable-next-line import/no-named-default
import { default as React } from 'react';
import { isMacOS } from '../OS';
import { SessionInboxView } from '../components/SessionInboxView';
@ -22,11 +24,13 @@ import { getOurPubKeyStrFromCache } from '../session/utils/User';
import { runners } from '../session/utils/job_runners/JobRunner';
import { LibSessionUtil } from '../session/utils/libsession/libsession_utils';
import { switchPrimaryColorTo } from '../themes/switchPrimaryColor';
import { switchThemeTo } from '../themes/switchTheme';
import { BlockedNumberController } from '../util';
import { initialiseEmojiData } from '../util/emoji';
import { Notifications } from '../util/notifications';
import { Registration } from '../util/registration';
import { Storage, isSignInByLinking } from '../util/storage';
import { getOppositeTheme, isThemeMismatched } from '../util/theme';
// Globally disable drag and drop
document.body.addEventListener(
@ -109,6 +113,23 @@ function mapOldThemeToNew(theme: string) {
return theme;
}
}
// using __unused as lodash is imported using _
ipcRenderer.on('native-theme-update', (__unused, shouldUseDarkColors) => {
const shouldFollowSystemTheme = window.getSettingValue(SettingsKey.hasFollowSystemThemeEnabled);
if (shouldFollowSystemTheme) {
const theme = window.Events.getThemeSetting();
if (isThemeMismatched(theme, shouldUseDarkColors)) {
const newTheme = getOppositeTheme(theme);
void switchThemeTo({
theme: newTheme,
mainWindow: true,
usePrimaryColor: true,
dispatch: window?.inboxStore?.dispatch,
});
}
}
});
async function startJobRunners() {
// start the job runners

@ -17,6 +17,7 @@ import {
xor,
} from 'lodash';
import { v4 } from 'uuid';
import { SignalService } from '../protobuf';
import { getMessageQueue } from '../session';
import { ConvoHub } from '../session/conversations';
@ -126,7 +127,6 @@ import {
getSubscriberCountOutsideRedux,
} from '../state/selectors/sogsRoomInfo'; // decide it it makes sense to move this to a redux slice?
import { v4 } from 'uuid';
import { DisappearingMessages } from '../session/disappearing_messages';
import { DisappearingMessageConversationModeType } from '../session/disappearing_messages/types';
import { FetchMsgExpirySwarm } from '../session/utils/job_runners/jobs/FetchMsgExpirySwarmJob';
@ -2569,10 +2569,10 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
mode === 'deleteAfterRead'
? ours === 'deleteAfterRead'
: mode === 'deleteAfterSend'
? ours === 'deleteAfterSend'
: mode === 'off'
? ours === 'off'
: false;
? ours === 'deleteAfterSend'
: mode === 'off'
? ours === 'off'
: false;
return success;
}

@ -309,14 +309,8 @@ function insertLegacyGroupIntoWrapper(
) {
checkTargetMigration(version, targetVersion);
const {
priority,
id,
groupAdmins,
members,
displayNameInProfile,
lastJoinedTimestamp,
} = legacyGroup;
const { priority, id, groupAdmins, members, displayNameInProfile, lastJoinedTimestamp } =
legacyGroup;
const latestEncryptionKeyPairHex = sqlNode.getLatestClosedGroupEncryptionKeyPair(
legacyGroup.id,

@ -16,15 +16,16 @@ import { ConversationTypeEnum } from '../models/conversationAttributes';
import { getSwarmPollingInstance } from '../session/apis/snode_api';
import { GetNetworkTime } from '../session/apis/snode_api/getNetworkTime';
import { SnodeNamespaces } from '../session/apis/snode_api/namespaces';
import { DisappearingMessageUpdate } from '../session/disappearing_messages/types';
import { ClosedGroupEncryptionPairReplyMessage } from '../session/messages/outgoing/controlMessage/group/ClosedGroupEncryptionPairReplyMessage';
import { UserUtils } from '../session/utils';
import { perfEnd, perfStart } from '../session/utils/Performance';
import { ReleasedFeatures } from '../util/releaseFeature';
import { Storage } from '../util/storage';
// eslint-disable-next-line import/no-unresolved, import/extensions
import { DisappearingMessageUpdate } from '../session/disappearing_messages/types';
import { ClosedGroup, GroupDiff, GroupInfo } from '../session/group/closed-group';
import { ConfigWrapperUser } from '../webworker/workers/browser/libsession_worker_functions';
import { ClosedGroup, GroupDiff, GroupInfo } from '../session/group/closed-group';
import { IncomingMessageCache } from './cache';
import { getSettingsKeyFromLibsessionWrapper } from './configMessage';
import { ECKeyPair, HexKeyPair } from './keypairs';
@ -318,8 +319,8 @@ export async function handleNewClosedGroup(
expireTimer === 0
? 'off'
: ReleasedFeatures.isDisappearMessageV2FeatureReleasedCached()
? 'deleteAfterSend'
: 'legacy',
? 'deleteAfterSend'
: 'legacy',
providedExpireTimer: expireTimer,
providedSource: sender,
receivedAt: GetNetworkTime.now(),

@ -15,6 +15,7 @@ import { ProfileManager } from '../session/profile_manager/ProfileManager';
import { PubKey } from '../session/types';
import { StringUtils, UserUtils } from '../session/utils';
import { toHex } from '../session/utils/String';
import { FetchMsgExpirySwarm } from '../session/utils/job_runners/jobs/FetchMsgExpirySwarmJob';
import { UserSync } from '../session/utils/job_runners/jobs/UserSyncJob';
import { LibSessionUtil } from '../session/utils/libsession/libsession_utils';
import { SessionUtilContact } from '../session/utils/libsession/libsession_utils_contacts';
@ -43,8 +44,9 @@ import {
} from '../webworker/workers/browser/libsession_worker_functions';
// eslint-disable-next-line import/no-unresolved, import/extensions
import { Data } from '../data/data';
import { FetchMsgExpirySwarm } from '../session/utils/job_runners/jobs/FetchMsgExpirySwarmJob';
import { ReleasedFeatures } from '../util/releaseFeature';
// eslint-disable-next-line import/no-unresolved
import {
ContactsWrapperActions,
ConvoInfoVolatileWrapperActions,

@ -13,6 +13,7 @@ import { GroupUpdateInviteResponseMessage } from '../../session/messages/outgoin
import { ed25519Str } from '../../session/onions/onionPath';
import { PubKey } from '../../session/types';
import { UserUtils } from '../../session/utils';
import { sleepFor } from '../../session/utils/Promise';
import { stringToUint8Array } from '../../session/utils/String';
import { PreConditionFailed } from '../../session/utils/errors';
import { UserSync } from '../../session/utils/job_runners/jobs/UserSyncJob';
@ -63,15 +64,16 @@ async function handleGroupInviteMessage({
author,
envelopeTimestamp,
}: GroupInviteDetails) {
if (!PubKey.is03Pubkey(inviteMessage.groupSessionId)) {
const groupPk = inviteMessage.groupSessionId;
if (!PubKey.is03Pubkey(groupPk)) {
return;
}
if (BlockedNumberController.isBlocked(author)) {
window.log.info(
`received invite to group ${ed25519Str(
inviteMessage.groupSessionId
)} by blocked user:${ed25519Str(author)}... dropping it`
`received invite to group ${ed25519Str(groupPk)} by blocked user:${ed25519Str(
author
)}... dropping it`
);
return;
}
@ -79,7 +81,7 @@ async function handleGroupInviteMessage({
const authorIsApproved = ConvoHub.use().get(author)?.isApproved() || false;
const sigValid = await verifySig({
pubKey: HexString.fromHexStringNoPrefix(inviteMessage.groupSessionId),
pubKey: HexString.fromHexStringNoPrefix(groupPk),
signature: inviteMessage.adminSignature,
data: stringToUint8Array(`INVITE${UserUtils.getOurPubKeyStrFromCache()}${envelopeTimestamp}`),
});
@ -89,15 +91,8 @@ async function handleGroupInviteMessage({
return;
}
window.log.debug(
`received invite to group ${ed25519Str(inviteMessage.groupSessionId)} by user:${ed25519Str(
author
)}`
);
const convo = await ConvoHub.use().getOrCreateAndWait(
inviteMessage.groupSessionId,
ConversationTypeEnum.GROUPV2
);
window.log.debug(`received invite to group ${ed25519Str(groupPk)} by user:${ed25519Str(author)}`);
const convo = await ConvoHub.use().getOrCreateAndWait(groupPk, ConversationTypeEnum.GROUPV2);
convo.set({
active_at: envelopeTimestamp,
didApproveMe: true,
@ -112,14 +107,14 @@ async function handleGroupInviteMessage({
await convo.commit();
const userEd25519Secretkey = (await UserUtils.getUserED25519KeyPairBytes()).privKeyBytes;
let found = await UserGroupsWrapperActions.getGroup(inviteMessage.groupSessionId);
let found = await UserGroupsWrapperActions.getGroup(groupPk);
if (!found) {
found = {
authData: null,
joinedAtSeconds: Date.now(),
name: inviteMessage.name,
priority: 0,
pubkeyHex: inviteMessage.groupSessionId,
pubkeyHex: groupPk,
secretKey: null,
kicked: false,
invitePending: true,
@ -136,24 +131,24 @@ async function handleGroupInviteMessage({
found.authData = inviteMessage.memberAuthData;
await UserGroupsWrapperActions.setGroup(found);
await MetaGroupWrapperActions.init(inviteMessage.groupSessionId, {
await MetaGroupWrapperActions.init(groupPk, {
metaDumped: null,
groupEd25519Secretkey: null,
userEd25519Secretkey: toFixedUint8ArrayOfLength(userEd25519Secretkey, 64).buffer,
groupEd25519Pubkey: toFixedUint8ArrayOfLength(
HexString.fromHexStringNoPrefix(inviteMessage.groupSessionId),
32
).buffer,
groupEd25519Pubkey: toFixedUint8ArrayOfLength(HexString.fromHexStringNoPrefix(groupPk), 32)
.buffer,
});
await LibSessionUtil.saveDumpsToDb(UserUtils.getOurPubKeyStrFromCache());
await UserSync.queueNewJobIfNeeded();
if (!found.invitePending) {
// if this group should already be polling
getSwarmPollingInstance().addGroupId(inviteMessage.groupSessionId);
console.warn(
'we need to do a first poll to fetch the keys etc before we can send our invite response...'
);
await sendInviteResponseToGroup({ groupPk: inviteMessage.groupSessionId });
getSwarmPollingInstance().addGroupId(groupPk, async () => {
// we need to do a first poll to fetch the keys etc before we can send our invite response
// this is pretty hacky, but also an admin seeing a message from that user in the group will mark it as not pending anymore
await sleepFor(2000);
await sendInviteResponseToGroup({ groupPk });
});
}
}

@ -80,7 +80,11 @@ export const getSwarmPollingInstance = () => {
return instance;
};
type GroupPollingEntry = { pubkey: PubKey; lastPolledTimestamp: number };
type GroupPollingEntry = {
pubkey: PubKey;
lastPolledTimestamp: number;
callbackFirstPoll?: () => Promise<void>;
};
function entryToKey(entry: GroupPollingEntry) {
return entry.pubkey.key;
@ -134,11 +138,11 @@ export class SwarmPolling {
}
}
public addGroupId(pubkey: PubKey | string) {
public addGroupId(pubkey: PubKey | string, callbackFirstPoll?: () => Promise<void>) {
const pk = PubKey.cast(pubkey);
if (this.groupPolling.findIndex(m => m.pubkey.key === pk.key) === -1) {
window?.log?.info('Swarm addGroupId: adding pubkey to polling', pk.key);
this.groupPolling.push({ pubkey: pk, lastPolledTimestamp: 0 });
this.groupPolling.push({ pubkey: pk, lastPolledTimestamp: 0, callbackFirstPoll });
}
}
@ -386,6 +390,11 @@ export class SwarmPolling {
const shouldDiscardMessages = await this.shouldLeaveNotPolledGroup({ type, pubkey });
if (shouldDiscardMessages) {
window.log.info(
`polled a pk which should not be polled anymore: ${ed25519Str(
pubkey
)}. Discarding polling result`
);
return;
}
@ -394,6 +403,13 @@ export class SwarmPolling {
// groupv2 messages are not stored in the cache, so for each that we process, we also add it as seen message.
// this is to take care of a crash half way through processing messages. We'd get the same 100 messages back, and we'd skip up to the first not seen message
await handleMessagesForGroupV2(newMessages, pubkey);
// if a callback was registered for the first poll of that group pk, call it
const groupEntry = this.groupPolling.find(m => m.pubkey.key === pubkey);
if (groupEntry && groupEntry.callbackFirstPoll) {
void groupEntry.callbackFirstPoll();
groupEntry.callbackFirstPoll = undefined;
}
return;
}

@ -1,5 +1,5 @@
// NOTE this must match Content.ExpirationType in the protobuf
export type DisappearingMessageType = typeof DisappearingMessageMode[number];
export type DisappearingMessageType = (typeof DisappearingMessageMode)[number];
export const DisappearingMessageMode = ['unknown', 'deleteAfterRead', 'deleteAfterSend'] as const;
export type DisappearAfterSendOnly = Exclude<DisappearingMessageType, 'deleteAfterRead'>;
@ -11,7 +11,8 @@ export const DisappearingMessageConversationModes = [
// TODO legacy messages support will be removed in a future release
'legacy',
] as const;
export type DisappearingMessageConversationModeType = typeof DisappearingMessageConversationModes[number];
export type DisappearingMessageConversationModeType =
(typeof DisappearingMessageConversationModes)[number];
// TODO legacy messages support will be removed in a future release
// expirationType will no longer have an undefined option

@ -203,7 +203,6 @@ export async function timeout<T>(promise: Promise<T>, timeoutMs: number): Promis
return Promise.race([timeoutPromise, promise]);
}
export const sleepFor = async (ms: number, showLog = false) => {
if (showLog) {
// eslint-disable-next-line no-console

@ -145,16 +145,17 @@ const iceServersFullArray = [
username: 'session202111',
credential: '053c268164bc7bd7',
},
{
urls: 'turn:fenrir.getsession.org',
username: 'session202111',
credential: '053c268164bc7bd7',
},
{
urls: 'turn:frigg.getsession.org',
username: 'session202111',
credential: '053c268164bc7bd7',
},
// excluding those two (fenrir & frigg) as they are TCP only for now
// {
// urls: 'turn:fenrir.getsession.org',
// username: 'session202111',
// credential: '053c268164bc7bd7',
// },
// {
// urls: 'turn:frigg.getsession.org',
// username: 'session202111',
// credential: '053c268164bc7bd7',
// },
{
urls: 'turn:angus.getsession.org',
username: 'session202111',

@ -153,7 +153,6 @@ class GroupInviteJob extends PersistedJob<GroupInvitePersistedData> {
} finally {
updateFailedStateForMember(groupPk, member, failed);
try {
debugger;
await MetaGroupWrapperActions.memberSetInvited(groupPk, member, failed);
} catch (e) {
window.log.warn('GroupInviteJob memberSetInvited failed with', e.message);

@ -56,8 +56,8 @@ function getConvoType(convo: ConversationModel): ConvoVolatileType {
SessionUtilUserProfile.isUserProfileToStoreInWrapper(convo.id)
? '1o1'
: SessionUtilUserGroups.isCommunityToStoreInWrapper(convo)
? 'Community'
: 'LegacyGroup';
? 'Community'
: 'LegacyGroup';
return convoType;
}

@ -82,8 +82,8 @@ async function insertGroupsFromDBIntoWrapperAndRefresh(
const convoType: UserGroupsType = SessionUtilUserGroups.isCommunityToStoreInWrapper(foundConvo)
? 'Community'
: PubKey.is03Pubkey(convoId)
? 'Group'
: 'LegacyGroup';
? 'Group'
: 'LegacyGroup';
switch (convoType) {
case 'Community':

@ -70,6 +70,7 @@ export function search(query: string): SearchResultsKickoffActionType {
async function doSearch(query: string): Promise<SearchResultsPayloadType> {
const options: SearchOptions = {
noteToSelf: window.i18n('noteToSelf').toLowerCase(),
savedMessages: window.i18n('savedMessages').toLowerCase(),
ourNumber: UserUtils.getOurPubKeyStrFromCache(),
};
const advancedSearchOptions = getAdvancedSearchOptionsFromQuery(query);
@ -200,7 +201,7 @@ async function queryMessages(query: string): Promise<Array<MessageResultProps>>
}
async function queryConversationsAndContacts(providedQuery: string, options: SearchOptions) {
const { ourNumber, noteToSelf } = options;
const { ourNumber, noteToSelf, savedMessages } = options;
const query = providedQuery.replace(/[+-.()]*/g, '');
const searchResults: Array<ReduxConversationType> = await Data.searchConversations(query);
@ -224,9 +225,11 @@ async function queryConversationsAndContacts(providedQuery: string, options: Sea
conversations.push(conversation.id);
}
}
const queryLowered = providedQuery.toLowerCase();
// Inject synthetic Note to Self entry if query matches localized 'Note to Self'
if (noteToSelf.indexOf(providedQuery.toLowerCase()) !== -1) {
// ensure that we don't have duplicates in our results
if (noteToSelf.includes(queryLowered) || savedMessages.includes(queryLowered)) {
// Ensure that we don't have duplicates in our results
contacts = contacts.filter(id => id !== ourNumber);
conversations = conversations.filter(id => id !== ourNumber);

@ -8,6 +8,8 @@ const SettingsBoolsKeyTrackedInRedux = [
SettingsKey.someDeviceOutdatedSyncing,
SettingsKey.settingsLinkPreview,
SettingsKey.hasBlindedMsgRequestsEnabled,
SettingsKey.hasFollowSystemThemeEnabled,
SettingsKey.hasShiftSendEnabled,
] as const;
export type SettingsState = {
@ -20,6 +22,8 @@ export function getSettingsInitialState() {
someDeviceOutdatedSyncing: false,
'link-preview-setting': false, // this is the value of SettingsKey.settingsLinkPreview
hasBlindedMsgRequestsEnabled: false,
hasFollowSystemThemeEnabled: false,
hasShiftSendEnabled: false,
},
};
}
@ -47,6 +51,11 @@ const settingsSlice = createSlice({
SettingsKey.hasBlindedMsgRequestsEnabled,
false
);
const hasFollowSystemThemeEnabled = Storage.get(
SettingsKey.hasFollowSystemThemeEnabled,
false
);
const hasShiftSendEnabled = Storage.get(SettingsKey.hasShiftSendEnabled, false);
state.settingsBools.someDeviceOutdatedSyncing = isBoolean(outdatedSync)
? outdatedSync
: false;
@ -54,6 +63,15 @@ const settingsSlice = createSlice({
state.settingsBools.hasBlindedMsgRequestsEnabled = isBoolean(hasBlindedMsgRequestsEnabled)
? hasBlindedMsgRequestsEnabled
: false;
state.settingsBools.hasFollowSystemThemeEnabled = isBoolean(hasFollowSystemThemeEnabled)
? hasFollowSystemThemeEnabled
: false;
state.settingsBools.hasShiftSendEnabled = isBoolean(hasShiftSendEnabled)
? hasShiftSendEnabled
: false;
return state;
},
updateSettingsBoolValue(state, action: PayloadAction<{ id: string; value: boolean }>) {

@ -11,6 +11,12 @@ const getHasDeviceOutdatedSyncing = (state: StateType) =>
const getHasBlindedMsgRequestsEnabled = (state: StateType) =>
state.settings.settingsBools[SettingsKey.hasBlindedMsgRequestsEnabled];
const getHasFollowSystemThemeEnabled = (state: StateType) =>
state.settings.settingsBools[SettingsKey.hasFollowSystemThemeEnabled];
const getHasShiftSendEnabled = (state: StateType) =>
state.settings.settingsBools[SettingsKey.hasShiftSendEnabled];
export const useHasLinkPreviewEnabled = () => {
const value = useSelector(getLinkPreviewEnabled);
return Boolean(value);
@ -25,3 +31,14 @@ export const useHasBlindedMsgRequestsEnabled = () => {
const value = useSelector(getHasBlindedMsgRequestsEnabled);
return Boolean(value);
};
export const useHasFollowSystemThemeEnabled = () => {
const value = useSelector(getHasFollowSystemThemeEnabled);
return Boolean(value);
};
export const useHasEnterSendEnabled = () => {
const value = useSelector(getHasShiftSendEnabled);
return Boolean(value);
};

@ -1,8 +1,9 @@
import { ThemeStateType } from '../../themes/constants/colors';
import { StateType } from '../reducer';
import { checkDarkTheme, checkLightTheme } from '../../util/theme';
export const getTheme = (state: StateType): ThemeStateType => state.theme;
export const isDarkTheme = (state: StateType): boolean => state.theme.includes('dark');
export const isDarkTheme = (state: StateType): boolean => checkDarkTheme(state.theme);
export const isLightTheme = (state: StateType): boolean => state.theme.includes('light');
export const isLightTheme = (state: StateType): boolean => checkLightTheme(state.theme);

@ -1,7 +1,9 @@
import { ipcRenderer } from 'electron';
import React from 'react';
import { createGlobalStyle } from 'styled-components';
import { switchThemeTo } from './switchTheme';
import { classicDark } from './classicDark';
import { getOppositeTheme, isThemeMismatched } from '../util/theme';
import { declareCSSVariables, THEME_GLOBALS } from './globals';
// Defaults to Classic Dark theme
@ -18,3 +20,26 @@ export const SessionTheme = ({ children }: { children: any }) => (
{children}
</>
);
export async function ensureThemeConsistency(): Promise<boolean> {
const theme = window.Events.getThemeSetting();
return new Promise(resolve => {
ipcRenderer.send('get-native-theme');
ipcRenderer.once('send-native-theme', (_, shouldUseDarkColors) => {
const isMismatchedTheme = isThemeMismatched(theme, shouldUseDarkColors);
if (isMismatchedTheme) {
const newTheme = getOppositeTheme(theme);
void switchThemeTo({
theme: newTheme,
mainWindow: true,
usePrimaryColor: true,
dispatch: window?.inboxStore?.dispatch,
});
resolve(true); // Theme was switched
} else {
resolve(false); // Theme was not switched
}
});
});
}

@ -178,9 +178,13 @@ export type LocalizerKeys =
| 'endCall'
| 'enterAnOpenGroupURL'
| 'enterDisplayName'
| 'enterKeySettingDescription'
| 'enterKeySettingTitle'
| 'enterNewLineDescription'
| 'enterNewPassword'
| 'enterPassword'
| 'enterRecoveryPhrase'
| 'enterSendNewMessageDescription'
| 'enterSessionID'
| 'enterSessionIDOfRecipient'
| 'enterSessionIDOrONSName'
@ -276,6 +280,8 @@ export type LocalizerKeys =
| 'mainMenuWindow'
| 'markAllAsRead'
| 'markUnread'
| 'matchThemeSystemSettingDescription'
| 'matchThemeSystemSettingTitle'
| 'maxPasswordAttempts'
| 'maximumAttachments'
| 'media'
@ -422,6 +428,7 @@ export type LocalizerKeys =
| 'save'
| 'saveLogToDesktop'
| 'saved'
| 'savedMessages'
| 'savedTheFile'
| 'searchFor...'
| 'searchForContactsOnly'

@ -1,6 +1,7 @@
export type SearchOptions = {
ourNumber: string;
noteToSelf: string;
savedMessages: string;
};
export type AdvancedSearchOptions = {

@ -0,0 +1,21 @@
import { ThemeStateType } from '../themes/constants/colors';
export const checkDarkTheme = (theme: ThemeStateType): boolean => theme.includes('dark');
export const checkLightTheme = (theme: ThemeStateType): boolean => theme.includes('light');
export function getOppositeTheme(themeName: ThemeStateType): ThemeStateType {
if (checkDarkTheme(themeName)) {
return themeName.replace('dark', 'light') as ThemeStateType;
}
if (checkLightTheme(themeName)) {
return themeName.replace('light', 'dark') as ThemeStateType;
}
// If neither 'dark' nor 'light' is in the theme name, return the original theme name.
return themeName as ThemeStateType;
}
export function isThemeMismatched(themeName: ThemeStateType, prefersDark: boolean): boolean {
const systemLightTheme = checkLightTheme(themeName);
const systemDarkTheme = checkDarkTheme(themeName);
return (prefersDark && systemLightTheme) || (!prefersDark && systemDarkTheme);
}

@ -230,8 +230,8 @@ onmessage = async (e: { data: [number, ConfigWrapperObjectTypesMeta, string, ...
const wrapper = isUserConfigWrapperType(config)
? getCorrespondingUserWrapper(config)
: isMetaWrapperType(config)
? getCorrespondingGroupWrapper(config)
: undefined;
? getCorrespondingGroupWrapper(config)
: undefined;
if (!wrapper) {
throw new Error(`did not find an already built wrapper for config: "${config}"`);
}

Loading…
Cancel
Save