Skip to content

Commit 9f5fd85

Browse files
committed
feat: moved blocked contact to modal
1 parent 57a4cff commit 9f5fd85

19 files changed

+314
-320
lines changed

ts/components/buttons/panel/PanelButton.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ const StyledRoundedPanelButtonGroup = styled.div`
7272
`;
7373

7474
const PanelButtonContainer = styled.div`
75+
--panel-button-container-min-height: 50px;
7576
overflow: auto;
76-
min-height: 50px;
77+
min-height: var(--panel-button-container-min-height);
7778
max-height: 100%;
7879
`;
7980

ts/components/dialog/user-settings/UserSettingsDialog.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { type UserSettingsModalState } from '../../../state/ducks/modalDialog';
2+
import { BlockedContactsSettingsPage } from './pages/BlockedContactsSettingsPage';
23
import { ConversationSettingsPage } from './pages/ConversationSettingsPage';
34
import { DefaultSettingPage } from './pages/DefaultSettingsPage';
45
import { HelpSettingsPage } from './pages/HelpSettingsPage';
56
import { NotificationsSettingsPage } from './pages/NotificationsSettingsPage';
7+
import { PreferencesSettingsPage } from './pages/PreferencesSettingsPage';
68
import { PrivacySettingsPage } from './pages/PrivacySettingsPage';
79

