From 776220c31fce885e3b509d1ac2fe5c9789fd1138 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 24 Sep 2024 14:03:43 +0100 Subject: [PATCH 01/31] start building storybook --- .../Settings/components/SettingsList.tsx | 22 +++++++++++++++++++ src/view/screens/Storybook/Settings.tsx | 21 ++++++++++++++++++ src/view/screens/Storybook/index.tsx | 3 +++ 3 files changed, 46 insertions(+) create mode 100644 src/screens/Settings/components/SettingsList.tsx create mode 100644 src/view/screens/Storybook/Settings.tsx diff --git a/src/screens/Settings/components/SettingsList.tsx b/src/screens/Settings/components/SettingsList.tsx new file mode 100644 index 00000000000..22d1734bc0c --- /dev/null +++ b/src/screens/Settings/components/SettingsList.tsx @@ -0,0 +1,22 @@ +import React from 'react' + +import * as Button from '#/components/Button' +import {Link, LinkProps} from '#/components/Link' + +export function LinkItem({style, ...props}: LinkProps) { + return +} + +export function ItemIcon( + props: React.ComponentProps, +) { + return +} + +export function ItemText({ + // eslint-disable-next-line react/prop-types + style, + ...props +}: React.ComponentProps) { + return +} diff --git a/src/view/screens/Storybook/Settings.tsx b/src/view/screens/Storybook/Settings.tsx new file mode 100644 index 00000000000..a3f5686894b --- /dev/null +++ b/src/view/screens/Storybook/Settings.tsx @@ -0,0 +1,21 @@ +import React from 'react' + +import * as SettingsList from '#/screens/Settings/components/SettingsList' +import {Person_Stroke2_Corner0_Rounded as PersonIcon} from '#/components/icons/Person' + +export function Settings() { + return ( + <> + + + Account + + + + Accessibilty & appearance + + + ) +} diff --git a/src/view/screens/Storybook/index.tsx b/src/view/screens/Storybook/index.tsx index f1152fb7e4a..45b4b94e6a5 100644 --- a/src/view/screens/Storybook/index.tsx +++ b/src/view/screens/Storybook/index.tsx @@ -16,6 +16,7 @@ import {Forms} from './Forms' import {Icons} from './Icons' import {Links} from './Links' import {Menus} from './Menus' +import {Settings} from './Settings' import {Shadows} from './Shadows' import {Spacing} from './Spacing' import {Theming} from './Theming' @@ -88,6 +89,8 @@ function StorybookInner() { + + From 28c4835137b2f33d0a8030d8f20a3b6f45d34f47 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 24 Sep 2024 15:57:48 +0100 Subject: [PATCH 02/31] add title --- src/view/screens/Storybook/Settings.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/view/screens/Storybook/Settings.tsx b/src/view/screens/Storybook/Settings.tsx index a3f5686894b..93b7314fab3 100644 --- a/src/view/screens/Storybook/Settings.tsx +++ b/src/view/screens/Storybook/Settings.tsx @@ -2,10 +2,12 @@ import React from 'react' import * as SettingsList from '#/screens/Settings/components/SettingsList' import {Person_Stroke2_Corner0_Rounded as PersonIcon} from '#/components/icons/Person' +import {Text} from '#/components/Typography' export function Settings() { return ( <> + Settings Account From 2db4084b6cda4f21f7fcf858c0fa7af977943df8 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 24 Sep 2024 16:23:44 +0100 Subject: [PATCH 03/31] add some styles --- .../Settings/components/SettingsList.tsx | 60 ++++++++++++++++--- src/view/screens/Storybook/Settings.tsx | 7 ++- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/src/screens/Settings/components/SettingsList.tsx b/src/screens/Settings/components/SettingsList.tsx index 22d1734bc0c..af10c7fa9ac 100644 --- a/src/screens/Settings/components/SettingsList.tsx +++ b/src/screens/Settings/components/SettingsList.tsx @@ -1,16 +1,60 @@ import React from 'react' +import {View} from 'react-native' +import {atoms as a, useTheme} from '#/alf' import * as Button from '#/components/Button' +import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRightIcon} from '#/components/icons/Chevron' import {Link, LinkProps} from '#/components/Link' -export function LinkItem({style, ...props}: LinkProps) { - return +export function LinkItem({style, children, ...props}: LinkProps) { + const t = useTheme() + return ( + + {args => ( + + {typeof children === 'function' ? children(args) : children} + + + + )} + + ) } -export function ItemIcon( - props: React.ComponentProps, -) { - return +export function ItemIcon({ + icon: Comp, + size = 'xl', +}: Omit, 'position'>) { + const t = useTheme() + + /* + * Copied here from icons/common.tsx so we can tweak if we need to, but + * also so that we can calculate transforms. + */ + const iconSize = { + xs: 12, + sm: 16, + md: 20, + lg: 24, + xl: 28, + '2xl': 32, + }[size] + + return ( + + + + ) } export function ItemText({ @@ -18,5 +62,7 @@ export function ItemText({ style, ...props }: React.ComponentProps) { - return + return ( + + ) } diff --git a/src/view/screens/Storybook/Settings.tsx b/src/view/screens/Storybook/Settings.tsx index 93b7314fab3..dc24f97d7f1 100644 --- a/src/view/screens/Storybook/Settings.tsx +++ b/src/view/screens/Storybook/Settings.tsx @@ -1,4 +1,5 @@ import React from 'react' +import {View} from 'react-native' import * as SettingsList from '#/screens/Settings/components/SettingsList' import {Person_Stroke2_Corner0_Rounded as PersonIcon} from '#/components/icons/Person' @@ -6,8 +7,8 @@ import {Text} from '#/components/Typography' export function Settings() { return ( - <> - Settings + + Settings Account @@ -18,6 +19,6 @@ export function Settings() { Accessibilty & appearance - + ) } From b630f112f9e799260beada279c3c094157185914 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 14 Oct 2024 14:32:18 +0300 Subject: [PATCH 04/31] try out new icons --- ...circleQuestion_stroke2_corner2_rounded.svg | 2 +- .../icons/earth_stroke2_corner2_rounded.svg | 1 + ...ingHand4finger_stroke2_corner2_rounded.svg | 1 + src/components/icons/CircleQuestion.tsx | 2 +- src/components/icons/Globe.tsx | 4 ++ src/components/icons/RaisingHand.tsx | 6 ++- .../Settings/components/SettingsList.tsx | 22 ++++++++--- src/view/screens/Storybook/Settings.tsx | 38 +++++++++++++++++-- 8 files changed, 65 insertions(+), 11 deletions(-) create mode 100644 assets/icons/earth_stroke2_corner2_rounded.svg create mode 100644 assets/icons/raisingHand4finger_stroke2_corner2_rounded.svg diff --git a/assets/icons/circleQuestion_stroke2_corner2_rounded.svg b/assets/icons/circleQuestion_stroke2_corner2_rounded.svg index a534f987160..c025a14509e 100644 --- a/assets/icons/circleQuestion_stroke2_corner2_rounded.svg +++ b/assets/icons/circleQuestion_stroke2_corner2_rounded.svg @@ -1 +1 @@ - + diff --git a/assets/icons/earth_stroke2_corner2_rounded.svg b/assets/icons/earth_stroke2_corner2_rounded.svg new file mode 100644 index 00000000000..b8922629a31 --- /dev/null +++ b/assets/icons/earth_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/raisingHand4finger_stroke2_corner2_rounded.svg b/assets/icons/raisingHand4finger_stroke2_corner2_rounded.svg new file mode 100644 index 00000000000..f6beb5647d7 --- /dev/null +++ b/assets/icons/raisingHand4finger_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/src/components/icons/CircleQuestion.tsx b/src/components/icons/CircleQuestion.tsx index 4eb369379ba..6242a2b5148 100644 --- a/src/components/icons/CircleQuestion.tsx +++ b/src/components/icons/CircleQuestion.tsx @@ -1,5 +1,5 @@ import {createSinglePathSVG} from './TEMPLATE' export const CircleQuestion_Stroke2_Corner2_Rounded = createSinglePathSVG({ - path: 'M12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Z" clip-rule="evenodd"/> + )} @@ -34,7 +39,11 @@ export function LinkItem({style, children, ...props}: LinkProps) { export function ItemIcon({ icon: Comp, size = 'xl', -}: Omit, 'position'>) { + + color, +}: Omit, 'position'> & { + color?: string +}) { const t = useTheme() /* @@ -52,7 +61,7 @@ export function ItemIcon({ return ( - + ) } @@ -63,6 +72,9 @@ export function ItemText({ ...props }: React.ComponentProps) { return ( - + ) } diff --git a/src/view/screens/Storybook/Settings.tsx b/src/view/screens/Storybook/Settings.tsx index dc24f97d7f1..f858486e37f 100644 --- a/src/view/screens/Storybook/Settings.tsx +++ b/src/view/screens/Storybook/Settings.tsx @@ -2,7 +2,13 @@ import React from 'react' import {View} from 'react-native' import * as SettingsList from '#/screens/Settings/components/SettingsList' -import {Person_Stroke2_Corner0_Rounded as PersonIcon} from '#/components/icons/Person' +import {BubbleInfo_Stroke2_Corner2_Rounded as BubbleInfoIcon} from '#/components/icons/BubbleInfo' +import {CircleQuestion_Stroke2_Corner2_Rounded as CircleQuestionIcon} from '#/components/icons/CircleQuestion' +import {Earth_Stroke2_Corner2_Rounded as EarthIcon} from '#/components/icons/Globe' +import {PaintRoller_Stroke2_Corner2_Rounded as PaintRollerIcon} from '#/components/icons/PaintRoller' +import {Person_Stroke2_Corner2_Rounded as PersonIcon} from '#/components/icons/Person' +import {RaisingHand4Finger_Stroke2_Corner2_Rounded as HandIcon} from '#/components/icons/RaisingHand' +import {Window_Stroke2_Corner2_Rounded as WindowIcon} from '#/components/icons/Window' import {Text} from '#/components/Typography' export function Settings() { @@ -13,11 +19,37 @@ export function Settings() { Account + + + Privacy and security + + + + Moderation + + + + Content and media + - - Accessibilty & appearance + + + Accessibilty and appearance + + + + + Languages + + + + Help + + + + About ) From e38a69b34a7fb4c74f0a0739c2f9a6b25c8eee7c Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 14 Oct 2024 15:33:32 +0300 Subject: [PATCH 05/31] more settings list component parts --- .../Settings/components/SettingsList.tsx | 197 +++++++++++++++--- src/view/screens/Storybook/Settings.tsx | 72 +++++++ 2 files changed, 245 insertions(+), 24 deletions(-) diff --git a/src/screens/Settings/components/SettingsList.tsx b/src/screens/Settings/components/SettingsList.tsx index a9847c574dc..c162f26c06a 100644 --- a/src/screens/Settings/components/SettingsList.tsx +++ b/src/screens/Settings/components/SettingsList.tsx @@ -1,50 +1,111 @@ -import React from 'react' -import {View} from 'react-native' +import React, {useContext, useMemo} from 'react' +import {GestureResponderEvent, StyleProp, View, ViewStyle} from 'react-native' +import {HITSLOP_10} from '#/lib/constants' import {atoms as a, useTheme} from '#/alf' import * as Button from '#/components/Button' import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRightIcon} from '#/components/icons/Chevron' import {Link, LinkProps} from '#/components/Link' +import {Text} from '#/components/Typography' -export function LinkItem({style, children, ...props}: LinkProps) { +const ItemContext = React.createContext({destructive: false}) + +export function Item({ + children, + destructive = false, + style, +}: { + children?: React.ReactNode + destructive?: boolean + style?: StyleProp +}) { + const context = useMemo(() => ({destructive}), [destructive]) + return ( + + {children} + + ) +} + +export function LinkItem({ + children, + destructive = false, + contentContainerStyle, + ...props +}: LinkProps & { + contentContainerStyle?: StyleProp + destructive?: boolean +}) { const t = useTheme() + return ( {args => ( - {typeof children === 'function' ? children(args) : children} - - - + + )} ) } +export function PressableItem({ + children, + destructive = false, + contentContainerStyle, + hoverStyle, + ...props +}: Button.ButtonProps & { + contentContainerStyle?: StyleProp + destructive?: boolean +}) { + const t = useTheme() + return ( + + {args => ( + + {typeof children === 'function' ? children(args) : children} + + )} + + ) +} + export function ItemIcon({ icon: Comp, size = 'xl', - - color, + color: colorProp, + style, }: Omit, 'position'> & { color?: string + style?: StyleProp }) { const t = useTheme() + const {destructive} = useContext(ItemContext) /* * Copied here from icons/common.tsx so we can tweak if we need to, but @@ -59,9 +120,12 @@ export function ItemIcon({ '2xl': 32, }[size] + const color = + colorProp ?? (destructive ? t.palette.negative_400 : t.atoms.text.color) + return ( - - + + ) } @@ -71,10 +135,95 @@ export function ItemText({ style, ...props }: React.ComponentProps) { + const t = useTheme() + const {destructive} = useContext(ItemContext) + return ( ) } + +export function Divider() { + const t = useTheme() + return ( + + ) +} + +export function Chevron({ + children, + color: colorProp, +}: { + children?: React.ReactNode + color?: string +}) { + const {destructive} = useContext(ItemContext) + const t = useTheme() + const color = + colorProp ?? (destructive ? t.palette.negative_400 : t.palette.contrast_500) + return ( + <> + + {children} + + + ) +} + +export function BadgeText({children}: {children: React.ReactNode}) { + const t = useTheme() + return ( + <> + + + {children} + + + ) +} + +export function BadgeButton({ + label, + onPress, +}: { + label: string + onPress: (evt: GestureResponderEvent) => void +}) { + const t = useTheme() + return ( + <> + + + {({pressed}) => ( + + {label} + + )} + + + ) +} diff --git a/src/view/screens/Storybook/Settings.tsx b/src/view/screens/Storybook/Settings.tsx index f858486e37f..a43a585a5a1 100644 --- a/src/view/screens/Storybook/Settings.tsx +++ b/src/view/screens/Storybook/Settings.tsx @@ -1,17 +1,26 @@ import React from 'react' import {View} from 'react-native' +import * as Toast from '#/view/com/util/Toast' import * as SettingsList from '#/screens/Settings/components/SettingsList' +import {atoms as a, useTheme} from '#/alf' +import {Alien_Stroke2_Corner0_Rounded as AlienIcon} from '#/components/icons/Alien' +import {BirthdayCake_Stroke2_Corner2_Rounded as BirthdayCakeIcon} from '#/components/icons/BirthdayCake' import {BubbleInfo_Stroke2_Corner2_Rounded as BubbleInfoIcon} from '#/components/icons/BubbleInfo' import {CircleQuestion_Stroke2_Corner2_Rounded as CircleQuestionIcon} from '#/components/icons/CircleQuestion' +import {Envelope_Stroke2_Corner2_Rounded as EnvelopeIcon} from '#/components/icons/Envelope' +import {Explosion_Stroke2_Corner0_Rounded as ExplosionIcon} from '#/components/icons/Explosion' import {Earth_Stroke2_Corner2_Rounded as EarthIcon} from '#/components/icons/Globe' import {PaintRoller_Stroke2_Corner2_Rounded as PaintRollerIcon} from '#/components/icons/PaintRoller' import {Person_Stroke2_Corner2_Rounded as PersonIcon} from '#/components/icons/Person' +import {Pizza_Stroke2_Corner0_Rounded as PizzaIcon} from '#/components/icons/Pizza' import {RaisingHand4Finger_Stroke2_Corner2_Rounded as HandIcon} from '#/components/icons/RaisingHand' +import {Verified_Stroke2_Corner2_Rounded as VerifiedIcon} from '#/components/icons/Verified' import {Window_Stroke2_Corner2_Rounded as WindowIcon} from '#/components/icons/Window' import {Text} from '#/components/Typography' export function Settings() { + const t = useTheme() return ( Settings @@ -51,6 +60,69 @@ export function Settings() { About + + Toast.show('Sign out pressed')} + label="Sign out"> + Sign out + + + + + Not pressable + + Toast.show('Pressable pressed')} + label="Pressable"> + + Pressable + + + + Destructive link + + + Toast.show('Email change dialog goes here')}> + + Email + hello@example.com + + Toast.show('Pressable pressed')} + label="Protect your account" + style={[ + a.my_sm, + a.mx_lg, + a.rounded_md, + {backgroundColor: t.palette.primary_50}, + ]} + hoverStyle={[{backgroundColor: t.palette.primary_100}]} + contentContainerStyle={[a.rounded_md, a.px_lg]}> + + + Protect your account + + + + + + + Birthday + Toast.show('Show edit birthday dialog')} + /> + ) } From b1e0b84459f02a3819013b4577575060b17fb97a Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 14 Oct 2024 15:42:57 +0300 Subject: [PATCH 06/31] make text do the spacing --- .../Settings/components/SettingsList.tsx | 68 ++++++------------- src/view/screens/Storybook/Settings.tsx | 9 +++ 2 files changed, 31 insertions(+), 46 deletions(-) diff --git a/src/screens/Settings/components/SettingsList.tsx b/src/screens/Settings/components/SettingsList.tsx index c162f26c06a..5e026a9ec0d 100644 --- a/src/screens/Settings/components/SettingsList.tsx +++ b/src/screens/Settings/components/SettingsList.tsx @@ -99,10 +99,8 @@ export function ItemIcon({ icon: Comp, size = 'xl', color: colorProp, - style, }: Omit, 'position'> & { color?: string - style?: StyleProp }) { const t = useTheme() const {destructive} = useContext(ItemContext) @@ -124,7 +122,7 @@ export function ItemIcon({ colorProp ?? (destructive ? t.palette.negative_400 : t.atoms.text.color) return ( - + ) @@ -144,6 +142,7 @@ export function ItemText({ a.text_md, a.font_normal, a.text_left, + a.flex_1, destructive ? {color: t.palette.negative_400} : t.atoms.text, style, ]} @@ -161,42 +160,22 @@ export function Divider() { ) } -export function Chevron({ - children, - color: colorProp, -}: { - children?: React.ReactNode - color?: string -}) { +export function Chevron({color: colorProp}: {color?: string}) { const {destructive} = useContext(ItemContext) const t = useTheme() const color = colorProp ?? (destructive ? t.palette.negative_400 : t.palette.contrast_500) - return ( - <> - - {children} - - - ) + return } export function BadgeText({children}: {children: React.ReactNode}) { const t = useTheme() return ( - <> - - - {children} - - + + {children} + ) } @@ -209,21 +188,18 @@ export function BadgeButton({ }) { const t = useTheme() return ( - <> - - - {({pressed}) => ( - - {label} - - )} - - + + {({pressed}) => ( + + {label} + + )} + ) } diff --git a/src/view/screens/Storybook/Settings.tsx b/src/view/screens/Storybook/Settings.tsx index a43a585a5a1..c2b20084e22 100644 --- a/src/view/screens/Storybook/Settings.tsx +++ b/src/view/screens/Storybook/Settings.tsx @@ -123,6 +123,15 @@ export function Settings() { onPress={() => Toast.show('Show edit birthday dialog')} /> + + + + + long long long long long long long long long long long long long long + long long long long long long long long long long long long long long + long long long long long long long long long + + ) } From a57d4c8eb978384d4512e72f5322aa50ac3345f6 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 14 Oct 2024 15:44:49 +0300 Subject: [PATCH 07/31] clean up storybook --- src/view/screens/Storybook/Settings.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/view/screens/Storybook/Settings.tsx b/src/view/screens/Storybook/Settings.tsx index c2b20084e22..6bc293c73ce 100644 --- a/src/view/screens/Storybook/Settings.tsx +++ b/src/view/screens/Storybook/Settings.tsx @@ -67,8 +67,7 @@ export function Settings() { label="Sign out"> Sign out - - + Not pressable @@ -85,7 +84,6 @@ export function Settings() { Destructive link - Toast.show('Email change dialog goes here')}> @@ -123,7 +121,6 @@ export function Settings() { onPress={() => Toast.show('Show edit birthday dialog')} /> - From 53a9cfd92e9e9cf23bef1731d3a7ae44ba1ec043 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 14 Oct 2024 19:49:45 +0300 Subject: [PATCH 08/31] gated new settings screen --- src/lib/statsig/gates.ts | 1 + src/screens/Settings/Settings.tsx | 124 ++++++++++++++++++++++++++++ src/view/screens/Settings/index.tsx | 13 ++- 3 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 src/screens/Settings/Settings.tsx diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index 866d87aef06..c39284558b9 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -2,4 +2,5 @@ export type Gate = // Keep this alphabetic please. | 'debug_show_feedcontext' | 'post_feed_lang_window' + | 'new_settings' | 'suggested_feeds_interstitial' diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx new file mode 100644 index 00000000000..ae37c14ac6d --- /dev/null +++ b/src/screens/Settings/Settings.tsx @@ -0,0 +1,124 @@ +import React from 'react' +import {View} from 'react-native' +import {Linking} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {NativeStackScreenProps} from '@react-navigation/native-stack' + +import {HELP_DESK_URL} from '#/lib/constants' +import {CommonNavigatorParams} from '#/lib/routes/types' +import {useSessionApi} from '#/state/session' +import {ViewHeader} from '#/view/com/util/ViewHeader' +import {ScrollView} from '#/view/com/util/Views' +import * as SettingsList from '#/screens/Settings/components/SettingsList' +import {atoms as a} from '#/alf' +import {BubbleInfo_Stroke2_Corner2_Rounded as BubbleInfoIcon} from '#/components/icons/BubbleInfo' +import {CircleQuestion_Stroke2_Corner2_Rounded as CircleQuestionIcon} from '#/components/icons/CircleQuestion' +import {Earth_Stroke2_Corner2_Rounded as EarthIcon} from '#/components/icons/Globe' +import {PaintRoller_Stroke2_Corner2_Rounded as PaintRollerIcon} from '#/components/icons/PaintRoller' +import {Person_Stroke2_Corner2_Rounded as PersonIcon} from '#/components/icons/Person' +import {RaisingHand4Finger_Stroke2_Corner2_Rounded as HandIcon} from '#/components/icons/RaisingHand' +import {Window_Stroke2_Corner2_Rounded as WindowIcon} from '#/components/icons/Window' +import * as Prompt from '#/components/Prompt' + +type Props = NativeStackScreenProps +export function SettingsScreen({}: Props) { + const {_} = useLingui() + const {logoutEveryAccount} = useSessionApi() + const signOutPromptControl = Prompt.usePromptControl() + + return ( + + + + + + + Account + + + + + + Privacy and security + + + + + + Moderation + + + + + + Content and media + + + + + + Accessibilty and appearance + + + + + + Languages + + + Linking.openURL(HELP_DESK_URL)} + label={_(msg`Help`)} + accessibilityHint="Open helpdesk in browser"> + + + Help + + + + + + + About + + + + signOutPromptControl.open()} + label={_(msg`Sign out`)}> + + Sign out + + + + + logoutEveryAccount('Settings')} + confirmButtonCta={_(msg`Sign out`)} + cancelButtonCta={_(msg`Cancel`)} + confirmButtonColor="negative" + /> + + ) +} + +/** + * I want to make a nicer one in the future. + * @deprecated + */ +export function TempHeader() { + return +} diff --git a/src/view/screens/Settings/index.tsx b/src/view/screens/Settings/index.tsx index ce21a043b70..a8d28cddcac 100644 --- a/src/view/screens/Settings/index.tsx +++ b/src/view/screens/Settings/index.tsx @@ -28,6 +28,7 @@ import {HandIcon, HashtagIcon} from '#/lib/icons' import {makeProfileLink} from '#/lib/routes/links' import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' import {NavigationProp} from '#/lib/routes/types' +import {useGate} from '#/lib/statsig/statsig' import {colors, s} from '#/lib/styles' import {isNative} from '#/platform/detection' import {useModalControls} from '#/state/modals' @@ -53,6 +54,7 @@ import * as Toast from '#/view/com/util/Toast' import {UserAvatar} from '#/view/com/util/UserAvatar' import {ScrollView} from '#/view/com/util/Views' import {DeactivateAccountDialog} from '#/screens/Settings/components/DeactivateAccountDialog' +import {SettingsScreen as NewSettingsScreen} from '#/screens/Settings/Settings' import {atoms as a, useTheme} from '#/alf' import {useDialogControl} from '#/components/Dialog' import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings' @@ -137,7 +139,16 @@ function SettingsAccountCard({ } type Props = NativeStackScreenProps -export function SettingsScreen({}: Props) { +export function SettingsScreen(props: Props) { + const gate = useGate() + return gate('new_settings') ? ( + + ) : ( + + ) +} + +function SettingsScreenInner({}: Props) { const queryClient = useQueryClient() const pal = usePalette('default') const {_} = useLingui() From a13aab1736a37606eac100f25093503fd0852406 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 14 Oct 2024 20:35:12 +0300 Subject: [PATCH 09/31] switch account --- .../personGroup_stroke2_corner2_rounded.svg | 1 + src/components/icons/Person.tsx | 4 + src/screens/Settings/Settings.tsx | 105 +++++++++++++++++- .../Settings/components/SettingsList.tsx | 2 +- 4 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 assets/icons/personGroup_stroke2_corner2_rounded.svg diff --git a/assets/icons/personGroup_stroke2_corner2_rounded.svg b/assets/icons/personGroup_stroke2_corner2_rounded.svg new file mode 100644 index 00000000000..fad8a8e7fb9 --- /dev/null +++ b/assets/icons/personGroup_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/src/components/icons/Person.tsx b/src/components/icons/Person.tsx index 4fcc83891ae..31d7078d9ce 100644 --- a/src/components/icons/Person.tsx +++ b/src/components/icons/Person.tsx @@ -23,3 +23,7 @@ export const PersonPlus_Stroke2_Corner0_Rounded = createSinglePathSVG({ export const PersonPlus_Filled_Stroke2_Corner0_Rounded = createSinglePathSVG({ path: 'M7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM12 12c-4.758 0-8.083 3.521-8.496 7.906A1 1 0 0 0 4.5 21H15a3 3 0 1 1 0-6c0-.824.332-1.571.87-2.113C14.739 12.32 13.435 12 12 12Zm6 2a1 1 0 0 1 1 1v2h2a1 1 0 1 1 0 2h-2v2a1 1 0 1 1-2 0v-2h-2a1 1 0 1 1 0-2h2v-2a1 1 0 0 1 1-1Z', }) + +export const PersonGroup_Stroke2_Corner2_Rounded = createSinglePathSVG({ + path: 'M8 5a2 2 0 1 0 0 4 2 2 0 0 0 0-4ZM4 7a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm13-1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm-3.5 1.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0Zm7.301 9.7c-.836-2.6-2.88-3.503-4.575-3.111a1 1 0 0 1-.451-1.949c2.815-.651 5.81.966 6.93 4.448a2.49 2.49 0 0 1-.506 2.43A2.92 2.92 0 0 1 20 20h-2a1 1 0 1 1 0-2h2a.92.92 0 0 0 .69-.295.49.49 0 0 0 .112-.505ZM8 14c-1.865 0-3.878 1.274-4.681 4.151a.57.57 0 0 0 .132.55c.15.171.4.299.695.299h7.708a.93.93 0 0 0 .695-.299.57.57 0 0 0 .132-.55C11.878 15.274 9.865 14 8 14Zm0-2c2.87 0 5.594 1.98 6.607 5.613.53 1.9-1.09 3.387-2.753 3.387H4.146c-1.663 0-3.283-1.487-2.753-3.387C2.406 13.981 5.129 12 8 12Z', +}) diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx index ae37c14ac6d..a20e5239e0d 100644 --- a/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx @@ -1,22 +1,31 @@ import React from 'react' import {View} from 'react-native' import {Linking} from 'react-native' +import {moderateProfile} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {NativeStackScreenProps} from '@react-navigation/native-stack' import {HELP_DESK_URL} from '#/lib/constants' import {CommonNavigatorParams} from '#/lib/routes/types' -import {useSessionApi} from '#/state/session' +import {useModerationOpts} from '#/state/preferences/moderation-opts' +import {useProfilesQuery} from '#/state/queries/profile' +import {useSession, useSessionApi} from '#/state/session' +import {UserAvatar} from '#/view/com/util/UserAvatar' import {ViewHeader} from '#/view/com/util/ViewHeader' import {ScrollView} from '#/view/com/util/Views' import * as SettingsList from '#/screens/Settings/components/SettingsList' -import {atoms as a} from '#/alf' +import {atoms as a, useTheme} from '#/alf' +import {useDialogControl} from '#/components/Dialog' +import {SwitchAccountDialog} from '#/components/dialogs/SwitchAccount' import {BubbleInfo_Stroke2_Corner2_Rounded as BubbleInfoIcon} from '#/components/icons/BubbleInfo' import {CircleQuestion_Stroke2_Corner2_Rounded as CircleQuestionIcon} from '#/components/icons/CircleQuestion' import {Earth_Stroke2_Corner2_Rounded as EarthIcon} from '#/components/icons/Globe' import {PaintRoller_Stroke2_Corner2_Rounded as PaintRollerIcon} from '#/components/icons/PaintRoller' -import {Person_Stroke2_Corner2_Rounded as PersonIcon} from '#/components/icons/Person' +import { + Person_Stroke2_Corner2_Rounded as PersonIcon, + PersonGroup_Stroke2_Corner2_Rounded as PersonGroupIcon, +} from '#/components/icons/Person' import {RaisingHand4Finger_Stroke2_Corner2_Rounded as HandIcon} from '#/components/icons/RaisingHand' import {Window_Stroke2_Corner2_Rounded as WindowIcon} from '#/components/icons/Window' import * as Prompt from '#/components/Prompt' @@ -25,12 +34,39 @@ type Props = NativeStackScreenProps export function SettingsScreen({}: Props) { const {_} = useLingui() const {logoutEveryAccount} = useSessionApi() + const {accounts, currentAccount} = useSession() + const switchAccountControl = useDialogControl() const signOutPromptControl = Prompt.usePromptControl() return ( + 1 + ? _(msg`Switch account`) + : _(msg`Add another account`) + } + onPress={() => switchAccountControl.open()}> + + + {accounts.length > 1 ? ( + Switch account + ) : ( + Add another account + )} + + {accounts.length > 1 && ( + acc.did) + .filter(did => did !== currentAccount?.did) + .slice(0, 5)} + /> + )} + + @@ -111,6 +147,8 @@ export function SettingsScreen({}: Props) { cancelButtonCta={_(msg`Cancel`)} confirmButtonColor="negative" /> + + ) } @@ -122,3 +160,64 @@ export function SettingsScreen({}: Props) { export function TempHeader() { return } + +function AvatarStack({profiles}: {profiles: string[]}) { + const {data, error} = useProfilesQuery({handles: profiles}) + const t = useTheme() + const moderationOpts = useModerationOpts() + + if (error) { + console.error(error) + return null + } + + const isPending = !data || !moderationOpts + + const items = isPending + ? Array.from({length: profiles.length}).map((_, i) => ({ + key: i, + profile: null, + moderation: null, + })) + : data.profiles.map(item => ({ + key: item.did, + profile: item, + moderation: moderateProfile(item, moderationOpts), + })) + + return ( + + {items.map((item, i) => ( + + {item.profile && ( + + )} + + ))} + + ) +} diff --git a/src/screens/Settings/components/SettingsList.tsx b/src/screens/Settings/components/SettingsList.tsx index 5e026a9ec0d..95c7b572aa9 100644 --- a/src/screens/Settings/components/SettingsList.tsx +++ b/src/screens/Settings/components/SettingsList.tsx @@ -155,7 +155,7 @@ export function Divider() { const t = useTheme() return ( ) } From 4460a675bcbf129b3e2a01f06f1a37caed2cf334 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 14 Oct 2024 21:59:59 +0300 Subject: [PATCH 10/31] add current profile --- src/screens/Settings/Settings.tsx | 74 +++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx index a20e5239e0d..e83b760c20a 100644 --- a/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx @@ -1,16 +1,19 @@ import React from 'react' import {View} from 'react-native' import {Linking} from 'react-native' -import {moderateProfile} from '@atproto/api' +import {AppBskyActorDefs, moderateProfile} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {NativeStackScreenProps} from '@react-navigation/native-stack' import {HELP_DESK_URL} from '#/lib/constants' import {CommonNavigatorParams} from '#/lib/routes/types' +import {useProfileShadow} from '#/state/cache/profile-shadow' import {useModerationOpts} from '#/state/preferences/moderation-opts' -import {useProfilesQuery} from '#/state/queries/profile' +import {useProfileQuery, useProfilesQuery} from '#/state/queries/profile' import {useSession, useSessionApi} from '#/state/session' +import {useLoggedOutViewControls} from '#/state/shell/logged-out' +import {useCloseAllActiveElements} from '#/state/util' import {UserAvatar} from '#/view/com/util/UserAvatar' import {ViewHeader} from '#/view/com/util/ViewHeader' import {ScrollView} from '#/view/com/util/Views' @@ -29,6 +32,8 @@ import { import {RaisingHand4Finger_Stroke2_Corner2_Rounded as HandIcon} from '#/components/icons/RaisingHand' import {Window_Stroke2_Corner2_Rounded as WindowIcon} from '#/components/icons/Window' import * as Prompt from '#/components/Prompt' +import {ProfileHeaderDisplayName} from '../Profile/Header/DisplayName' +import {ProfileHeaderHandle} from '../Profile/Header/Handle' type Props = NativeStackScreenProps export function SettingsScreen({}: Props) { @@ -37,18 +42,41 @@ export function SettingsScreen({}: Props) { const {accounts, currentAccount} = useSession() const switchAccountControl = useDialogControl() const signOutPromptControl = Prompt.usePromptControl() + const {data: profile} = useProfileQuery({did: currentAccount?.did}) + const {setShowLoggedOut} = useLoggedOutViewControls() + const closeEverything = useCloseAllActiveElements() + + const onAddAnotherAccount = () => { + setShowLoggedOut(true) + closeEverything() + } return ( - + + + {profile && } + 1 ? _(msg`Switch account`) : _(msg`Add another account`) } - onPress={() => switchAccountControl.open()}> + onPress={() => + accounts.length > 1 + ? switchAccountControl.open() + : onAddAnotherAccount() + }> {accounts.length > 1 ? ( @@ -161,6 +189,34 @@ export function TempHeader() { return } +function ProfilePreview({ + profile, +}: { + profile: AppBskyActorDefs.ProfileViewDetailed +}) { + const shadow = useProfileShadow(profile) + const moderationOpts = useModerationOpts() + + if (!moderationOpts) return null + + const moderation = moderateProfile(profile, moderationOpts) + + return ( + <> + + + + + ) +} + +const AVI_SIZE = 26 +const HALF_AVI_SIZE = AVI_SIZE / 2 + function AvatarStack({profiles}: {profiles: string[]}) { const {data, error} = useProfilesQuery({handles: profiles}) const t = useTheme() @@ -191,7 +247,7 @@ function AvatarStack({profiles}: {profiles: string[]}) { a.flex_row, a.align_center, a.relative, - {width: 30 + (items.length - 1) * 15}, + {width: AVI_SIZE + (items.length - 1) * HALF_AVI_SIZE}, ]}> {items.map((item, i) => ( {item.profile && ( From ee0d30a7eed46dff00decaa70b77bcba66f2cfbe Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 15 Oct 2024 09:29:50 +0300 Subject: [PATCH 11/31] use Layout.Screen --- src/screens/Settings/Settings.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx index e83b760c20a..fd98547bf12 100644 --- a/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx @@ -17,6 +17,8 @@ import {useCloseAllActiveElements} from '#/state/util' import {UserAvatar} from '#/view/com/util/UserAvatar' import {ViewHeader} from '#/view/com/util/ViewHeader' import {ScrollView} from '#/view/com/util/Views' +import {ProfileHeaderDisplayName} from '#/screens/Profile/Header/DisplayName' +import {ProfileHeaderHandle} from '#/screens/Profile/Header/Handle' import * as SettingsList from '#/screens/Settings/components/SettingsList' import {atoms as a, useTheme} from '#/alf' import {useDialogControl} from '#/components/Dialog' @@ -31,9 +33,8 @@ import { } from '#/components/icons/Person' import {RaisingHand4Finger_Stroke2_Corner2_Rounded as HandIcon} from '#/components/icons/RaisingHand' import {Window_Stroke2_Corner2_Rounded as WindowIcon} from '#/components/icons/Window' +import * as Layout from '#/components/Layout' import * as Prompt from '#/components/Prompt' -import {ProfileHeaderDisplayName} from '../Profile/Header/DisplayName' -import {ProfileHeaderHandle} from '../Profile/Header/Handle' type Props = NativeStackScreenProps export function SettingsScreen({}: Props) { @@ -52,7 +53,7 @@ export function SettingsScreen({}: Props) { } return ( - + - + ) } From 87bca998c2497745fedf29d0593e328906956620 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 15 Oct 2024 09:49:59 +0300 Subject: [PATCH 12/31] Layout.Header and Layout.Content --- src/components/Layout.tsx | 85 ++++++++++++++++++++++++++----- src/screens/Settings/Settings.tsx | 16 ++---- 2 files changed, 75 insertions(+), 26 deletions(-) diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 57e37316454..ea11e221740 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -1,16 +1,22 @@ -import React from 'react' +import React, {useContext, useMemo} from 'react' import {View, ViewStyle} from 'react-native' import {StyleProp} from 'react-native' import {useSafeAreaInsets} from 'react-native-safe-area-context' +import {ViewHeader} from '#/view/com/util/ViewHeader' +import {ScrollView} from '#/view/com/util/Views' +import {CenteredView} from '#/view/com/util/Views' import {atoms as a} from '#/alf' // Every screen should have a Layout component wrapping it. // This component provides a default padding for the top of the screen. // This allows certain screens to avoid the top padding if they want to. -// -// In a future PR I will add a unified header component to this file and -// things like a preconfigured scrollview. + +const LayoutContext = React.createContext({ + withinScreen: false, + topPaddingDisabled: false, + withinScrollView: false, +}) /** * Every screen should have a Layout.Screen component wrapping it. @@ -18,7 +24,7 @@ import {atoms as a} from '#/alf' * and height/minHeight */ let Screen = ({ - disableTopPadding, + disableTopPadding = false, style, ...props }: React.ComponentProps & { @@ -26,16 +32,69 @@ let Screen = ({ style?: StyleProp }): React.ReactNode => { const {top} = useSafeAreaInsets() + const context = useMemo( + () => ({ + withinScreen: true, + topPaddingDisabled: disableTopPadding, + withinScrollView: false, + }), + [disableTopPadding], + ) return ( - + + + ) } Screen = React.memo(Screen) export {Screen} + +let Header = ( + props: React.ComponentProps, +): React.ReactNode => { + const {withinScrollView} = useContext(LayoutContext) + if (!withinScrollView) { + return ( + + + + ) + } else { + return + } +} +Header = React.memo(Header) +export {Header} + +let Content = ({ + style, + contentContainerStyle, + ...props +}: React.ComponentProps & { + style?: StyleProp + contentContainerStyle?: StyleProp +}): React.ReactNode => { + const context = useContext(LayoutContext) + const newContext = useMemo( + () => ({...context, withinScrollView: true}), + [context], + ) + return ( + + + + ) +} +Content = React.memo(Content) +export {Content} diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx index fd98547bf12..d5eb34d87f5 100644 --- a/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx @@ -15,8 +15,6 @@ import {useSession, useSessionApi} from '#/state/session' import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useCloseAllActiveElements} from '#/state/util' import {UserAvatar} from '#/view/com/util/UserAvatar' -import {ViewHeader} from '#/view/com/util/ViewHeader' -import {ScrollView} from '#/view/com/util/Views' import {ProfileHeaderDisplayName} from '#/screens/Profile/Header/DisplayName' import {ProfileHeaderHandle} from '#/screens/Profile/Header/Handle' import * as SettingsList from '#/screens/Settings/components/SettingsList' @@ -54,8 +52,8 @@ export function SettingsScreen({}: Props) { return ( - - + + Sign out - + -} - function ProfilePreview({ profile, }: { From 8b4db17ca12c9f8171f76fff09cbebd029c76638 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 15 Oct 2024 11:23:12 +0300 Subject: [PATCH 13/31] translate helpdesk text thanks @surfdude29! Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> --- src/screens/Settings/Settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx index d5eb34d87f5..486da2d5d73 100644 --- a/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx @@ -141,7 +141,7 @@ export function SettingsScreen({}: Props) { Linking.openURL(HELP_DESK_URL)} label={_(msg`Help`)} - accessibilityHint="Open helpdesk in browser"> + accessibilityHint={_(msg`Open helpdesk in browser`)}> Help From 69749558317d3b2a356d88aa7783ef64e6a3f9b3 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 15 Oct 2024 11:29:31 +0300 Subject: [PATCH 14/31] add account settings --- assets/icons/car_stroke2_corner2_rounded.svg | 1 + .../icons/freeze_stroke2_corner2_rounded.svg | 1 + .../pencilLine_stroke2_corner2_rounded.svg | 1 + bskyweb/cmd/bskyweb/server.go | 5 + src/Navigation.tsx | 11 +- src/components/dialogs/BirthDateSettings.tsx | 2 +- src/components/icons/Car.tsx | 5 + src/components/icons/Freeze.tsx | 5 + src/components/icons/Pencil.tsx | 4 + src/lib/routes/types.ts | 6 +- src/routes.ts | 18 +- src/screens/Settings/AccountSettings.tsx | 180 ++++++++++++++ src/screens/Settings/Settings.tsx | 219 +++++++++--------- .../Settings/components/SettingsList.tsx | 12 +- src/view/com/modals/DeleteAccount.tsx | 13 +- src/view/screens/Settings/ExportCarDialog.tsx | 6 +- 16 files changed, 363 insertions(+), 126 deletions(-) create mode 100644 assets/icons/car_stroke2_corner2_rounded.svg create mode 100644 assets/icons/freeze_stroke2_corner2_rounded.svg create mode 100644 assets/icons/pencilLine_stroke2_corner2_rounded.svg create mode 100644 src/components/icons/Car.tsx create mode 100644 src/components/icons/Freeze.tsx create mode 100644 src/screens/Settings/AccountSettings.tsx diff --git a/assets/icons/car_stroke2_corner2_rounded.svg b/assets/icons/car_stroke2_corner2_rounded.svg new file mode 100644 index 00000000000..ca29cefd1c1 --- /dev/null +++ b/assets/icons/car_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/freeze_stroke2_corner2_rounded.svg b/assets/icons/freeze_stroke2_corner2_rounded.svg new file mode 100644 index 00000000000..1091454782e --- /dev/null +++ b/assets/icons/freeze_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/pencilLine_stroke2_corner2_rounded.svg b/assets/icons/pencilLine_stroke2_corner2_rounded.svg new file mode 100644 index 00000000000..92f1fed1c4a --- /dev/null +++ b/assets/icons/pencilLine_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index 1089e3c1af8..a1b2bd9da20 100644 --- a/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go @@ -244,6 +244,11 @@ func serve(cctx *cli.Context) error { e.GET("/settings/external-embeds", server.WebGeneric) e.GET("/settings/accessibility", server.WebGeneric) e.GET("/settings/appearance", server.WebGeneric) + e.GET("/settings/account", server.WebGeneric) + e.GET("/settings/privacy-and-security", server.WebGeneric) + e.GET("/settings/content-and-media", server.WebGeneric) + e.GET("/settings/accessibility-and-appearance", server.WebGeneric) + e.GET("/settings/about", server.WebGeneric) e.GET("/sys/debug", server.WebGeneric) e.GET("/sys/debug-mod", server.WebGeneric) e.GET("/sys/log", server.WebGeneric) diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 81d08c7da37..56bcd24f1a2 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -95,6 +95,7 @@ import {Wizard} from '#/screens/StarterPack/Wizard' import {useTheme} from '#/alf' import {router} from '#/routes' import {Referrer} from '../modules/expo-bluesky-swiss-army' +import {AccountSettingsScreen} from './screens/Settings/AccountSettings' const navigationRef = createNavigationContainerRef() @@ -322,7 +323,15 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { name="AppearanceSettings" getComponent={() => AppearanceSettingsScreen} options={{ - title: title(msg`Appearance Settings`), + title: title(msg`Appearance`), + requireAuth: true, + }} + /> + AccountSettingsScreen} + options={{ + title: title(msg`Account`), requireAuth: true, }} /> diff --git a/src/components/dialogs/BirthDateSettings.tsx b/src/components/dialogs/BirthDateSettings.tsx index 81d0c6740ec..8f47d05b064 100644 --- a/src/components/dialogs/BirthDateSettings.tsx +++ b/src/components/dialogs/BirthDateSettings.tsx @@ -29,7 +29,7 @@ export function BirthDateSettingsDialog({ const {isLoading, error, data: preferences} = usePreferencesQuery() return ( - + diff --git a/src/components/icons/Car.tsx b/src/components/icons/Car.tsx new file mode 100644 index 00000000000..25859176acd --- /dev/null +++ b/src/components/icons/Car.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const Car_Stroke2_Corner2_Rounded = createSinglePathSVG({ + path: 'M7.018 6a1 1 0 0 0-.808.412L5.4 5.824l.809.588L3 10.825V17a1 1 0 1 0 2 0 1 1 0 0 1 1-1h12a1 1 0 0 1 1 1 1 1 0 1 0 2 0v-5.998l-3.22-4.577A1 1 0 0 0 16.962 6H7.018ZM23 11.686V17a3 3 0 0 1-5.83 1H6.83A3.001 3.001 0 0 1 1 17v-5.5a1 1 0 1 1 0-2h.49l3.102-4.265A3 3 0 0 1 7.018 4h9.944a3 3 0 0 1 2.453 1.274l3.104 4.412H23a1 1 0 1 1 0 2ZM5 13a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2H6a1 1 0 0 1-1-1Zm10 0a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Z', +}) diff --git a/src/components/icons/Freeze.tsx b/src/components/icons/Freeze.tsx new file mode 100644 index 00000000000..628157b4f0e --- /dev/null +++ b/src/components/icons/Freeze.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const Freeze_Stroke2_Corner2_Rounded = createSinglePathSVG({ + path: 'M8.789 2.293a1 1 0 0 1 1.414 0l1.793 1.793 1.793-1.793a1 1 0 1 1 1.414 1.414l-2.207 2.207v4.355l3.772-2.178.808-3.015a1 1 0 1 1 1.931.518l-.656 2.449 2.45.656a1 1 0 1 1-.518 1.932l-3.015-.808L13.998 12l3.77 2.177 3.015-.808a1 1 0 1 1 .517 1.932l-2.449.656.657 2.45a1 1 0 1 1-1.932.517l-.808-3.015-3.772-2.178v4.355l2.207 2.207a1 1 0 0 1-1.414 1.414l-1.793-1.793-1.793 1.793a1 1 0 0 1-1.414-1.414l2.207-2.207v-4.353l-3.77 2.176-.807 3.015a1 1 0 0 1-1.932-.518l.656-2.449-2.449-.656a1 1 0 1 1 .518-1.932l3.015.808L9.997 12l-3.77-2.177-3.015.808a1 1 0 0 1-.518-1.932l2.45-.656-.657-2.45a1 1 0 0 1 1.932-.517l.808 3.015 3.77 2.176V5.914L8.788 3.707a1 1 0 0 1 0-1.414Z', +}) diff --git a/src/components/icons/Pencil.tsx b/src/components/icons/Pencil.tsx index 51fd8ba795e..a99f8d61447 100644 --- a/src/components/icons/Pencil.tsx +++ b/src/components/icons/Pencil.tsx @@ -7,3 +7,7 @@ export const Pencil_Stroke2_Corner0_Rounded = createSinglePathSVG({ export const PencilLine_Stroke2_Corner0_Rounded = createSinglePathSVG({ path: 'M15.586 2.5a2 2 0 0 1 2.828 0L21.5 5.586a2 2 0 0 1 0 2.828l-13 13A2 2 0 0 1 7.086 22H3a1 1 0 0 1-1-1v-4.086a2 2 0 0 1 .586-1.414l13-13ZM17 3.914l-13 13V20h3.086l13-13L17 3.914ZM13 21a1 1 0 0 1 1-1h7a1 1 0 1 1 0 2h-7a1 1 0 0 1-1-1Z', }) + +export const PencilLine_Stroke2_Corner2_Rounded = createSinglePathSVG({ + path: 'M15.379 2.707a3 3 0 0 1 4.242 0l1.672 1.672a3 3 0 0 1 0 4.242l-12.5 12.5A3 3 0 0 1 6.672 22H3a1 1 0 0 1-1-1v-3.672a3 3 0 0 1 .879-2.121l12.5-12.5Zm2.828 1.414a1 1 0 0 0-1.414 0l-12.5 12.5a1 1 0 0 0-.293.707V20h2.672a1 1 0 0 0 .707-.293l12.5-12.5.707.707-.707-.707a1 1 0 0 0 0-1.414L18.207 4.12ZM13 21a1 1 0 0 1 1-1h7a1 1 0 0 1 0 2h-7a1 1 0 0 1-1-1Z', +}) diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index 426665d07d4..d3a8a900dfe 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -11,7 +11,6 @@ export type CommonNavigatorParams = { ModerationMutedAccounts: undefined ModerationBlockedAccounts: undefined Settings: undefined - LanguageSettings: undefined Profile: {name: string; hideBackButton?: boolean} ProfileFollowers: {name: string} ProfileFollows: {name: string} @@ -33,6 +32,7 @@ export type CommonNavigatorParams = { TermsOfService: undefined CommunityGuidelines: undefined CopyrightPolicy: undefined + LanguageSettings: undefined AppPasswords: undefined SavedFeeds: undefined PreferencesFollowingFeed: undefined @@ -40,6 +40,10 @@ export type CommonNavigatorParams = { PreferencesExternalEmbeds: undefined AccessibilitySettings: undefined AppearanceSettings: undefined + AccountSettings: undefined + PrivacyAndSecuritySettings: undefined + ContentAndMediaSettings: undefined + AccessibilityAndAppearanceSettings: undefined Search: {q?: string} Hashtag: {tag: string; author?: string} MessagesConversation: {conversation: string; embed?: string} diff --git a/src/routes.ts b/src/routes.ts index 2ae4126ace2..3b9fc6b1a47 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -1,4 +1,4 @@ -import {Router} from 'lib/routes/router' +import {Router} from '#/lib/routes/router' export const router = new Router({ Home: '/', @@ -7,12 +7,13 @@ export const router = new Router({ Notifications: '/notifications', NotificationsSettings: '/notifications/settings', Settings: '/settings', - LanguageSettings: '/settings/language', Lists: '/lists', + // moderation Moderation: '/moderation', ModerationModlists: '/moderation/modlists', ModerationMutedAccounts: '/moderation/muted-accounts', ModerationBlockedAccounts: '/moderation/blocked-accounts', + // profiles, threads, lists Profile: ['/profile/:name', '/profile/:name/rss'], ProfileFollowers: '/profile/:name/followers', ProfileFollows: '/profile/:name/follows', @@ -25,9 +26,12 @@ export const router = new Router({ ProfileFeed: '/profile/:name/feed/:rkey', ProfileFeedLikedBy: '/profile/:name/feed/:rkey/liked-by', ProfileLabelerLikedBy: '/profile/:name/labeler/liked-by', + // debug Debug: '/sys/debug', DebugMod: '/sys/debug-mod', Log: '/sys/log', + // settings + LanguageSettings: '/settings/language', AppPasswords: '/settings/app-passwords', PreferencesFollowingFeed: '/settings/following-feed', PreferencesThreads: '/settings/threads', @@ -35,15 +39,25 @@ export const router = new Router({ AccessibilitySettings: '/settings/accessibility', AppearanceSettings: '/settings/appearance', SavedFeeds: '/settings/saved-feeds', + // new settings + AccountSettings: '/settings/account', + PrivacyAndSecuritySettings: '/settings/privacy-and-security', + ContentAndMediaSettings: '/settings/content-and-media', + AccessibilityAndAppearanceSettings: '/settings/accessibility-and-appearance', + About: '/settings/about', + // support Support: '/support', PrivacyPolicy: '/support/privacy', TermsOfService: '/support/tos', CommunityGuidelines: '/support/community-guidelines', CopyrightPolicy: '/support/copyright', + // hashtags Hashtag: '/hashtag/:tag', + // DMs Messages: '/messages', MessagesSettings: '/messages/settings', MessagesConversation: '/messages/:conversation', + // starter packs Start: '/start/:name/:rkey', StarterPackEdit: '/starter-pack/edit/:rkey', StarterPack: '/starter-pack/:name/:rkey', diff --git a/src/screens/Settings/AccountSettings.tsx b/src/screens/Settings/AccountSettings.tsx new file mode 100644 index 00000000000..0dcf651be7f --- /dev/null +++ b/src/screens/Settings/AccountSettings.tsx @@ -0,0 +1,180 @@ +import React from 'react' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {NativeStackScreenProps} from '@react-navigation/native-stack' +import {useQueryClient} from '@tanstack/react-query' + +import {CommonNavigatorParams} from '#/lib/routes/types' +import {useModalControls} from '#/state/modals' +import {RQKEY as RQKEY_PROFILE} from '#/state/queries/profile' +import {useProfileQuery} from '#/state/queries/profile' +import {useSession} from '#/state/session' +import {ExportCarDialog} from '#/view/screens/Settings/ExportCarDialog' +import * as SettingsList from '#/screens/Settings/components/SettingsList' +import {atoms as a, useTheme} from '#/alf' +import {useDialogControl} from '#/components/Dialog' +import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings' +import {At_Stroke2_Corner2_Rounded as AtIcon} from '#/components/icons/At' +import {BirthdayCake_Stroke2_Corner2_Rounded as BirthdayCakeIcon} from '#/components/icons/BirthdayCake' +import {Car_Stroke2_Corner2_Rounded as CarIcon} from '#/components/icons/Car' +import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' +import {Envelope_Stroke2_Corner2_Rounded as EnvelopeIcon} from '#/components/icons/Envelope' +import {Freeze_Stroke2_Corner2_Rounded as FreezeIcon} from '#/components/icons/Freeze' +import {Lock_Stroke2_Corner2_Rounded as LockIcon} from '#/components/icons/Lock' +import {PencilLine_Stroke2_Corner2_Rounded as PencilIcon} from '#/components/icons/Pencil' +import {Trash_Stroke2_Corner2_Rounded} from '#/components/icons/Trash' +import {Verified_Stroke2_Corner2_Rounded as VerifiedIcon} from '#/components/icons/Verified' +import * as Layout from '#/components/Layout' +import {DeactivateAccountDialog} from './components/DeactivateAccountDialog' + +type Props = NativeStackScreenProps +export function AccountSettingsScreen({}: Props) { + const t = useTheme() + const {_} = useLingui() + const {currentAccount} = useSession() + const queryClient = useQueryClient() + const {data: profile} = useProfileQuery({did: currentAccount?.did}) + const {openModal} = useModalControls() + const birthdayControl = useDialogControl() + const exportCarControl = useDialogControl() + const deactivateAccountControl = useDialogControl() + + return ( + + + + + + + + Email + + {currentAccount && ( + <> + + {currentAccount.email || (no email)} + + {currentAccount.emailConfirmed ? ( + + ) : ( + {}} + /> + )} + + )} + + openModal({name: 'change-email'})}> + + + Change email + + + + {}} + label="Protect your account" + style={[ + a.my_xs, + a.mx_lg, + a.rounded_md, + {backgroundColor: t.palette.primary_50}, + ]} + hoverStyle={[{backgroundColor: t.palette.primary_100}]} + contentContainerStyle={[a.rounded_md, a.px_lg]}> + + + Protect your account + + + + + + + + Birthday + + birthdayControl.open()} + /> + + openModal({name: 'change-password'})}> + + + Password + + + + + openModal({ + name: 'change-handle', + onChanged() { + if (currentAccount) { + // refresh my profile + queryClient.invalidateQueries({ + queryKey: RQKEY_PROFILE(currentAccount.did), + }) + } + }, + }) + }> + + + Handle + + {profile && ( + @{profile.handle} + )} + + + + exportCarControl.open()}> + + + Export my data + + + + deactivateAccountControl.open()} + destructive> + + + Deactivate account + + + + openModal({name: 'delete-account'})} + destructive> + + + Delete account + + + + + + + + + + + ) +} diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx index 486da2d5d73..3fa528900b7 100644 --- a/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx @@ -24,6 +24,7 @@ import {SwitchAccountDialog} from '#/components/dialogs/SwitchAccount' import {BubbleInfo_Stroke2_Corner2_Rounded as BubbleInfoIcon} from '#/components/icons/BubbleInfo' import {CircleQuestion_Stroke2_Corner2_Rounded as CircleQuestionIcon} from '#/components/icons/CircleQuestion' import {Earth_Stroke2_Corner2_Rounded as EarthIcon} from '#/components/icons/Globe' +import {Lock_Stroke2_Corner2_Rounded as LockIcon} from '#/components/icons/Lock' import {PaintRoller_Stroke2_Corner2_Rounded as PaintRollerIcon} from '#/components/icons/PaintRoller' import { Person_Stroke2_Corner2_Rounded as PersonIcon, @@ -54,115 +55,117 @@ export function SettingsScreen({}: Props) { - - {profile && } - - 1 - ? _(msg`Switch account`) - : _(msg`Add another account`) - } - onPress={() => - accounts.length > 1 - ? switchAccountControl.open() - : onAddAnotherAccount() - }> - - - {accounts.length > 1 ? ( - Switch account - ) : ( - Add another account + + + {profile && } + + 1 + ? _(msg`Switch account`) + : _(msg`Add another account`) + } + onPress={() => + accounts.length > 1 + ? switchAccountControl.open() + : onAddAnotherAccount() + }> + + + {accounts.length > 1 ? ( + Switch account + ) : ( + Add another account + )} + + {accounts.length > 1 && ( + acc.did) + .filter(did => did !== currentAccount?.did) + .slice(0, 5)} + /> )} - - {accounts.length > 1 && ( - acc.did) - .filter(did => did !== currentAccount?.did) - .slice(0, 5)} - /> - )} - - - - - - Account - - - - - - Privacy and security - - - - - - Moderation - - - - - - Content and media - - - - - - Accessibilty and appearance - - - - - - Languages - - - Linking.openURL(HELP_DESK_URL)} - label={_(msg`Help`)} - accessibilityHint={_(msg`Open helpdesk in browser`)}> - - - Help - - - - - - - About - - - - signOutPromptControl.open()} - label={_(msg`Sign out`)}> - - Sign out - - + + + + + + Account + + + + + + Privacy and security + + + + + + Moderation + + + + + + Content and media + + + + + + Accessibilty and appearance + + + + + + Languages + + + Linking.openURL(HELP_DESK_URL)} + label={_(msg`Help`)} + accessibilityHint={_(msg`Open helpdesk in browser`)}> + + + Help + + + + + + + About + + + + signOutPromptControl.open()} + label={_(msg`Sign out`)}> + + Sign out + + + {children} +} + export function Item({ children, destructive = false, @@ -119,7 +123,7 @@ export function ItemIcon({ }[size] const color = - colorProp ?? (destructive ? t.palette.negative_400 : t.atoms.text.color) + colorProp ?? (destructive ? t.palette.negative_500 : t.atoms.text.color) return ( @@ -143,7 +147,7 @@ export function ItemText({ a.font_normal, a.text_left, a.flex_1, - destructive ? {color: t.palette.negative_400} : t.atoms.text, + destructive ? {color: t.palette.negative_500} : t.atoms.text, style, ]} {...props} @@ -164,7 +168,7 @@ export function Chevron({color: colorProp}: {color?: string}) { const {destructive} = useContext(ItemContext) const t = useTheme() const color = - colorProp ?? (destructive ? t.palette.negative_400 : t.palette.contrast_500) + colorProp ?? (destructive ? t.palette.negative_500 : t.palette.contrast_500) return } @@ -172,7 +176,7 @@ export function BadgeText({children}: {children: React.ReactNode}) { const t = useTheme() return ( {children} diff --git a/src/view/com/modals/DeleteAccount.tsx b/src/view/com/modals/DeleteAccount.tsx index 6dd248ca7e1..b865d7bbf10 100644 --- a/src/view/com/modals/DeleteAccount.tsx +++ b/src/view/com/modals/DeleteAccount.tsx @@ -10,15 +10,15 @@ import {LinearGradient} from 'expo-linear-gradient' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {usePalette} from '#/lib/hooks/usePalette' +import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' +import {cleanError} from '#/lib/strings/errors' +import {colors, gradients, s} from '#/lib/styles' +import {useTheme} from '#/lib/ThemeContext' +import {isAndroid, isWeb} from '#/platform/detection' import {useModalControls} from '#/state/modals' import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' import {useAgent, useSession, useSessionApi} from '#/state/session' -import {usePalette} from 'lib/hooks/usePalette' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {cleanError} from 'lib/strings/errors' -import {colors, gradients, s} from 'lib/styles' -import {useTheme} from 'lib/ThemeContext' -import {isAndroid, isWeb} from 'platform/detection' import {DeactivateAccountDialog} from '#/screens/Settings/components/DeactivateAccountDialog' import {atoms as a, useTheme as useNewTheme} from '#/alf' import {useDialogControl} from '#/components/Dialog' @@ -210,6 +210,7 @@ export function Component({}: {}) { to="#" onPress={e => { e.preventDefault() + closeModal() deactivateAccountControl.open() return false }}> diff --git a/src/view/screens/Settings/ExportCarDialog.tsx b/src/view/screens/Settings/ExportCarDialog.tsx index 1d8d2647127..2e6a1296ce5 100644 --- a/src/view/screens/Settings/ExportCarDialog.tsx +++ b/src/view/screens/Settings/ExportCarDialog.tsx @@ -10,6 +10,7 @@ import * as Toast from '#/view/com/util/Toast' import {atoms as a, useTheme} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' +import {Download_Stroke2_Corner0_Rounded as DownloadIcon} from '#/components/icons/Download' import {InlineLinkText} from '#/components/Link' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' @@ -53,9 +54,7 @@ export function ExportCarDialog({ return ( - + Export My Data @@ -76,6 +75,7 @@ export function ExportCarDialog({ label={_(msg`Download CAR file`)} disabled={loading} onPress={download}> + Download CAR file From 711e9339831108aa73fb6a0afed096b643f95007 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 15 Oct 2024 11:56:34 +0300 Subject: [PATCH 15/31] undo changes to export car dialog --- src/view/screens/Settings/ExportCarDialog.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/view/screens/Settings/ExportCarDialog.tsx b/src/view/screens/Settings/ExportCarDialog.tsx index 2e6a1296ce5..2de3895d310 100644 --- a/src/view/screens/Settings/ExportCarDialog.tsx +++ b/src/view/screens/Settings/ExportCarDialog.tsx @@ -54,7 +54,9 @@ export function ExportCarDialog({ return ( - + Export My Data From c650eeabb180576ab5d907e3ca4ff74ea2988b0e Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 15 Oct 2024 12:46:47 +0300 Subject: [PATCH 16/31] privacy and security screen --- assets/icons/key_stroke2_corner2_rounded.svg | 1 + src/Navigation.tsx | 9 ++ src/components/icons/Key.tsx | 5 + src/screens/Settings/AccountSettings.tsx | 8 +- .../Settings/PrivacyAndSecuritySettings.tsx | 93 +++++++++++++++++ .../Settings/components/Email2FAToggle.tsx | 66 +++++++++++++ src/screens/Settings/components/PwiOptOut.tsx | 99 +++++++++++++++++++ .../Settings/components/SettingsList.tsx | 19 +++- .../Settings/DisableEmail2FADialog.tsx | 12 ++- 9 files changed, 305 insertions(+), 7 deletions(-) create mode 100644 assets/icons/key_stroke2_corner2_rounded.svg create mode 100644 src/components/icons/Key.tsx create mode 100644 src/screens/Settings/PrivacyAndSecuritySettings.tsx create mode 100644 src/screens/Settings/components/Email2FAToggle.tsx create mode 100644 src/screens/Settings/components/PwiOptOut.tsx diff --git a/assets/icons/key_stroke2_corner2_rounded.svg b/assets/icons/key_stroke2_corner2_rounded.svg new file mode 100644 index 00000000000..2beb2b4ae49 --- /dev/null +++ b/assets/icons/key_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 56bcd24f1a2..b87e34eee3f 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -96,6 +96,7 @@ import {useTheme} from '#/alf' import {router} from '#/routes' import {Referrer} from '../modules/expo-bluesky-swiss-army' import {AccountSettingsScreen} from './screens/Settings/AccountSettings' +import {PrivacyAndSecuritySettingsScreen} from './screens/Settings/PrivacyAndSecuritySettings' const navigationRef = createNavigationContainerRef() @@ -335,6 +336,14 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { requireAuth: true, }} /> + PrivacyAndSecuritySettingsScreen} + options={{ + title: title(msg`Privacy and Security`), + requireAuth: true, + }} + /> HashtagScreen} diff --git a/src/components/icons/Key.tsx b/src/components/icons/Key.tsx new file mode 100644 index 00000000000..a7abd6b9a00 --- /dev/null +++ b/src/components/icons/Key.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const Key_Stroke2_Corner2_Rounded = createSinglePathSVG({ + path: 'M3 12a4 4 0 0 1 7.212-2.385c.363.488.963.885 1.696.885h8.111l1.2 1.5-1.2 1.5h-1.783l-1.789-.894a1 1 0 0 0-.894 0l-1.79.894h-1.855c-.733 0-1.333.397-1.696.885A4 4 0 0 1 3 12Zm4-6a6 6 0 1 0 4.817 9.579.3.3 0 0 1 .076-.072l.017-.007H14a1 1 0 0 0 .447-.106L16 14.618l1.553.776c.139.07.292.106.447.106h2.02a2 2 0 0 0 1.561-.75l1.2-1.5a2 2 0 0 0 0-2.5l-1.2-1.5a2 2 0 0 0-1.562-.75h-8.11l-.016-.007a.3.3 0 0 1-.077-.071A6 6 0 0 0 7 6Zm0 7.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z', +}) diff --git a/src/screens/Settings/AccountSettings.tsx b/src/screens/Settings/AccountSettings.tsx index 0dcf651be7f..a7df1341d9b 100644 --- a/src/screens/Settings/AccountSettings.tsx +++ b/src/screens/Settings/AccountSettings.tsx @@ -74,8 +74,8 @@ export function AccountSettingsScreen({}: Props) { - {}} + Protect your account - - + diff --git a/src/screens/Settings/PrivacyAndSecuritySettings.tsx b/src/screens/Settings/PrivacyAndSecuritySettings.tsx new file mode 100644 index 00000000000..993dd92f3b5 --- /dev/null +++ b/src/screens/Settings/PrivacyAndSecuritySettings.tsx @@ -0,0 +1,93 @@ +import React from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {NativeStackScreenProps} from '@react-navigation/native-stack' + +import {CommonNavigatorParams} from '#/lib/routes/types' +import {useAppPasswordsQuery} from '#/state/queries/app-passwords' +import * as SettingsList from '#/screens/Settings/components/SettingsList' +import {atoms as a} from '#/alf' +import * as Admonition from '#/components/Admonition' +import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlashIcon} from '#/components/icons/EyeSlash' +import {Key_Stroke2_Corner2_Rounded as KeyIcon} from '#/components/icons/Key' +import {Verified_Stroke2_Corner2_Rounded as VerifiedIcon} from '#/components/icons/Verified' +import * as Layout from '#/components/Layout' +import {InlineLinkText} from '#/components/Link' +import {Email2FAToggle} from './components/Email2FAToggle' +import {PwiOptOut} from './components/PwiOptOut' + +type Props = NativeStackScreenProps< + CommonNavigatorParams, + 'PrivacyAndSecuritySettings' +> +export function PrivacyAndSecuritySettingsScreen({}: Props) { + const {_} = useLingui() + const {data: appPasswords} = useAppPasswordsQuery() + return ( + + + + + + + + Two-factor authentication + + + + + + + App passwords + + {appPasswords && appPasswords.length > 0 && ( + + {appPasswords.length} + + )} + + + + + + Logged-out visibility + + + + + + + + + + + + + Note: Bluesky is an open and public network. This setting + only limits the visibility of your content on the Bluesky + app and website, and other apps may not respect this + setting. Your content may still be shown to logged-out + users by other apps and websites. + + + + + Learn more about what is public on Bluesky. + + + + + + + + + + ) +} diff --git a/src/screens/Settings/components/Email2FAToggle.tsx b/src/screens/Settings/components/Email2FAToggle.tsx new file mode 100644 index 00000000000..d89e5f18e47 --- /dev/null +++ b/src/screens/Settings/components/Email2FAToggle.tsx @@ -0,0 +1,66 @@ +import React from 'react' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {useModalControls} from '#/state/modals' +import {useAgent, useSession} from '#/state/session' +import {DisableEmail2FADialog} from '#/view/screens/Settings/DisableEmail2FADialog' +import {useDialogControl} from '#/components/Dialog' +import * as Prompt from '#/components/Prompt' +import * as SettingsList from './SettingsList' + +export function Email2FAToggle() { + const {_} = useLingui() + const {currentAccount} = useSession() + const {openModal} = useModalControls() + const disableDialogControl = useDialogControl() + const enableDialogControl = useDialogControl() + const agent = useAgent() + + const enableEmailAuthFactor = React.useCallback(async () => { + if (currentAccount?.email) { + await agent.com.atproto.server.updateEmail({ + email: currentAccount.email, + emailAuthFactor: true, + }) + await agent.resumeSession(agent.session!) + } + }, [currentAccount, agent]) + + const onToggle = React.useCallback(() => { + if (!currentAccount) { + return + } + if (currentAccount.emailAuthFactor) { + disableDialogControl.open() + } else { + if (!currentAccount.emailConfirmed) { + openModal({ + name: 'verify-email', + onSuccess: enableDialogControl.open, + }) + return + } + enableDialogControl.open() + } + }, [currentAccount, enableDialogControl, openModal, disableDialogControl]) + + return ( + <> + + + + + ) +} diff --git a/src/screens/Settings/components/PwiOptOut.tsx b/src/screens/Settings/components/PwiOptOut.tsx new file mode 100644 index 00000000000..f47d1981c90 --- /dev/null +++ b/src/screens/Settings/components/PwiOptOut.tsx @@ -0,0 +1,99 @@ +import React from 'react' +import {View} from 'react-native' +import {ComAtprotoLabelDefs} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import { + useProfileQuery, + useProfileUpdateMutation, +} from '#/state/queries/profile' +import {useSession} from '#/state/session' +import {atoms as a, useTheme} from '#/alf' +import * as Toggle from '#/components/forms/Toggle' +import {Text} from '#/components/Typography' + +export function PwiOptOut() { + const t = useTheme() + const {_} = useLingui() + const {currentAccount} = useSession() + const {data: profile} = useProfileQuery({did: currentAccount?.did}) + const updateProfile = useProfileUpdateMutation() + + const isOptedOut = + profile?.labels?.some(l => l.val === '!no-unauthenticated') || false + const canToggle = profile && !updateProfile.isPending + + const onToggleOptOut = React.useCallback(() => { + if (!profile) { + return + } + let wasAdded = false + updateProfile.mutate({ + profile, + updates: existing => { + // create labels attr if needed + existing.labels = ComAtprotoLabelDefs.isSelfLabels(existing.labels) + ? existing.labels + : { + $type: 'com.atproto.label.defs#selfLabels', + values: [], + } + + // toggle the label + const hasLabel = existing.labels.values.some( + l => l.val === '!no-unauthenticated', + ) + if (hasLabel) { + wasAdded = false + existing.labels.values = existing.labels.values.filter( + l => l.val !== '!no-unauthenticated', + ) + } else { + wasAdded = true + existing.labels.values.push({val: '!no-unauthenticated'}) + } + + // delete if no longer needed + if (existing.labels.values.length === 0) { + delete existing.labels + } + return existing + }, + checkCommitted: res => { + const exists = !!res.data.labels?.some( + l => l.val === '!no-unauthenticated', + ) + return exists === wasAdded + }, + }) + }, [updateProfile, profile]) + + return ( + + + + + Discourage apps from showing my account to logged-out users + + + + + + + + Bluesky will not show your profile and posts to logged-out users. + Other apps may not honor this request. This does not make your account + private. + + + + ) +} diff --git a/src/screens/Settings/components/SettingsList.tsx b/src/screens/Settings/components/SettingsList.tsx index 8f281e8f56b..77458067826 100644 --- a/src/screens/Settings/components/SettingsList.tsx +++ b/src/screens/Settings/components/SettingsList.tsx @@ -17,10 +17,16 @@ export function Container({children}: {children: React.ReactNode}) { export function Item({ children, destructive = false, + iconInset = false, style, }: { children?: React.ReactNode destructive?: boolean + /** + * Adds left padding so that the content will be aligned with other Items that contain icons + * @default false + */ + iconInset?: boolean style?: StyleProp }) { const context = useMemo(() => ({destructive}), [destructive]) @@ -34,6 +40,15 @@ export function Item({ a.flex_1, a.flex_row, {minHeight: 48}, + iconInset && { + paddingLeft: + // existing padding + a.pl_xl.paddingLeft + + // icon + 28 + + // gap + a.gap_md.gap, + }, style, ]}> {children} @@ -45,10 +60,12 @@ export function LinkItem({ children, destructive = false, contentContainerStyle, + chevronColor, ...props }: LinkProps & { contentContainerStyle?: StyleProp destructive?: boolean + chevronColor?: string }) { const t = useTheme() @@ -62,7 +79,7 @@ export function LinkItem({ contentContainerStyle, ]}> {typeof children === 'function' ? children(args) : children} - + )} diff --git a/src/view/screens/Settings/DisableEmail2FADialog.tsx b/src/view/screens/Settings/DisableEmail2FADialog.tsx index e4341fcd21d..e3d08851772 100644 --- a/src/view/screens/Settings/DisableEmail2FADialog.tsx +++ b/src/view/screens/Settings/DisableEmail2FADialog.tsx @@ -108,7 +108,11 @@ export function DisableEmail2FADialog({ {error ? : undefined} {stage === Stages.Email ? ( - +