chore: add a redux settings slice, currently outdated banner inc

pull/2620/head
Audric Ackermann 3 years ago
parent 0080254286
commit 0e286142f1

@ -43,10 +43,11 @@ moment.locale((window.i18n as any).getLocale());
// Workaround: A react component's required properties are filtering up through connect() // Workaround: A react component's required properties are filtering up through connect()
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363 // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
import useUpdate from 'react-use/lib/useUpdate'; import useUpdate from 'react-use/lib/useUpdate';
import useInterval from 'react-use/lib/useInterval';
import { SettingsKey } from '../data/settings-key'; import { SettingsKey } from '../data/settings-key';
import { NoticeBanner } from './NoticeBanner'; import { NoticeBanner } from './NoticeBanner';
import { Flex } from './basic/Flex'; import { Flex } from './basic/Flex';
import { useHasDeviceOutdatedSyncing } from '../state/selectors/settings';
import { getSettingsInitialState, updateAllOnStorageReady } from '../state/ducks/settings';
const StyledGutter = styled.div` const StyledGutter = styled.div`
width: 380px !important; width: 380px !important;
@ -60,7 +61,6 @@ function createSessionInboxStore() {
.map(conversation => conversation.getConversationModelProps()); .map(conversation => conversation.getConversationModelProps());
const timerOptions: TimerOptionsArray = ExpirationTimerOptions.getTimerSecondsWithName(); const timerOptions: TimerOptionsArray = ExpirationTimerOptions.getTimerSecondsWithName();
const initialState: StateType = { const initialState: StateType = {
conversations: { conversations: {
...getEmptyConversationState(), ...getEmptyConversationState(),
@ -83,6 +83,7 @@ function createSessionInboxStore() {
stagedAttachments: getEmptyStagedAttachmentsState(), stagedAttachments: getEmptyStagedAttachmentsState(),
call: initialCallState, call: initialCallState,
sogsRoomInfo: initialSogsRoomInfoState, sogsRoomInfo: initialSogsRoomInfoState,
settings: getSettingsInitialState(),
}; };
return createStore(initialState); return createStore(initialState);
@ -91,29 +92,18 @@ function createSessionInboxStore() {
function setupLeftPane(forceUpdateInboxComponent: () => void) { function setupLeftPane(forceUpdateInboxComponent: () => void) {
window.openConversationWithMessages = openConversationWithMessages; window.openConversationWithMessages = openConversationWithMessages;
window.inboxStore = createSessionInboxStore(); window.inboxStore = createSessionInboxStore();
window.inboxStore.dispatch(updateAllOnStorageReady());
forceUpdateInboxComponent(); forceUpdateInboxComponent();
} }
const SomeDeviceOutdatedSyncingNotice = () => { const SomeDeviceOutdatedSyncingNotice = () => {
const forceUpdate = useUpdate(); const outdatedBannerShouldBeShown = useHasDeviceOutdatedSyncing();
const isShown = Boolean(window.getSettingValue(SettingsKey.someDeviceOutdatedSyncing));
// it would be nice to get the settings into a redux slice in addition to their Storage location and keep them in sync.
// So we could just use a selector here.
useInterval(() => {
const shouldBeShown = Storage.get(SettingsKey.someDeviceOutdatedSyncing);
if (!isShown && shouldBeShown) {
forceUpdate();
}
}, 1000);
const dismiss = async () => { const dismiss = async () => {
await window.setSettingValue(SettingsKey.someDeviceOutdatedSyncing, false); await Storage.put(SettingsKey.someDeviceOutdatedSyncing, false);
forceUpdate();
}; };
if (!isShown) { if (!outdatedBannerShouldBeShown) {
return null; return null;
} }
return ( return (

@ -2,7 +2,8 @@ import React from 'react';
import { TypingAnimation } from './TypingAnimation'; import { TypingAnimation } from './TypingAnimation';
import styled from 'styled-components'; import styled from 'styled-components';
import { ConversationTypeEnum, isOpenOrClosedGroup } from '../../models/conversationAttributes'; import { ConversationTypeEnum } from '../../models/conversationAttributes';
import { useSelectedIsGroup } from '../../state/selectors/selectedConversation';
interface TypingBubbleProps { interface TypingBubbleProps {
conversationType: ConversationTypeEnum; conversationType: ConversationTypeEnum;
@ -22,11 +23,8 @@ const TypingBubbleContainer = styled.div<TypingBubbleProps>`
`; `;
export const TypingBubble = (props: TypingBubbleProps) => { export const TypingBubble = (props: TypingBubbleProps) => {
if (isOpenOrClosedGroup(props.conversationType)) { const isOpenOrClosedGroup = useSelectedIsGroup();
return null; if (!isOpenOrClosedGroup || !props.isTyping) {
}
if (!props.isTyping) {
return null; return null;
} }

@ -56,6 +56,7 @@ import {
getSelectedConversation, getSelectedConversation,
getSelectedConversationKey, getSelectedConversationKey,
} from '../../../state/selectors/selectedConversation'; } from '../../../state/selectors/selectedConversation';
import { SettingsKey } from '../../../data/settings-key';
export interface ReplyingToMessageProps { export interface ReplyingToMessageProps {
convoId: string; convoId: string;
@ -601,7 +602,7 @@ class CompositionBoxInner extends React.Component<Props, State> {
private renderStagedLinkPreview(): JSX.Element | null { private renderStagedLinkPreview(): JSX.Element | null {
// Don't generate link previews if user has turned them off // Don't generate link previews if user has turned them off
if (!(window.getSettingValue('link-preview-setting') || false)) { if (!(window.getSettingValue(SettingsKey.settingsLinkPreview) || false)) {
return null; return null;
} }

@ -8,9 +8,9 @@ import styled, { css } from 'styled-components';
import { MessageModelType, MessageRenderingProps } from '../../../../models/messageType'; import { MessageModelType, MessageRenderingProps } from '../../../../models/messageType';
import { import {
getMessageContentSelectorProps, getMessageContentSelectorProps,
getMessageTextProps,
getQuotedMessageToAnimate, getQuotedMessageToAnimate,
getShouldHighlightMessage, getShouldHighlightMessage,
useMessageIsDeleted,
} from '../../../../state/selectors/conversations'; } from '../../../../state/selectors/conversations';
import { ScrollToLoadedMessageContext } from '../../SessionMessagesListContainer'; import { ScrollToLoadedMessageContext } from '../../SessionMessagesListContainer';
import { MessageAttachment } from './MessageAttachment'; import { MessageAttachment } from './MessageAttachment';
@ -96,6 +96,7 @@ export const MessageContent = (props: Props) => {
const contentProps = useSelector(state => const contentProps = useSelector(state =>
getMessageContentSelectorProps(state as any, props.messageId) getMessageContentSelectorProps(state as any, props.messageId)
); );
const isDeleted = useMessageIsDeleted(props.messageId);
const [isMessageVisible, setMessageIsVisible] = useState(false); const [isMessageVisible, setMessageIsVisible] = useState(false);
const scrollToLoadedMessage = useContext(ScrollToLoadedMessageContext); const scrollToLoadedMessage = useContext(ScrollToLoadedMessageContext);
@ -149,13 +150,6 @@ export const MessageContent = (props: Props) => {
const { direction, text, timestamp, serverTimestamp, previews } = contentProps; const { direction, text, timestamp, serverTimestamp, previews } = contentProps;
const selectedMsg = useSelector(state => getMessageTextProps(state as any, props.messageId));
let isDeleted = false;
if (selectedMsg && selectedMsg.isDeleted !== undefined) {
isDeleted = selectedMsg.isDeleted;
}
const hasContentAfterAttachmentAndQuote = !isEmpty(previews) || !isEmpty(text); const hasContentAfterAttachmentAndQuote = !isEmpty(previews) || !isEmpty(text);
const toolTipTitle = moment(serverTimestamp || timestamp).format('llll'); const toolTipTitle = moment(serverTimestamp || timestamp).format('llll');

@ -9,6 +9,7 @@ import {
} from '../../../../state/selectors/conversations'; } from '../../../../state/selectors/conversations';
import { SessionIcon } from '../../../icon'; import { SessionIcon } from '../../../icon';
import { MessageBody } from './MessageBody'; import { MessageBody } from './MessageBody';
import { StateType } from '../../../../state/reducer';
type Props = { type Props = {
messageId: string; messageId: string;
@ -20,7 +21,7 @@ export type MessageTextSelectorProps = Pick<
>; >;
export const MessageText = (props: Props) => { export const MessageText = (props: Props) => {
const selected = useSelector(state => getMessageTextProps(state as any, props.messageId)); const selected = useSelector((state: StateType) => getMessageTextProps(state, props.messageId));
const multiSelectMode = useSelector(isMessageSelectionMode); const multiSelectMode = useSelector(isMessageSelectionMode);
if (!selected) { if (!selected) {

@ -52,7 +52,7 @@ const ChangeItemLeft = (left: Array<string>): string => {
// tslint:disable-next-line: cyclomatic-complexity // tslint:disable-next-line: cyclomatic-complexity
const ChangeItem = (change: PropsForGroupUpdateType): string => { const ChangeItem = (change: PropsForGroupUpdateType): string => {
const type = change.type; const { type } = change;
switch (type) { switch (type) {
case 'name': case 'name':
return window.i18n('titleIsNow', [change.newName || '']); return window.i18n('titleIsNow', [change.newName || '']);

@ -24,6 +24,7 @@ import { SessionWrapperModal } from '../SessionWrapperModal';
import { SessionButton, SessionButtonType } from '../basic/SessionButton'; import { SessionButton, SessionButtonType } from '../basic/SessionButton';
import { SessionSpinner } from '../basic/SessionSpinner'; import { SessionSpinner } from '../basic/SessionSpinner';
import { SessionIconButton } from '../icon'; import { SessionIconButton } from '../icon';
import { ConfigurationDumpSync } from '../../session/utils/job_runners/jobs/ConfigurationSyncDumpJob';
const handleSaveQRCode = (event: MouseEvent) => { const handleSaveQRCode = (event: MouseEvent) => {
event.preventDefault(); event.preventDefault();
@ -360,6 +361,7 @@ async function commitProfileEdits(newName: string, scaledAvatarUrl: string | nul
if (window.sessionFeatureFlags.useSharedUtilForUserConfig) { if (window.sessionFeatureFlags.useSharedUtilForUserConfig) {
await ConfigurationSync.queueNewJobIfNeeded(); await ConfigurationSync.queueNewJobIfNeeded();
await ConfigurationDumpSync.queueNewJobIfNeeded();
await setLastProfileUpdateTimestamp(Date.now()); await setLastProfileUpdateTimestamp(Date.now());
} else { } else {
await setLastProfileUpdateTimestamp(Date.now()); await setLastProfileUpdateTimestamp(Date.now());

@ -3,11 +3,7 @@ import { getConversationController } from '../../session/conversations';
import { syncConfigurationIfNeeded } from '../../session/utils/sync/syncUtils'; import { syncConfigurationIfNeeded } from '../../session/utils/sync/syncUtils';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { import { Data } from '../../data/data';
Data,
hasSyncedInitialConfigurationItem,
lastAvatarUploadTimestamp,
} from '../../data/data';
import { getMessageQueue } from '../../session/sending'; import { getMessageQueue } from '../../session/sending';
// tslint:disable: no-submodule-imports // tslint:disable: no-submodule-imports
import useInterval from 'react-use/lib/useInterval'; import useInterval from 'react-use/lib/useInterval';
@ -47,6 +43,7 @@ import { forceRefreshRandomSnodePool } from '../../session/apis/snode_api/snodeP
import { isDarkTheme } from '../../state/selectors/theme'; import { isDarkTheme } from '../../state/selectors/theme';
import { ThemeStateType } from '../../themes/constants/colors'; import { ThemeStateType } from '../../themes/constants/colors';
import { switchThemeTo } from '../../themes/switchTheme'; import { switchThemeTo } from '../../themes/switchTheme';
import { SettingsKey } from '../../data/settings-key';
const Section = (props: { type: SectionType }) => { const Section = (props: { type: SectionType }) => {
const ourNumber = useSelector(getOurNumber); const ourNumber = useSelector(getOurNumber);
@ -172,14 +169,15 @@ const triggerSyncIfNeeded = async () => {
.get(us) .get(us)
.setIsApproved(true, true); .setIsApproved(true, true);
const didWeHandleAConfigurationMessageAlready = const didWeHandleAConfigurationMessageAlready =
(await Data.getItemById(hasSyncedInitialConfigurationItem))?.value || false; (await Data.getItemById(SettingsKey.hasSyncedInitialConfigurationItem))?.value || false;
if (didWeHandleAConfigurationMessageAlready) { if (didWeHandleAConfigurationMessageAlready) {
await syncConfigurationIfNeeded(); await syncConfigurationIfNeeded();
} }
}; };
const triggerAvatarReUploadIfNeeded = async () => { const triggerAvatarReUploadIfNeeded = async () => {
const lastTimeStampAvatarUpload = (await Data.getItemById(lastAvatarUploadTimestamp))?.value || 0; const lastTimeStampAvatarUpload =
(await Data.getItemById(SettingsKey.lastAvatarUploadTimestamp))?.value || 0;
if (Date.now() - lastTimeStampAvatarUpload > DURATION.DAYS * 14) { if (Date.now() - lastTimeStampAvatarUpload > DURATION.DAYS * 14) {
window.log.info('Reuploading avatar...'); window.log.info('Reuploading avatar...');

@ -36,7 +36,7 @@ export const LightboxGallery = (props: Props) => {
const selectedConversation = useSelectedConversationKey(); const selectedConversation = useSelectedConversationKey();
if (!selectedConversation) { if (!selectedConversation) {
throw new Error('LightboxGallery: selectedConversation is undefined'); return null;
} }
const dispatch = useDispatch(); const dispatch = useDispatch();

@ -16,6 +16,7 @@ import {
} from '../../util/accountManager'; } from '../../util/accountManager';
import { fromHex } from '../../session/utils/String'; import { fromHex } from '../../session/utils/String';
import { setSignInByLinking, setSignWithRecoveryPhrase, Storage } from '../../util/storage'; import { setSignInByLinking, setSignWithRecoveryPhrase, Storage } from '../../util/storage';
import { SettingsKey } from '../../data/settings-key';
// tslint:disable: use-simple-attributes // tslint:disable: use-simple-attributes
@ -59,11 +60,7 @@ export async function signUp(signUpDetails: {
try { try {
await resetRegistration(); await resetRegistration();
await registerSingleDevice(generatedRecoveryPhrase, 'english', trimName); await registerSingleDevice(generatedRecoveryPhrase, 'english', trimName);
await Data.createOrUpdateItem({ await Storage.put(SettingsKey.hasSyncedInitialConfigurationItem, Date.now());
id: 'hasSyncedInitialConfigurationItem',
value: true,
timestamp: Date.now(),
});
await setSignWithRecoveryPhrase(false); await setSignWithRecoveryPhrase(false);
trigger('openInbox'); trigger('openInbox');
} catch (e) { } catch (e) {

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports
import useUpdate from 'react-use/lib/useUpdate'; import useUpdate from 'react-use/lib/useUpdate';
import { Data, hasLinkPreviewPopupBeenDisplayed } from '../../../data/data';
import { SettingsKey } from '../../../data/settings-key'; import { SettingsKey } from '../../../data/settings-key';
import { ConversationTypeEnum } from '../../../models/conversationAttributes'; import { ConversationTypeEnum } from '../../../models/conversationAttributes';
import { updateConfirmModal } from '../../../state/ducks/modalDialog'; import { updateConfirmModal } from '../../../state/ducks/modalDialog';
@ -11,9 +10,10 @@ import { TypingBubble } from '../../conversation/TypingBubble';
import { SessionSettingButtonItem, SessionToggleWithDescription } from '../SessionSettingListItem'; import { SessionSettingButtonItem, SessionToggleWithDescription } from '../SessionSettingListItem';
import { displayPasswordModal } from '../SessionSettings'; import { displayPasswordModal } from '../SessionSettings';
import { Storage } from '../../../util/storage';
import { useHasLinkPreviewEnabled } from '../../../state/selectors/settings';
async function toggleLinkPreviews(forceUpdate: () => void) { async function toggleLinkPreviews(isToggleOn: boolean, forceUpdate: () => void) {
const isToggleOn = Boolean(window.getSettingValue(SettingsKey.settingsLinkPreview));
if (!isToggleOn) { if (!isToggleOn) {
window.inboxStore?.dispatch( window.inboxStore?.dispatch(
updateConfirmModal({ updateConfirmModal({
@ -29,7 +29,7 @@ async function toggleLinkPreviews(forceUpdate: () => void) {
); );
} else { } else {
await window.setSettingValue(SettingsKey.settingsLinkPreview, false); await window.setSettingValue(SettingsKey.settingsLinkPreview, false);
await Data.createOrUpdateItem({ id: hasLinkPreviewPopupBeenDisplayed, value: false }); await Storage.put(SettingsKey.hasLinkPreviewPopupBeenDisplayed, false);
forceUpdate(); forceUpdate();
} }
} }
@ -48,7 +48,7 @@ export const SettingsCategoryPrivacy = (props: {
onPasswordUpdated: (action: string) => void; onPasswordUpdated: (action: string) => void;
}) => { }) => {
const forceUpdate = useUpdate(); const forceUpdate = useUpdate();
const isLinkPreviewsOn = Boolean(window.getSettingValue(SettingsKey.settingsLinkPreview)); const isLinkPreviewsOn = useHasLinkPreviewEnabled();
if (props.hasPassword !== null) { if (props.hasPassword !== null) {
return ( return (
@ -76,7 +76,7 @@ export const SettingsCategoryPrivacy = (props: {
/> />
<SessionToggleWithDescription <SessionToggleWithDescription
onClickToggle={async () => { onClickToggle={async () => {
await toggleLinkPreviews(forceUpdate); await toggleLinkPreviews(isLinkPreviewsOn, forceUpdate);
}} }}
title={window.i18n('linkPreviewsTitle')} title={window.i18n('linkPreviewsTitle')}
description={window.i18n('linkPreviewDescription')} description={window.i18n('linkPreviewDescription')}

@ -53,10 +53,6 @@ export type SwarmNode = Snode & {
address: string; address: string;
}; };
export const hasSyncedInitialConfigurationItem = 'hasSyncedInitialConfigurationItem';
export const lastAvatarUploadTimestamp = 'lastAvatarUploadTimestamp';
export const hasLinkPreviewPopupBeenDisplayed = 'hasLinkPreviewPopupBeenDisplayed';
// Basic // Basic
async function shutdown(): Promise<void> { async function shutdown(): Promise<void> {
// Stop accepting new SQL jobs, flush outstanding queue // Stop accepting new SQL jobs, flush outstanding queue
@ -654,7 +650,7 @@ async function getSnodePoolFromDb(): Promise<Array<Snode> | null> {
} }
async function updateSnodePoolOnDb(snodesAsJsonString: string): Promise<void> { async function updateSnodePoolOnDb(snodesAsJsonString: string): Promise<void> {
await Data.createOrUpdateItem({ id: SNODE_POOL_ITEM_ID, value: snodesAsJsonString }); await Storage.put(SNODE_POOL_ITEM_ID, snodesAsJsonString);
} }
function keysToArrayBuffer(keys: any, data: any) { function keysToArrayBuffer(keys: any, data: any) {
@ -692,8 +688,8 @@ const ITEM_KEYS: Object = {
}; };
/** /**
* Note: In the app, you should always call createOrUpdateItem through Data.createOrUpdateItem (from the data.ts file). * For anything related to the UI and redux, do not use `createOrUpdateItem` directly. Instead use Storage.put (from the utils folder).
* This is to ensure testing and stubbbing works as expected * `Storage.put` will update the settings redux slice if needed but createOrUpdateItem will not.
*/ */
export async function createOrUpdateItem(data: StorageItem): Promise<void> { export async function createOrUpdateItem(data: StorageItem): Promise<void> {
const { id } = data; const { id } = data;

@ -9,7 +9,10 @@ const settingsStartInTray = 'start-in-tray-setting';
const settingsOpengroupPruning = 'prune-setting'; const settingsOpengroupPruning = 'prune-setting';
const settingsNotification = 'notification-setting'; const settingsNotification = 'notification-setting';
const settingsAudioNotification = 'audio-notification-setting'; const settingsAudioNotification = 'audio-notification-setting';
const someDeviceOutdatedSyncing = 'some-device-outdated-syncing'; const someDeviceOutdatedSyncing = 'someDeviceOutdatedSyncing';
const hasSyncedInitialConfigurationItem = 'hasSyncedInitialConfigurationItem';
const lastAvatarUploadTimestamp = 'lastAvatarUploadTimestamp';
const hasLinkPreviewPopupBeenDisplayed = 'hasLinkPreviewPopupBeenDisplayed';
export const SettingsKey = { export const SettingsKey = {
settingsReadReceipt, settingsReadReceipt,
@ -23,7 +26,10 @@ export const SettingsKey = {
settingsNotification, settingsNotification,
settingsAudioNotification, settingsAudioNotification,
someDeviceOutdatedSyncing, someDeviceOutdatedSyncing,
}; hasSyncedInitialConfigurationItem,
lastAvatarUploadTimestamp,
hasLinkPreviewPopupBeenDisplayed,
} as const;
export const KNOWN_BLINDED_KEYS_ITEM = 'KNOWN_BLINDED_KEYS_ITEM'; export const KNOWN_BLINDED_KEYS_ITEM = 'KNOWN_BLINDED_KEYS_ITEM';
export const SNODE_POOL_ITEM_ID = 'SNODE_POOL_ITEM_ID'; export const SNODE_POOL_ITEM_ID = 'SNODE_POOL_ITEM_ID';

@ -6,7 +6,7 @@ import { CallManager, SyncUtils, ToastUtils, UserUtils } from '../session/utils'
import { SessionButtonColor } from '../components/basic/SessionButton'; import { SessionButtonColor } from '../components/basic/SessionButton';
import { getCallMediaPermissionsSettings } from '../components/settings/SessionSettings'; import { getCallMediaPermissionsSettings } from '../components/settings/SessionSettings';
import { Data, hasLinkPreviewPopupBeenDisplayed, lastAvatarUploadTimestamp } from '../data/data'; import { Data } from '../data/data';
import { uploadFileToFsWithOnionV4 } from '../session/apis/file_server_api/FileServerApi'; import { uploadFileToFsWithOnionV4 } from '../session/apis/file_server_api/FileServerApi';
import { getConversationController } from '../session/conversations'; import { getConversationController } from '../session/conversations';
import { getSodiumRenderer } from '../session/crypto'; import { getSodiumRenderer } from '../session/crypto';
@ -37,11 +37,13 @@ import { processNewAttachment } from '../types/MessageAttachment';
import { IMAGE_JPEG } from '../types/MIME'; import { IMAGE_JPEG } from '../types/MIME';
import { BlockedNumberController } from '../util/blockedNumberController'; import { BlockedNumberController } from '../util/blockedNumberController';
import { encryptProfile } from '../util/crypto/profileEncrypter'; import { encryptProfile } from '../util/crypto/profileEncrypter';
import { setLastProfileUpdateTimestamp } from '../util/storage'; import { Storage, setLastProfileUpdateTimestamp } from '../util/storage';
import { OpenGroupUtils } from '../session/apis/open_group_api/utils'; import { OpenGroupUtils } from '../session/apis/open_group_api/utils';
import { SessionUtilUserGroups } from '../session/utils/libsession/libsession_utils_user_groups'; import { SessionUtilUserGroups } from '../session/utils/libsession/libsession_utils_user_groups';
import { leaveClosedGroup } from '../session/group/closed-group'; import { leaveClosedGroup } from '../session/group/closed-group';
import { SessionUtilContact } from '../session/utils/libsession/libsession_utils_contacts'; import { SessionUtilContact } from '../session/utils/libsession/libsession_utils_contacts';
import { SettingsKey } from '../data/settings-key';
import { ConfigurationDumpSync } from '../session/utils/job_runners/jobs/ConfigurationSyncDumpJob';
export function copyPublicKeyByConvoId(convoId: string) { export function copyPublicKeyByConvoId(convoId: string) {
if (OpenGroupUtils.isOpenGroupV2(convoId)) { if (OpenGroupUtils.isOpenGroupV2(convoId)) {
@ -457,12 +459,13 @@ export async function uploadOurAvatar(newAvatarDecrypted?: ArrayBuffer) {
avatarImageId: fileId, avatarImageId: fileId,
}); });
const newTimestampReupload = Date.now(); const newTimestampReupload = Date.now();
await Data.createOrUpdateItem({ id: lastAvatarUploadTimestamp, value: newTimestampReupload }); await Storage.put(SettingsKey.lastAvatarUploadTimestamp, newTimestampReupload);
if (newAvatarDecrypted) { if (newAvatarDecrypted) {
await setLastProfileUpdateTimestamp(Date.now()); await setLastProfileUpdateTimestamp(Date.now());
if (window.sessionFeatureFlags.useSharedUtilForUserConfig) { if (window.sessionFeatureFlags.useSharedUtilForUserConfig) {
await ConfigurationSync.queueNewJobIfNeeded(); await ConfigurationSync.queueNewJobIfNeeded();
await ConfigurationDumpSync.queueNewJobIfNeeded();
} else { } else {
await SyncUtils.forceSyncConfigurationNowIfNeeded(true); await SyncUtils.forceSyncConfigurationNowIfNeeded(true);
} }
@ -502,22 +505,22 @@ export async function replyToMessage(messageId: string) {
*/ */
export async function showLinkSharingConfirmationModalDialog(e: any) { export async function showLinkSharingConfirmationModalDialog(e: any) {
const pastedText = e.clipboardData.getData('text'); const pastedText = e.clipboardData.getData('text');
if (isURL(pastedText) && !window.getSettingValue('link-preview-setting', false)) { if (isURL(pastedText) && !window.getSettingValue(SettingsKey.settingsLinkPreview, false)) {
const alreadyDisplayedPopup = const alreadyDisplayedPopup =
(await Data.getItemById(hasLinkPreviewPopupBeenDisplayed))?.value || false; (await Data.getItemById(SettingsKey.hasLinkPreviewPopupBeenDisplayed))?.value || false;
if (!alreadyDisplayedPopup) { if (!alreadyDisplayedPopup) {
window.inboxStore?.dispatch( window.inboxStore?.dispatch(
updateConfirmModal({ updateConfirmModal({
shouldShowConfirm: shouldShowConfirm:
!window.getSettingValue('link-preview-setting') && !alreadyDisplayedPopup, !window.getSettingValue(SettingsKey.settingsLinkPreview) && !alreadyDisplayedPopup,
title: window.i18n('linkPreviewsTitle'), title: window.i18n('linkPreviewsTitle'),
message: window.i18n('linkPreviewsConfirmMessage'), message: window.i18n('linkPreviewsConfirmMessage'),
okTheme: SessionButtonColor.Danger, okTheme: SessionButtonColor.Danger,
onClickOk: async () => { onClickOk: async () => {
await window.setSettingValue('link-preview-setting', true); await window.setSettingValue(SettingsKey.settingsLinkPreview, true);
}, },
onClickClose: async () => { onClickClose: async () => {
await Data.createOrUpdateItem({ id: hasLinkPreviewPopupBeenDisplayed, value: true }); await Storage.put(SettingsKey.hasLinkPreviewPopupBeenDisplayed, true);
}, },
}) })
); );

@ -24,6 +24,7 @@ import { initialiseEmojiData } from '../util/emoji';
import { switchPrimaryColorTo } from '../themes/switchPrimaryColor'; import { switchPrimaryColorTo } from '../themes/switchPrimaryColor';
import { LibSessionUtil } from '../session/utils/libsession/libsession_utils'; import { LibSessionUtil } from '../session/utils/libsession/libsession_utils';
import { runners } from '../session/utils/job_runners/JobRunner'; import { runners } from '../session/utils/job_runners/JobRunner';
import { SettingsKey } from '../data/settings-key';
// tslint:disable: max-classes-per-file // tslint:disable: max-classes-per-file
// Globally disable drag and drop // Globally disable drag and drop
@ -163,7 +164,7 @@ Storage.onready(async () => {
// Stop background processing // Stop background processing
AttachmentDownloads.stop(); AttachmentDownloads.stop();
// Stop processing incoming messages // Stop processing incoming messages
// FIXME audric stop polling opengroupv2 and swarm nodes // TODO stop polling opengroupv2 and swarm nodes
// Shut down the data interface cleanly // Shut down the data interface cleanly
await Data.shutdown(); await Data.shutdown();
@ -262,11 +263,6 @@ async function start() {
WhisperEvents.on('registration_done', async () => { WhisperEvents.on('registration_done', async () => {
window.log.info('handling registration event'); window.log.info('handling registration event');
// Disable link previews as default per Kee
Storage.onready(async () => {
await Storage.put('link-preview-setting', false);
});
await connect(); await connect();
}); });
@ -287,7 +283,7 @@ async function start() {
}); });
} }
function openStandAlone() { function showRegistrationView() {
ReactDOM.render(<SessionRegistrationView />, document.getElementById('root')); ReactDOM.render(<SessionRegistrationView />, document.getElementById('root'));
} }
ExpirationTimerOptions.initExpiringMessageListener(); ExpirationTimerOptions.initExpiringMessageListener();
@ -298,7 +294,7 @@ async function start() {
} else { } else {
const primaryColor = window.Events.getPrimaryColorSetting(); const primaryColor = window.Events.getPrimaryColorSetting();
await switchPrimaryColorTo(primaryColor); await switchPrimaryColorTo(primaryColor);
openStandAlone(); showRegistrationView();
} }
window.addEventListener('focus', () => { window.addEventListener('focus', () => {
@ -386,7 +382,7 @@ async function start() {
if (launchCount === 1) { if (launchCount === 1) {
// Initialise default settings // Initialise default settings
await window.setSettingValue('hide-menu-bar', true); await window.setSettingValue('hide-menu-bar', true);
await window.setSettingValue('link-preview-setting', false); await window.setSettingValue(SettingsKey.settingsLinkPreview, false);
} }
WhisperEvents.on('openInbox', () => { WhisperEvents.on('openInbox', () => {

@ -1219,7 +1219,7 @@ function insertContactIntoContactWrapper(
const dbApproved = !!contact.isApproved || false; const dbApproved = !!contact.isApproved || false;
const dbApprovedMe = !!contact.didApproveMe || false; const dbApprovedMe = !!contact.didApproveMe || false;
const dbBlocked = blockedNumbers.includes(contact.id); const dbBlocked = blockedNumbers.includes(contact.id);
const priority = contact.priority || 0; const priority = contact.priority || CONVERSATION_PRIORITIES.default;
const expirationTimerSeconds = contact.expireTimer || 0; const expirationTimerSeconds = contact.expireTimer || 0;
const wrapperContact = getContactInfoFromDBValues({ const wrapperContact = getContactInfoFromDBValues({
@ -1581,12 +1581,7 @@ function updateToSessionSchemaVersion30(currentVersion: number, db: BetterSqlite
ourDbProfileKey ourDbProfileKey
); );
} else { } else {
userProfileWrapper.setUserInfo( userProfileWrapper.setUserInfo(ourDbName, ourConvoPriority, '', new Uint8Array());
ourDbName,
ourConvoPriority, // consider that the Note to self is hidden on a fresh account (without avatar set)
'',
new Uint8Array()
);
} }
insertContactIntoContactWrapper( insertContactIntoContactWrapper(

@ -1,5 +1,4 @@
export type StorageItem = { export type StorageItem = {
id: string; id: string;
value: any; value: any;
timestamp?: number;
}; };

@ -1,6 +1,6 @@
import { compact, isEmpty, toNumber } from 'lodash'; import { compact, isEmpty, isNumber, toNumber } from 'lodash';
import { ConfigDumpData } from '../data/configDump/configDump'; import { ConfigDumpData } from '../data/configDump/configDump';
import { Data, hasSyncedInitialConfigurationItem } from '../data/data'; import { Data } from '../data/data';
import { ConversationInteraction } from '../interactions'; import { ConversationInteraction } from '../interactions';
import { ConversationTypeEnum } from '../models/conversationAttributes'; import { ConversationTypeEnum } from '../models/conversationAttributes';
import { SignalService } from '../protobuf'; import { SignalService } from '../protobuf';
@ -27,7 +27,11 @@ import { configurationMessageReceived, trigger } from '../shims/events';
import { assertUnreachable } from '../types/sqlSharedTypes'; import { assertUnreachable } from '../types/sqlSharedTypes';
import { BlockedNumberController } from '../util'; import { BlockedNumberController } from '../util';
import { Registration } from '../util/registration'; import { Registration } from '../util/registration';
import { getLastProfileUpdateTimestamp, setLastProfileUpdateTimestamp } from '../util/storage'; import {
Storage,
getLastProfileUpdateTimestamp,
setLastProfileUpdateTimestamp,
} from '../util/storage';
import { ConfigWrapperObjectTypes } from '../webworker/workers/browser/libsession_worker_functions'; import { ConfigWrapperObjectTypes } from '../webworker/workers/browser/libsession_worker_functions';
import { import {
ContactsWrapperActions, ContactsWrapperActions,
@ -703,8 +707,19 @@ async function handleGroupsAndContactsFromConfigMessageLegacy(
return; return;
} }
const envelopeTimestamp = toNumber(envelope.timestamp); const envelopeTimestamp = toNumber(envelope.timestamp);
const lastConfigUpdate = await Data.getItemById(hasSyncedInitialConfigurationItem);
const lastConfigTimestamp = lastConfigUpdate?.timestamp; // at some point, we made the hasSyncedInitialConfigurationItem item to have a value=true and a timestamp set.
// we can actually just use the timestamp as a boolean, as if it is set, we know we have synced the initial config
// but we still need to handle the case where the timestamp was set when the value is true (for backwards compatiblity, until we get rid of the config message legacy)
const lastConfigUpdate = await Data.getItemById(SettingsKey.hasSyncedInitialConfigurationItem);
let lastConfigTimestamp: number | undefined;
if (isNumber(lastConfigUpdate?.value)) {
lastConfigTimestamp = lastConfigUpdate?.value;
} else if (isNumber((lastConfigUpdate as any)?.timestamp)) {
lastConfigTimestamp = (lastConfigUpdate as any)?.timestamp; // ugly, but we can remove it once we dropped support for legacy config message, see comment above
}
const isNewerConfig = const isNewerConfig =
!lastConfigTimestamp || (lastConfigTimestamp && lastConfigTimestamp < envelopeTimestamp); !lastConfigTimestamp || (lastConfigTimestamp && lastConfigTimestamp < envelopeTimestamp);
@ -713,11 +728,7 @@ async function handleGroupsAndContactsFromConfigMessageLegacy(
return; return;
} }
await Data.createOrUpdateItem({ await Storage.put(SettingsKey.hasSyncedInitialConfigurationItem, envelopeTimestamp);
id: 'hasSyncedInitialConfigurationItem',
value: true,
timestamp: envelopeTimestamp,
});
// we only want to apply changes to closed groups if we never got them // we only want to apply changes to closed groups if we never got them
// new opengroups get added when we get a new closed group message from someone, or a sync'ed message from outself creating the group // new opengroups get added when we get a new closed group message from someone, or a sync'ed message from outself creating the group

@ -13,6 +13,7 @@ import { SogsBlinding } from './sogsBlinding';
import { fromHexToArray } from '../../../utils/String'; import { fromHexToArray } from '../../../utils/String';
import { KNOWN_BLINDED_KEYS_ITEM } from '../../../../data/settings-key'; import { KNOWN_BLINDED_KEYS_ITEM } from '../../../../data/settings-key';
import { roomHasBlindEnabled } from '../../../../types/sqlSharedTypes'; import { roomHasBlindEnabled } from '../../../../types/sqlSharedTypes';
import { Storage } from '../../../../util/storage';
export type BlindedIdMapping = { export type BlindedIdMapping = {
blindedId: string; blindedId: string;
@ -65,10 +66,7 @@ export async function loadKnownBlindedKeys() {
*/ */
export async function writeKnownBlindedKeys() { export async function writeKnownBlindedKeys() {
if (cachedKnownMapping && cachedKnownMapping.length) { if (cachedKnownMapping && cachedKnownMapping.length) {
await Data.createOrUpdateItem({ await Storage.put(KNOWN_BLINDED_KEYS_ITEM, JSON.stringify(cachedKnownMapping));
id: KNOWN_BLINDED_KEYS_ITEM,
value: JSON.stringify(cachedKnownMapping),
});
} }
} }

@ -1,9 +1,13 @@
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import { Data } from '../../../data/data'; import { Data } from '../../../data/data';
import { Storage } from '../../../util/storage';
let hasSeenHardfork190: boolean | undefined; let hasSeenHardfork190: boolean | undefined;
let hasSeenHardfork191: boolean | undefined; let hasSeenHardfork191: boolean | undefined;
const hasSeenHardfork190ItemId = 'hasSeenHardfork190';
const hasSeenHardfork191ItemId = 'hasSeenHardfork191';
/** /**
* this is only intended for testing. Do not call this in production. * this is only intended for testing. Do not call this in production.
*/ */
@ -11,13 +15,16 @@ export function resetHardForkCachedValues() {
hasSeenHardfork190 = hasSeenHardfork191 = undefined; hasSeenHardfork190 = hasSeenHardfork191 = undefined;
} }
/**
* Not used anymore, but keeping those here in case we ever need to do hardfork enabling of features again
*/
export async function getHasSeenHF190() { export async function getHasSeenHF190() {
if (hasSeenHardfork190 === undefined) { if (hasSeenHardfork190 === undefined) {
// read values from db and cache them as it looks like we did not // read values from db and cache them as it looks like we did not
const oldHhasSeenHardfork190 = (await Data.getItemById('hasSeenHardfork190'))?.value; const oldHhasSeenHardfork190 = (await Data.getItemById(hasSeenHardfork190ItemId))?.value;
// values do not exist in the db yet. Let's store false for now in the db and update our cached value. // values do not exist in the db yet. Let's store false for now in the db and update our cached value.
if (oldHhasSeenHardfork190 === undefined) { if (oldHhasSeenHardfork190 === undefined) {
await Data.createOrUpdateItem({ id: 'hasSeenHardfork190', value: false }); await Storage.put(hasSeenHardfork190ItemId, false);
hasSeenHardfork190 = false; hasSeenHardfork190 = false;
} else { } else {
hasSeenHardfork190 = oldHhasSeenHardfork190; hasSeenHardfork190 = oldHhasSeenHardfork190;
@ -26,14 +33,17 @@ export async function getHasSeenHF190() {
return hasSeenHardfork190; return hasSeenHardfork190;
} }
/**
* Not used anymore, but keeping those here in case we ever need to do hardfork enabling of features again
*/
export async function getHasSeenHF191() { export async function getHasSeenHF191() {
if (hasSeenHardfork191 === undefined) { if (hasSeenHardfork191 === undefined) {
// read values from db and cache them as it looks like we did not // read values from db and cache them as it looks like we did not
const oldHhasSeenHardfork191 = (await Data.getItemById('hasSeenHardfork191'))?.value; const oldHhasSeenHardfork191 = (await Data.getItemById(hasSeenHardfork191ItemId))?.value;
// values do not exist in the db yet. Let's store false for now in the db and update our cached value. // values do not exist in the db yet. Let's store false for now in the db and update our cached value.
if (oldHhasSeenHardfork191 === undefined) { if (oldHhasSeenHardfork191 === undefined) {
await Data.createOrUpdateItem({ id: 'hasSeenHardfork191', value: false }); await Storage.put(hasSeenHardfork191ItemId, false);
hasSeenHardfork191 = false; hasSeenHardfork191 = false;
} else { } else {
hasSeenHardfork191 = oldHhasSeenHardfork191; hasSeenHardfork191 = oldHhasSeenHardfork191;
@ -45,18 +55,18 @@ export async function getHasSeenHF191() {
export async function handleHardforkResult(json: Record<string, any>) { export async function handleHardforkResult(json: Record<string, any>) {
if (hasSeenHardfork190 === undefined || hasSeenHardfork191 === undefined) { if (hasSeenHardfork190 === undefined || hasSeenHardfork191 === undefined) {
// read values from db and cache them as it looks like we did not // read values from db and cache them as it looks like we did not
const oldHhasSeenHardfork190 = (await Data.getItemById('hasSeenHardfork190'))?.value; const oldHhasSeenHardfork190 = (await Data.getItemById(hasSeenHardfork190ItemId))?.value;
const oldHasSeenHardfork191 = (await Data.getItemById('hasSeenHardfork191'))?.value; const oldHasSeenHardfork191 = (await Data.getItemById(hasSeenHardfork191ItemId))?.value;
// values do not exist in the db yet. Let's store false for now in the db and update our cached value. // values do not exist in the db yet. Let's store false for now in the db and update our cached value.
if (oldHhasSeenHardfork190 === undefined) { if (oldHhasSeenHardfork190 === undefined) {
await Data.createOrUpdateItem({ id: 'hasSeenHardfork190', value: false }); await Storage.put(hasSeenHardfork190ItemId, false);
hasSeenHardfork190 = false; hasSeenHardfork190 = false;
} else { } else {
hasSeenHardfork190 = oldHhasSeenHardfork190; hasSeenHardfork190 = oldHhasSeenHardfork190;
} }
if (oldHasSeenHardfork191 === undefined) { if (oldHasSeenHardfork191 === undefined) {
await Data.createOrUpdateItem({ id: 'hasSeenHardfork191', value: false }); await Storage.put(hasSeenHardfork191ItemId, false);
hasSeenHardfork191 = false; hasSeenHardfork191 = false;
} else { } else {
hasSeenHardfork191 = oldHasSeenHardfork191; hasSeenHardfork191 = oldHasSeenHardfork191;
@ -78,13 +88,11 @@ export async function handleHardforkResult(json: Record<string, any>) {
isNumber(json.hf[1]) isNumber(json.hf[1])
) { ) {
if (!hasSeenHardfork190 && json.hf[0] >= 19 && json.hf[1] >= 0) { if (!hasSeenHardfork190 && json.hf[0] >= 19 && json.hf[1] >= 0) {
// window.log.info('[HF]: We just detected HF 19.0 on "retrieve"'); await Storage.put(hasSeenHardfork190ItemId, true);
await Data.createOrUpdateItem({ id: 'hasSeenHardfork190', value: true });
hasSeenHardfork190 = true; hasSeenHardfork190 = true;
} }
if (!hasSeenHardfork191 && json.hf[0] >= 19 && json.hf[1] >= 1) { if (!hasSeenHardfork191 && json.hf[0] >= 19 && json.hf[1] >= 1) {
// window.log.info('[HF]: We just detected HF 19.1 on "retrieve"'); await Storage.put(hasSeenHardfork191ItemId, true);
await Data.createOrUpdateItem({ id: 'hasSeenHardfork191', value: true });
hasSeenHardfork191 = true; hasSeenHardfork191 = true;
} }
} }

@ -29,7 +29,7 @@ export class ExpirationTimerUpdateMessage extends DataMessage {
data.flags = SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE; data.flags = SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE;
// FIXME we shouldn't need this once android recieving refactor is done. // TODO we shouldn't need this once android recieving refactor is done.
// the envelope stores the groupId for a closed group already. // the envelope stores the groupId for a closed group already.
if (this.groupId) { if (this.groupId) {
const groupMessage = new SignalService.GroupContext(); const groupMessage = new SignalService.GroupContext();

@ -5,6 +5,7 @@ import { ContentMessage } from '../messages/outgoing';
import { PubKey } from '../types'; import { PubKey } from '../types';
import { MessageUtils } from '../utils'; import { MessageUtils } from '../utils';
import { SnodeNamespaces } from '../apis/snode_api/namespaces'; import { SnodeNamespaces } from '../apis/snode_api/namespaces';
import { Storage } from '../../util/storage';
// This is an abstraction for storing pending messages. // This is an abstraction for storing pending messages.
// Ideally we want to store pending messages in the database so that // Ideally we want to store pending messages in the database so that
@ -140,9 +141,6 @@ export class PendingMessageCache {
}); });
const encodedPendingMessages = JSON.stringify(encodedCache) || '[]'; const encodedPendingMessages = JSON.stringify(encodedCache) || '[]';
await Data.createOrUpdateItem({ await Storage.put('pendingMessages', encodedPendingMessages);
id: 'pendingMessages',
value: encodedPendingMessages,
});
} }
} }

@ -11,6 +11,7 @@ import {
RunJobResult, RunJobResult,
TypeOfPersistedData, TypeOfPersistedData,
} from './PersistedJob'; } from './PersistedJob';
import { Storage } from '../../../util/storage';
/** /**
* 'job_in_progress' if there is already a job in progress * 'job_in_progress' if there is already a job in progress
@ -177,10 +178,7 @@ export class PersistedJobRunner<T extends TypeOfPersistedData> {
private async writeJobsToDB() { private async writeJobsToDB() {
const serialized = this.getSerializedJobs(); const serialized = this.getSerializedJobs();
window.log.debug(`writing to db for "${this.jobRunnerType}": `, serialized); window.log.debug(`writing to db for "${this.jobRunnerType}": `, serialized);
await Data.createOrUpdateItem({ await Storage.put(this.getJobRunnerItemId(), JSON.stringify(serialized));
id: this.getJobRunnerItemId(),
value: JSON.stringify(serialized),
});
} }
private async addJobUnchecked(job: PersistedJob<T>) { private async addJobUnchecked(job: PersistedJob<T>) {

@ -31,6 +31,7 @@ import { ConfigurationDumpSync } from '../job_runners/jobs/ConfigurationSyncDump
import { ConfigurationSync } from '../job_runners/jobs/ConfigurationSyncJob'; import { ConfigurationSync } from '../job_runners/jobs/ConfigurationSyncJob';
import { fromBase64ToArray, fromHexToArray } from '../String'; import { fromBase64ToArray, fromHexToArray } from '../String';
import { getCompleteUrlFromRoom } from '../../apis/open_group_api/utils/OpenGroupUtils'; import { getCompleteUrlFromRoom } from '../../apis/open_group_api/utils/OpenGroupUtils';
import { Storage } from '../../../util/storage';
const ITEM_ID_LAST_SYNC_TIMESTAMP = 'lastSyncedTimestamp'; const ITEM_ID_LAST_SYNC_TIMESTAMP = 'lastSyncedTimestamp';
@ -38,7 +39,7 @@ const getLastSyncTimestampFromDb = async (): Promise<number | undefined> =>
(await Data.getItemById(ITEM_ID_LAST_SYNC_TIMESTAMP))?.value; (await Data.getItemById(ITEM_ID_LAST_SYNC_TIMESTAMP))?.value;
const writeLastSyncTimestampToDb = async (timestamp: number) => const writeLastSyncTimestampToDb = async (timestamp: number) =>
Data.createOrUpdateItem({ id: ITEM_ID_LAST_SYNC_TIMESTAMP, value: timestamp }); Storage.put(ITEM_ID_LAST_SYNC_TIMESTAMP, timestamp);
/** /**
* Conditionally Syncs user configuration with other devices linked. * Conditionally Syncs user configuration with other devices linked.
@ -73,6 +74,7 @@ export const syncConfigurationIfNeeded = async () => {
} else { } else {
await ConfigurationDumpSync.queueNewJobIfNeeded(); await ConfigurationDumpSync.queueNewJobIfNeeded();
await ConfigurationSync.queueNewJobIfNeeded(); await ConfigurationSync.queueNewJobIfNeeded();
await ConfigurationDumpSync.queueNewJobIfNeeded();
} }
}; };

@ -0,0 +1,73 @@
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
import { SettingsKey } from '../../data/settings-key';
import { isBoolean } from 'lodash';
import { Storage } from '../../util/storage';
const SettingsBoolsKeyTrackedInRedux = [
SettingsKey.someDeviceOutdatedSyncing,
SettingsKey.settingsLinkPreview,
] as const;
export type SettingsState = {
settingsBools: Record<typeof SettingsBoolsKeyTrackedInRedux[number], boolean>;
};
export function getSettingsInitialState() {
return {
settingsBools: {
someDeviceOutdatedSyncing: false,
'link-preview-setting': false, // this is the value of SettingsKey.settingsLinkPreview
},
};
}
function isTrackedBoolean(key: string): key is typeof SettingsBoolsKeyTrackedInRedux[number] {
return SettingsBoolsKeyTrackedInRedux.indexOf(key as any) !== -1;
}
/**
* This slice is the one holding the settings of the currently logged in user in redux.
* This is in addition to the settings stored in the Storage class but is a memory only representation of them.
* You should not try to make changes here, but instead through the Storage class.
* What you can do with this slice, is to create selectors and hooks to keep your UI in sync with the state in whatever is Storage.
*/
const settingsSlice = createSlice({
name: 'settings',
// when this createSlice gets invoke, the storage is not ready, but redux still wants a state so we just avoid hitting the storage.
// Once the storage is ready,
initialState: getSettingsInitialState(),
reducers: {
updateAllOnStorageReady(state) {
const linkPreview = Storage.get(SettingsKey.settingsLinkPreview, false);
const outdatedSync = Storage.get(SettingsKey.someDeviceOutdatedSyncing, false);
state.settingsBools.someDeviceOutdatedSyncing = isBoolean(outdatedSync)
? outdatedSync
: false;
state.settingsBools['link-preview-setting'] = isBoolean(linkPreview) ? linkPreview : false; // this is the value of SettingsKey.settingsLinkPreview
return state;
},
updateSettingsBoolValue(state, action: PayloadAction<{ id: string; value: boolean }>) {
const { id, value } = action.payload;
if (!isTrackedBoolean(id) || !isBoolean(value)) return state;
state.settingsBools[id] = value;
return state;
},
deleteSettingsBoolValue(state, action: PayloadAction<string>) {
if (!isTrackedBoolean(action.payload)) return state;
delete state.settingsBools[action.payload];
return state;
},
},
});
const { actions, reducer } = settingsSlice;
export const {
updateSettingsBoolValue,
deleteSettingsBoolValue,
updateAllOnStorageReady,
} = actions;
export const settingsReducer = reducer;

@ -19,6 +19,7 @@ import {
StagedAttachmentsStateType, StagedAttachmentsStateType,
} from './ducks/stagedAttachments'; } from './ducks/stagedAttachments';
import { PrimaryColorStateType, ThemeStateType } from '../themes/constants/colors'; import { PrimaryColorStateType, ThemeStateType } from '../themes/constants/colors';
import { settingsReducer, SettingsState } from './ducks/settings';
export type StateType = { export type StateType = {
search: SearchStateType; search: SearchStateType;
@ -35,6 +36,7 @@ export type StateType = {
stagedAttachments: StagedAttachmentsStateType; stagedAttachments: StagedAttachmentsStateType;
call: CallStateType; call: CallStateType;
sogsRoomInfo: SogsRoomInfoState; sogsRoomInfo: SogsRoomInfoState;
settings: SettingsState;
}; };
export const reducers = { export const reducers = {
@ -52,6 +54,7 @@ export const reducers = {
stagedAttachments, stagedAttachments,
call, call,
sogsRoomInfo: ReduxSogsRoomInfos.sogsRoomInfoReducer, sogsRoomInfo: ReduxSogsRoomInfos.sogsRoomInfoReducer,
settings: settingsReducer,
}; };
// Making this work would require that our reducer signature supported AnyAction, not // Making this work would require that our reducer signature supported AnyAction, not

@ -42,6 +42,7 @@ import { filter, isEmpty, isNumber, pick, sortBy } from 'lodash';
import { MessageReactsSelectorProps } from '../../components/conversation/message/message-content/MessageReactions'; import { MessageReactsSelectorProps } from '../../components/conversation/message/message-content/MessageReactions';
import { getModeratorsOutsideRedux } from './sogsRoomInfo'; import { getModeratorsOutsideRedux } from './sogsRoomInfo';
import { getSelectedConversation, getSelectedConversationKey } from './selectedConversation'; import { getSelectedConversation, getSelectedConversationKey } from './selectedConversation';
import { useSelector } from 'react-redux';
export const getConversations = (state: StateType): ConversationsStateType => state.conversations; export const getConversations = (state: StateType): ConversationsStateType => state.conversations;
@ -882,6 +883,11 @@ export const getMessageTextProps = createSelector(getMessagePropsByMessageId, (p
return msgProps; return msgProps;
}); });
export const useMessageIsDeleted = (messageId: string): boolean => {
const props = useSelector((state: StateType) => getMessagePropsByMessageId(state, messageId));
return props?.propsForMessage.isDeleted || false;
};
export const getMessageContextMenuProps = createSelector(getMessagePropsByMessageId, (props): export const getMessageContextMenuProps = createSelector(getMessagePropsByMessageId, (props):
| MessageContextMenuSelectorProps | MessageContextMenuSelectorProps
| undefined => { | undefined => {

@ -80,10 +80,10 @@ export function getSelectedCanWrite(state: StateType) {
if (!selectedConvo) { if (!selectedConvo) {
return false; return false;
} }
const canWrite = getCanWrite(state, selectedConvoPubkey); const canWriteSogs = getCanWrite(state, selectedConvoPubkey);
const { isBlocked, isKickedFromGroup, left, isPublic } = selectedConvo; const { isBlocked, isKickedFromGroup, left, isPublic } = selectedConvo;
return !(isBlocked || isKickedFromGroup || left || (isPublic && !canWrite)); return !(isBlocked || isKickedFromGroup || left || (isPublic && !canWriteSogs));
} }
/** /**

@ -0,0 +1,19 @@
import { useSelector } from 'react-redux';
import { SettingsKey } from '../../data/settings-key';
import { StateType } from '../reducer';
const getLinkPreviewEnabled = (state: StateType) =>
state.settings.settingsBools[SettingsKey.settingsLinkPreview];
const getHasDeviceOutdatedSyncing = (state: StateType) =>
state.settings.settingsBools[SettingsKey.someDeviceOutdatedSyncing];
export const useHasLinkPreviewEnabled = () => {
const value = useSelector(getLinkPreviewEnabled);
return Boolean(value);
};
export const useHasDeviceOutdatedSyncing = () => {
const value = useSelector(getHasDeviceOutdatedSyncing);
return Boolean(value);
};

@ -197,8 +197,10 @@ async function registrationDone(ourPubkey: string, displayName: string) {
await conversation.setIsApproved(true, false); await conversation.setIsApproved(true, false);
await conversation.setDidApproveMe(true, false); await conversation.setDidApproveMe(true, false);
// when onboarding, hide the note to self by default.
await conversation.setHidden(true);
await conversation.commit(); await conversation.commit();
const user = { const user = {
ourNumber: getOurPubKeyStrFromCache(), ourNumber: getOurPubKeyStrFromCache(),
ourPrimary: ourPubkey, ourPrimary: ourPubkey,

@ -1,6 +1,7 @@
import { Data } from '../data/data'; import { Data } from '../data/data';
import { commitConversationAndRefreshWrapper } from '../models/conversation'; import { commitConversationAndRefreshWrapper } from '../models/conversation';
import { PubKey } from '../session/types'; import { PubKey } from '../session/types';
import { Storage } from './storage';
const BLOCKED_NUMBERS_ID = 'blocked'; const BLOCKED_NUMBERS_ID = 'blocked';
@ -112,9 +113,6 @@ export class BlockedNumberController {
} }
private static async saveToDB(id: string, numbers: Set<string>): Promise<void> { private static async saveToDB(id: string, numbers: Set<string>): Promise<void> {
await Data.createOrUpdateItem({ await Storage.put(id, [...numbers]);
id,
value: [...numbers],
});
} }
} }

@ -1,10 +1,12 @@
import { Data } from '../data/data'; import { Data } from '../data/data';
import { SessionKeyPair } from '../receiver/keypairs'; import { SessionKeyPair } from '../receiver/keypairs';
import { DEFAULT_RECENT_REACTS } from '../session/constants'; import { DEFAULT_RECENT_REACTS } from '../session/constants';
import { deleteSettingsBoolValue, updateSettingsBoolValue } from '../state/ducks/settings';
import { isBoolean } from 'lodash';
let ready = false; let ready = false;
type ValueType = string | number | boolean | SessionKeyPair; type ValueType = string | number | boolean | SessionKeyPair | Array<string>;
type InsertedValueType = { id: string; value: ValueType }; type InsertedValueType = { id: string; value: ValueType };
let items: Record<string, InsertedValueType>; let items: Record<string, InsertedValueType>;
let callbacks: Array<() => void> = []; let callbacks: Array<() => void> = [];
@ -23,6 +25,10 @@ async function put(key: string, value: ValueType) {
items[key] = data; items[key] = data;
await Data.createOrUpdateItem(data); await Data.createOrUpdateItem(data);
if (isBoolean(value)) {
window?.inboxStore?.dispatch(updateSettingsBoolValue({ id: key, value }));
}
} }
function get(key: string, defaultValue?: ValueType) { function get(key: string, defaultValue?: ValueType) {
@ -45,6 +51,9 @@ async function remove(key: string) {
// tslint:disable-next-line: no-dynamic-delete // tslint:disable-next-line: no-dynamic-delete
delete items[key]; delete items[key];
window?.inboxStore?.dispatch(deleteSettingsBoolValue(key));
await Data.removeItemById(key); await Data.removeItemById(key);
} }

@ -90,7 +90,6 @@ function initUserWrapper(
const userType = assertUserWrapperType(wrapperType); const userType = assertUserWrapperType(wrapperType);
const wrapper = getUserWrapper(wrapperType); const wrapper = getUserWrapper(wrapperType);
console.warn('initUserWrapper: ', wrapperType, options);
if (wrapper) { if (wrapper) {
throw new Error(`${wrapperType} already init`); throw new Error(`${wrapperType} already init`);
} }

Loading…
Cancel
Save