From 848c97938ce3c13a0098d150b3d295f4e695aa87 Mon Sep 17 00:00:00 2001 From: William Grant Date: Mon, 3 Apr 2023 14:09:05 +0200 Subject: [PATCH] feat: disabled new disappearing message modes behind a timed feature release function --- .../conversation/right-panel/RightPanel.tsx | 30 ++++++++++- .../overlay/OverlayDisappearingMessages.tsx | 41 ++++++++------ ts/state/selectors/conversations.ts | 29 +++++++++- ts/util/releaseFeature.ts | 54 +++++++++++++++++++ 4 files changed, 135 insertions(+), 19 deletions(-) create mode 100644 ts/util/releaseFeature.ts diff --git a/ts/components/conversation/right-panel/RightPanel.tsx b/ts/components/conversation/right-panel/RightPanel.tsx index 223499b84..6a6be1eae 100644 --- a/ts/components/conversation/right-panel/RightPanel.tsx +++ b/ts/components/conversation/right-panel/RightPanel.tsx @@ -1,15 +1,41 @@ -import React from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { getRightOverlayMode } from '../../../state/selectors/section'; +import { checkIsFeatureReleased } from '../../../util/releaseFeature'; import { OverlayDisappearingMessages } from './overlay/OverlayDisappearingMessages'; import { OverlayRightPanelSettings } from './overlay/OverlayRightPanelSettings'; const ClosableOverlay = () => { const rightOverlayMode = useSelector(getRightOverlayMode); + const [showNewDisppearingMessageModes, setShowNewDisppearingMessageModes] = useState(false); + + const checkForFeatureRelease = useCallback(async () => { + const isReleased = await checkIsFeatureReleased('Disappearing Messages V2'); + return isReleased; + }, []); + + useEffect(() => { + let isCancelled = false; + checkForFeatureRelease() + .then(result => { + if (isCancelled) { + return; + } + setShowNewDisppearingMessageModes(result); + }) + .catch(() => { + if (isCancelled) return; + }); + + return () => { + isCancelled = true; + }; + }, [checkForFeatureRelease]); + switch (rightOverlayMode) { case 'disappearing-messages': - return ; + return ; case 'panel-settings': default: return ; diff --git a/ts/components/conversation/right-panel/overlay/OverlayDisappearingMessages.tsx b/ts/components/conversation/right-panel/overlay/OverlayDisappearingMessages.tsx index 9a05fa132..0cf2a8f4d 100644 --- a/ts/components/conversation/right-panel/overlay/OverlayDisappearingMessages.tsx +++ b/ts/components/conversation/right-panel/overlay/OverlayDisappearingMessages.tsx @@ -13,6 +13,7 @@ import { PanelRadioButton } from '../../../buttons/PanelRadioButton'; import { SessionIconButton } from '../../../icon'; import { getSelectedConversationExpirationModes, + getSelectedConversationExpirationModesLocked, getSelectedConversationExpirationSettings, getSelectedConversationKey, } from '../../../../state/selectors/conversations'; @@ -98,7 +99,7 @@ const Header = (props: HeaderProps) => { }; type DisappearingModesProps = { - options: Array; + options: Record; selected?: DisappearingMessageConversationType; setSelected: (value: string) => void; }; @@ -109,36 +110,37 @@ const DisappearingModes = (props: DisappearingModesProps) => { <> {window.i18n('disappearingMessagesModeLabel')} - {options.map((option: DisappearingMessageConversationType) => { + {Object.keys(options).map((mode: DisappearingMessageConversationType) => { const optionI18n = - option === 'legacy' + mode === 'legacy' ? window.i18n('disappearingMessagesModeLegacy') - : option === 'deleteAfterRead' + : mode === 'deleteAfterRead' ? window.i18n('disappearingMessagesModeAfterRead') - : option === 'deleteAfterSend' + : mode === 'deleteAfterSend' ? window.i18n('disappearingMessagesModeAfterSend') : window.i18n('disappearingMessagesModeOff'); const subtitleI18n = - option === 'legacy' + mode === 'legacy' ? window.i18n('disappearingMessagesModeLegacySubtitle') - : option === 'deleteAfterRead' + : mode === 'deleteAfterRead' ? window.i18n('disappearingMessagesModeAfterReadSubtitle') - : option === 'deleteAfterSend' + : mode === 'deleteAfterSend' ? window.i18n('disappearingMessagesModeAfterSendSubtitle') : undefined; return ( { - setSelected(option); + setSelected(mode); }} - disableBg={true} + disabled={options[mode]} + noBackgroundColor={true} /> ); })} @@ -173,7 +175,7 @@ const TimeOptions = (props: TimerOptionsProps) => { onSelect={() => { setSelected(option.value); }} - disableBg={true} + noBackgroundColor={true} /> ))} @@ -181,10 +183,17 @@ const TimeOptions = (props: TimerOptionsProps) => { ); }; -export const OverlayDisappearingMessages = () => { +type OverlayDisappearingMessagesProps = { unlockAllModes: boolean }; + +export const OverlayDisappearingMessages = (props: OverlayDisappearingMessagesProps) => { + const { unlockAllModes } = props; const dispatch = useDispatch(); const selectedConversationKey = useSelector(getSelectedConversationKey); - const disappearingModeOptions = useSelector(getSelectedConversationExpirationModes); + const disappearingModeOptions = useSelector( + unlockAllModes + ? getSelectedConversationExpirationModes + : getSelectedConversationExpirationModesLocked + ); const convoProps = useSelector(getSelectedConversationExpirationSettings); diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 524be7036..dd00ca10b 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -1193,7 +1193,34 @@ export const getSelectedConversationExpirationModes = createSelector( // Legacy mode is the 2nd option in the UI modes = [modes[0], modes[modes.length - 1], ...modes.slice(1, modes.length - 1)]; - return modes; + const modesWithDisabledState: any = {}; + + if (modes && modes.length > 1) { + modes.forEach(mode => { + modesWithDisabledState[mode] = false; + }); + } + + return modesWithDisabledState; + } +); + +export const getSelectedConversationExpirationModesLocked = createSelector( + getSelectedConversationExpirationModes, + (modes: any | undefined) => { + const modesWithDisabledState: any = {}; + + if (modes && Object.keys(modes).length > 1) { + Object.keys(modes).forEach(mode => { + let result = false; + if (mode !== 'legacy') { + result = true; + } + modesWithDisabledState[mode] = result; + }); + } + + return modesWithDisabledState; } ); diff --git a/ts/util/releaseFeature.ts b/ts/util/releaseFeature.ts new file mode 100644 index 000000000..385d989a6 --- /dev/null +++ b/ts/util/releaseFeature.ts @@ -0,0 +1,54 @@ +import { Data } from '../data/data'; + +// TODO update to agreed value between platforms +const featureReleaseTimestamp = 1676851200000; // unix 13/02/2023 +// const featureReleaseTimestamp = 1676608378; // test value +let isFeatureReleased: boolean | undefined; + +/** + * this is only intended for testing. Do not call this in production. + */ +export function resetFeatureReleasedCachedValue() { + isFeatureReleased = undefined; +} + +export async function getIsFeatureReleased(): Promise { + if (isFeatureReleased === undefined) { + // read values from db and cache them as it looks like we did not + const oldIsFeatureReleased = (await Data.getItemById('featureReleased'))?.value; + // values do not exist in the db yet. Let's store false for now in the db and update our cached value. + if (oldIsFeatureReleased === undefined) { + await Data.createOrUpdateItem({ id: 'featureReleased', value: false }); + isFeatureReleased = false; + } else { + isFeatureReleased = oldIsFeatureReleased; + } + } + return Boolean(isFeatureReleased); +} + +export async function checkIsFeatureReleased(featureName: string): Promise { + if (isFeatureReleased === undefined) { + const featureAlreadyReleased = await getIsFeatureReleased(); + + // Is it time to release the feature? + if (Date.now() >= featureReleaseTimestamp) { + if (featureAlreadyReleased) { + // Feature is already released and we don't need to update the db + window.log.info(`WIP: [releaseFeature]: ${featureName} is released`); + } else { + window.log.info( + `WIP: [releaseFeature]: It is time to release ${featureName}. Releasing it now` + ); + await Data.createOrUpdateItem({ + id: 'featureReleased', + value: true, + }); + } + return true; + } + } + + window.log.info(`WIP: [releaseFeature]: ${featureName} has not been released yet`); + return false; +}