810
export const UserSettingsDialog = (modalState: UserSettingsModalState) => {
@@ -21,6 +23,10 @@ export const UserSettingsDialog = (modalState: UserSettingsModalState) => {
2123
return <ConversationSettingsPage {...modalState} />;
2224
case 'help':
2325
return <HelpSettingsPage {...modalState} />;
26+
case 'preferences':
27+
return <PreferencesSettingsPage {...modalState} />;
28+
case 'blocked-contacts':
29+
return <BlockedContactsSettingsPage {...modalState} />;
2430
default:
2531
return <DefaultSettingPage />;
2632
}

ts/components/dialog/user-settings/components/SettingsChevronBasic.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export function SettingsChevronBasic({
1313
textToken: TokenSimpleNoArgs;
1414
subTextToken: TokenSimpleNoArgs;
1515
baseDataTestId: SettingsChevron;
16-
onClick: () => Promise<void>;
16+
onClick: (() => Promise<void>) | (() => void);
1717
}) {
1818
return (
1919
<PanelChevronButton
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { useState } from 'react';
2+
import useUpdate from 'react-use/lib/useUpdate';
3+
import styled from 'styled-components';
4+
import { useDispatch } from 'react-redux';
5+
6+
import {
7+
updateBlockOrUnblockModal,
8+
type UserSettingsModalState,
9+
} from '../../../../state/ducks/modalDialog';
10+
import { PanelButtonGroup } from '../../../buttons/panel/PanelButton';
11+
import {
12+
ModalBasicHeader,
13+
SessionWrapperModal,
14+
WrapperModalWidth,
15+
} from '../../../SessionWrapperModal';
16+
import { ModalBackButton } from '../../shared/ModalBackButton';
17+
import {
18+
useUserSettingsBackAction,
19+
useUserSettingsCloseAction,
20+
useUserSettingsTitle,
21+
} from './userSettingsHooks';
22+
import { SpacerLG } from '../../../basic/Text';
23+
import { SessionButton, SessionButtonColor } from '../../../basic/SessionButton';
24+
import { tr } from '../../../../localization/localeTools';
25+
import { MemberListItem } from '../../../MemberListItem';
26+
import { Localizer } from '../../../basic/Localizer';
27+
import { BlockedNumberController } from '../../../../util';
28+
29+
const StyledButtonContainer = styled.div`
30+
display: flex;
31+
width: min-content;
32+
flex-direction: column;
33+
padding-inline-start: var(--margins-lg);
34+
`;
35+
36+
const StyledNoBlockedContactsContainer = styled.div`
37+
justify-self: center;
38+
// not pretty, but the only other way I found is to change the style of PanelButtonContainer itself, which I'd like to avoid
39+
height: var(--panel-button-container-min-height);
40+
align-content: center;
41+
`;
42+
43+
const NoBlockedContacts = () => {
44+
return (
45+
<StyledNoBlockedContactsContainer>
46+
<Localizer token="blockBlockedNone" />
47+
</StyledNoBlockedContactsContainer>
48+
);
49+
};
50+
51+
export function BlockedContactsSettingsPage(modalState: UserSettingsModalState) {
52+
const forceUpdate = useUpdate();
53+
const backAction = useUserSettingsBackAction(modalState);
54+
const closeAction = useUserSettingsCloseAction(modalState);
55+
const title = useUserSettingsTitle(modalState);
56+
const dispatch = useDispatch();
57+
const [selectedIds, setSelectedIds] = useState<Array<string>>([]);
58+
59+
async function unBlockThoseUsers() {
60+
if (selectedIds.length) {
61+
dispatch(
62+
updateBlockOrUnblockModal({
63+
action: 'unblock',
64+
pubkeys: selectedIds,
65+
onConfirmed: () => {
66+
// annoying, but until that BlockedList is in redux, we need to force a refresh of this component when a change is made.
67+
setSelectedIds([]);
68+
forceUpdate();
69+
},
70+
})
71+
);
72+
}
73+
}
74+
75+
const blocked = BlockedNumberController.getBlockedNumbers();
76+
const hasBlocked = blocked.length > 0;
77+
const canUnblock = selectedIds.length > 0;
78+
79+
return (
80+
<SessionWrapperModal
81+
headerChildren={
82+
<ModalBasicHeader
83+
title={title}
84+
bigHeader={true}
85+
showExitIcon={true}
86+
extraLeftButton={backAction ? <ModalBackButton onClick={backAction} /> : undefined}
87+
/>
88+
}
89+
onClose={closeAction || undefined}
90+
shouldOverflow={true}
91+
allowOutsideClick={false}
92+
$contentMinWidth={WrapperModalWidth.normal}
93+
>
94+
<PanelButtonGroup>
95+
{!blocked.length ? (
96+
<NoBlockedContacts />
97+
) : (
98+
blocked.map(blockedEntry => {
99+
return (
100+
<MemberListItem
101+
key={`blocked-list-item-${blockedEntry}`}
102+
pubkey={blockedEntry}
103+
isSelected={selectedIds.includes(blockedEntry)}
104+
onSelect={() => {
105+
setSelectedIds([...selectedIds, blockedEntry]);
106+
}}
107+
onUnselect={() => {
108+
setSelectedIds(selectedIds.filter(id => id !== blockedEntry));
109+
}}
110+
disableBg={true}
111+
/>
112+
);
113+
})
114+
)}
115+
</PanelButtonGroup>
116+
{hasBlocked ? (
117+
<StyledButtonContainer>
118+
<SpacerLG />
119+
<SessionButton
120+
text={tr('blockUnblock')}
121+
onClick={unBlockThoseUsers}
122+
buttonColor={SessionButtonColor.Danger}
123+
dataTestId="unblock-button-settings-screen"
124+
disabled={!canUnblock}
125+
/>
126+
</StyledButtonContainer>
127+
) : null}
128+
</SessionWrapperModal>
129+
);
130+
}

ts/components/dialog/user-settings/pages/ConversationSettingsPage.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import useUpdate from 'react-use/lib/useUpdate';
22
import { useDispatch, useSelector } from 'react-redux';
33

4-
import { type UserSettingsModalState } from '../../../../state/ducks/modalDialog';
4+
import {
5+
userSettingsModal,
6+
type UserSettingsModalState,
7+
} from '../../../../state/ducks/modalDialog';
58
import { PanelButtonGroup, PanelLabelWithDescription } from '../../../buttons/panel/PanelButton';
69
import {
710
ModalBasicHeader,
@@ -111,7 +114,7 @@ export function ConversationSettingsPage(modalState: UserSettingsModalState) {
111114
<SettingsChevronBasic
112115
baseDataTestId="blocked-contacts"
113116
onClick={() => {
114-
throw new Error('Not implemented');
117+
dispatch(userSettingsModal({ userSettingsPage: 'blocked-contacts' }));
115118
}}
116119
textToken={'conversationsBlockedContacts'}
117120
subTextToken={'blockedContactsManageDescription'}

ts/components/dialog/user-settings/pages/DefaultSettingsPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ function SettingsSection() {
206206
iconElement={<LucideIconForSettings unicode={LUCIDE_ICONS_UNICODE.SETTINGS} />}
207207
text={tr('preferences')}
208208
onClick={() => {
209-
throw new Error('Not implemented');
209+
dispatch(userSettingsModal({ userSettingsPage: 'preferences' }));
210210
}}
211211
dataTestId="preferences-settings-menu-item"
212212
/>

ts/components/dialog/user-settings/pages/NotificationsSettingsPage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ function NotificationsContent({
126126
<PanelButtonTextWithSubText
127127
text={text}
128128
subText={subText}
129-
textDataTestId="disappearing-messages-menu-option"
130-
subTextDataTestId="disappearing-messages-timer-menu-option"
129+
textDataTestId={`set-notifications-${value}-settings-text`}
130+
subTextDataTestId={`set-notifications-${value}-settings-sub-text`}
131131
/>
132132
}
133133
value={value}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import useUpdate from 'react-use/lib/useUpdate';
2+
3+
import { type UserSettingsModalState } from '../../../../state/ducks/modalDialog';
4+
import {
5+
PanelButtonGroup,
6+
PanelButtonTextWithSubText,
7+
PanelLabelWithDescription,
8+
} from '../../../buttons/panel/PanelButton';
9+
import {
10+
ModalBasicHeader,
11+
SessionWrapperModal,
12+
WrapperModalWidth,
13+
} from '../../../SessionWrapperModal';
14+
import { ModalBackButton } from '../../shared/ModalBackButton';
15+
import {
16+
useUserSettingsBackAction,
17+
useUserSettingsCloseAction,
18+
useUserSettingsTitle,
19+
} from './userSettingsHooks';
20+
import { SettingsToggleBasic } from '../components/SettingsToggleBasic';
21+
import { SettingsKey } from '../../../../data/settings-key';
22+
import { ToastUtils } from '../../../../session/utils';
23+
import { tr } from '../../../../localization/localeTools';
24+
import { PanelRadioButton } from '../../../buttons/panel/PanelRadioButton';
25+
import { useHasEnterSendEnabled } from '../../../../state/selectors/settings';
26+
27+
async function toggleStartInTray() {
28+
try {
29+
const newValue = !(await window.getStartInTray());
30+
31+
// make sure to write it here too, as this is the value used on the UI to mark the toggle as true/false
32+
await window.setSettingValue(SettingsKey.settingsStartInTray, newValue);
33+
await window.setStartInTray(newValue);
34+
if (!newValue) {
35+
ToastUtils.pushRestartNeeded();
36+
}
37+
} catch (e) {
38+
window.log.warn('start in tray change error:', e);
39+
}
40+
}
41+
42+
function SendWithShiftEnter() {
43+
const initialSetting = useHasEnterSendEnabled();
44+
const selectedWithSettingTrue = 'enterForNewLine';
45+
const selectedWithSettingFalse = 'enterForSend';
46+
const forceUpdate = useUpdate();
47+
48+
const selected = initialSetting ? selectedWithSettingTrue : selectedWithSettingFalse;
49+
50+
const items = [
51+
{
52+
text: tr('conversationsSendWithEnterKey'),
53+
subText: tr('conversationsSendWithEnterKeyDescription'),
54+
value: selectedWithSettingFalse,
55+
},
56+
{
57+
text: tr('conversationsSendWithShiftEnter'),
58+
subText: tr('conversationsEnterNewLine'),
59+
value: selectedWithSettingTrue,
60+
},
61+
] as const;
62+
63+
return (
64+
<>
65+
<PanelLabelWithDescription title={{ token: 'conversationsEnter' }} />
66+
<PanelButtonGroup>
67+
{items.map(({ value, text, subText }) => {
68+
return (
69+
<PanelRadioButton
70+
key={value}
71+
textElement={
72+
<PanelButtonTextWithSubText
73+
text={text}
74+
subText={subText}
75+
textDataTestId={`send-with-${value}-settings-text`}
76+
subTextDataTestId={`send-with-${value}-settings-sub-text`}
77+
/>
78+
}
79+
value={value}
80+
isSelected={selected === value}
81+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
82+
onSelect={async () => {
83+
await window.setSettingValue(
84+
SettingsKey.hasShiftSendEnabled,
85+
value === selectedWithSettingTrue
86+
);
87+
forceUpdate();
88+
}}
89+
rowDataTestId={`send-with-${value}-settings-row`}
90+
radioInputDataTestId={`send-with-${value}-settings-radio`}
91+
/>
92+
);
93+
})}
94+
</PanelButtonGroup>
95+
</>
96+
);
97+
}
98+
99+
export function PreferencesSettingsPage(modalState: UserSettingsModalState) {
100+
const backAction = useUserSettingsBackAction(modalState);
101+
const closeAction = useUserSettingsCloseAction(modalState);
102+
const title = useUserSettingsTitle(modalState);
103+
const isStartInTrayActive = Boolean(window.getSettingValue(SettingsKey.settingsStartInTray));
104+
const forceUpdate = useUpdate();
105+
106+
return (
107+
<SessionWrapperModal
108+
headerChildren={
109+
<ModalBasicHeader
110+
title={title}
111+
bigHeader={true}
112+
showExitIcon={true}
113+
extraLeftButton={backAction ? <ModalBackButton onClick={backAction} /> : undefined}
114+
/>
115+
}
116+
onClose={closeAction || undefined}
117+
shouldOverflow={true}
118+
allowOutsideClick={false}
119+
$contentMinWidth={WrapperModalWidth.normal}
120+
>
121+
<PanelLabelWithDescription title={{ token: 'updates' }} />
122+
<PanelButtonGroup>
123+
<SettingsToggleBasic
124+
baseDataTestId="auto-update"
125+
textToken="permissionsAutoUpdate"
126+
subTextToken="permissionsAutoUpdateDescription"
127+
onClick={async () => {
128+
const old = Boolean(window.getSettingValue(SettingsKey.settingsAutoUpdate));
129+
await window.setSettingValue(SettingsKey.settingsAutoUpdate, !old);
130+
forceUpdate();
131+
}}
132+
active={Boolean(window.getSettingValue(SettingsKey.settingsAutoUpdate))}
133+
/>
134+
</PanelButtonGroup>
135+
<PanelLabelWithDescription title={{ token: 'tray' }} />
136+
<PanelButtonGroup>
137+
<SettingsToggleBasic
138+
baseDataTestId="auto-update"
139+
textToken="permissionsKeepInSystemTray"
140+
subTextToken="permissionsKeepInSystemTrayDescription"
141+
onClick={async () => {
142+
await toggleStartInTray();
143+
forceUpdate();
144+
}}
145+
active={isStartInTrayActive}
146+
/>
147+
</PanelButtonGroup>
148+
<SendWithShiftEnter />
149+
</SessionWrapperModal>
150+
);
151+
}

ts/components/dialog/user-settings/pages/userSettingsHooks.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export function useUserSettingsTitle(page: UserSettingsModalState | undefined) {
1717
return tr('sessionNotifications');
1818
case 'clear-data':
1919
return tr('sessionClearData');
20+
case 'blocked-contacts':
21+
return tr('conversationsBlockedContacts');
2022
case 'conversations':
2123
return tr('sessionConversations');
2224
case 'message-requests':
@@ -55,6 +57,7 @@ export function useUserSettingsCloseAction(props: UserSettingsModalState) {
5557
case 'help':
5658
case 'clear-data':
5759
case 'preferences':
60+
case 'blocked-contacts':
5861
return () => dispatch(userSettingsModal(null));
5962

6063
default:

0 commit comments

Comments
 (0)