An ATproto social media client -- with an independent Appview.

Settings revamp (#5745)

* start building storybook

* add title

* add some styles

* try out new icons

* more settings list component parts

* make text do the spacing

* clean up storybook

* gated new settings screen

* switch account

* add current profile

* use Layout.Screen

* Layout.Header and Layout.Content

* translate helpdesk text

thanks @surfdude29!

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* add account settings

* undo changes to export car dialog

* privacy and security screen

* Translate protect account stuff

Thanks @surfdude29!

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* content and media settings

* about settings

* 2fa copy

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* a11y and appearance

* use new components for appearance settings

* redesign accessibility settings

* Update ContentAndMediaSettings.tsx

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* add divider

* remove a11y and appearance middleman screen

* fix web settingslist styles

* new SettingsList.Group component

* explain how portal magic works

* hide pwioptout in old location

* Update Settings.tsx

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* replace gate with `IS_INTERNAL`

* add IS_INTERNAL to app-info.web

* fix profile area growing

* add close button to switch account

---------

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

authored by samuel.fm

surfdude29 and committed by
GitHub
c8f264b7 ab492cd7

+1811 -133
+1
assets/icons/bubbles_stroke2_corner2_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M6.002 6a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-1v1a3 3 0 0 1-3 3h-4.24l-4.274 2.374a1 1 0 0 1-1.486-.874V19a3 3 0 0 1-3-3v-6a3 3 0 0 1 3-3h1V6Zm-1 3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h1a1 1 0 0 1 1 1v.8l3.015-1.674a1 1 0 0 1 .485-.126h4.5a1 1 0 0 0 1-1v-1.933a1 1 0 0 1 0-.134V10a1 1 0 0 0-1-1h-10Zm13 4v-3a3 3 0 0 0-3-3h-7V6a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-1Z" clip-rule="evenodd"/></svg>
+1
assets/icons/car_stroke2_corner2_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M7.018 6a1 1 0 0 0-.808.412l-.81-.588.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" clip-rule="evenodd"/></svg>
+1 -1
assets/icons/circleQuestion_stroke2_corner2_rounded.svg
··· 1 - <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="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"/><path fill="#000" fill-rule="evenodd" d="M12 9a1 1 0 0 0-.879.522 1 1 0 0 1-1.754-.96A3 3 0 0 1 12 7c1.515 0 2.567 1.006 2.866 2.189.302 1.189-.156 2.574-1.524 3.258A.62.62 0 0 0 13 13a1 1 0 1 1-2 0c0-.992.56-1.898 1.447-2.342.455-.227.572-.618.48-.978C12.836 9.314 12.529 9 12 9Z" clip-rule="evenodd"/><path fill="#000" d="M13 16a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"/></svg> 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="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 12Zm10-3a1 1 0 0 0-.879.522 1 1 0 0 1-1.754-.96A3 3 0 0 1 12 7c1.515 0 2.567 1.006 2.866 2.189.302 1.189-.156 2.574-1.524 3.258A.62.62 0 0 0 13 13a1 1 0 1 1-2 0c0-.992.56-1.898 1.447-2.342.455-.227.572-.618.48-.978C12.836 9.314 12.529 9 12 9Zm1 7a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"/></svg>
+1
assets/icons/codeBrackets_stroke2_corner2_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M14.243 3.03a1 1 0 0 1 .727 1.213l-4 16a1 1 0 1 1-1.94-.485l4-16a1 1 0 0 1 1.213-.728ZM6.707 7.293a1 1 0 0 1 0 1.414l-2.586 2.586a1 1 0 0 0 0 1.414l2.586 2.586a1 1 0 1 1-1.414 1.414l-2.586-2.586a3 3 0 0 1 0-4.242l2.586-2.586a1 1 0 0 1 1.414 0Zm10.586 0a1 1 0 0 1 1.414 0l2.586 2.586a3 3 0 0 1 0 4.242l-2.586 2.586a1 1 0 1 1-1.414-1.414l2.586-2.586a1 1 0 0 0 0-1.414l-2.586-2.586a1 1 0 0 1 0-1.414Z" clip-rule="evenodd"/></svg>
+1
assets/icons/codeLines_stroke2_corner2_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M2 5a1 1 0 0 1 1-1h10a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm15 0a1 1 0 0 1 1-1h3a1 1 0 1 1 0 2h-3a1 1 0 0 1-1-1ZM2 12a1 1 0 0 1 1-1h5a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm10 0a1 1 0 0 1 1-1h8a1 1 0 1 1 0 2h-8a1 1 0 0 1-1-1ZM2 19a1 1 0 0 1 1-1h7a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm12 0a1 1 0 0 1 1-1h6a1 1 0 1 1 0 2h-6a1 1 0 0 1-1-1Z" clip-rule="evenodd"/></svg>
+1
assets/icons/earth_stroke2_corner2_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M4.4 9.493C4.14 10.28 4 11.124 4 12a8 8 0 1 0 10.899-7.459l-.67 2.679a2.95 2.95 0 0 1-2.14 2.142l-2.173.547a.32.32 0 0 0-.205.164 2.316 2.316 0 0 1-3.457.81L4.4 9.493Zm.883-1.84 2.171 1.63a.315.315 0 0 0 .471-.11c.303-.6.851-1.04 1.503-1.204l2.174-.546a.95.95 0 0 0 .687-.688l.97.242-.97-.242.67-2.678a7.99 7.99 0 0 0-7.676 3.597ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm8.048.543a2.2 2.2 0 0 1 1.69-.636l.827.053c.52.033 1.023.204 1.456.495l1.37.921a2.453 2.453 0 0 1-1.367 4.489h-.98a2.95 2.95 0 0 1-2.45-1.312L9.77 15.32a2.2 2.2 0 0 1 .278-2.776Zm1.563 1.36a.197.197 0 0 0-.177.306l.823 1.235c.176.263.471.42.787.42h.98a.453.453 0 0 0 .252-.828l-1.37-.921a.95.95 0 0 0-.468-.159l-.827-.053Z" clip-rule="evenodd"/></svg>
+1
assets/icons/freeze_stroke2_corner2_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="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" clip-rule="evenodd"/></svg>
+1
assets/icons/haptic_stroke2_corner2_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M9 5a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H9ZM6 6a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H9a3 3 0 0 1-3-3V6Zm4 1a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Zm-5.793.293a1 1 0 0 1 0 1.414L3.155 9.76a.546.546 0 0 0-.05.713 2.55 2.55 0 0 1 0 3.056.546.546 0 0 0 .05.713l1.052 1.052a1 1 0 1 1-1.414 1.414L1.74 15.655a2.546 2.546 0 0 1-.237-3.327.55.55 0 0 0 0-.655 2.546 2.546 0 0 1 .237-3.328l1.052-1.052a1 1 0 0 1 1.414 0Zm15.586 0a1 1 0 0 1 1.414 0l1.052 1.052c.896.896.997 2.314.237 3.327a.55.55 0 0 0 0 .656 2.546 2.546 0 0 1-.237 3.327l-1.052 1.052a1 1 0 0 1-1.414-1.414l1.052-1.052a.546.546 0 0 0 .05-.713 2.55 2.55 0 0 1 0-3.056.546.546 0 0 0-.05-.713l-1.052-1.052a1 1 0 0 1 0-1.414Z" clip-rule="evenodd"/></svg>
+1
assets/icons/home_stroke2_corner2_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12.659 3.905a1 1 0 0 0-1.318 0l-6 5.25A1 1 0 0 0 5 9.907V18a1 1 0 0 0 1 1h3v-3a3 3 0 0 1 6 0v3h3a1 1 0 0 0 1-1V9.907a1 1 0 0 0-.341-.752l-6-5.25ZM10.024 2.4a3 3 0 0 1 3.952 0l6 5.25A3 3 0 0 1 21 9.907V18a3 3 0 0 1-3 3h-3a2 2 0 0 1-2-2v-3a1 1 0 0 0-2 0v3a2 2 0 0 1-2 2H6a3 3 0 0 1-3-3V9.907A3 3 0 0 1 4.024 7.65l6-5.25Z" clip-rule="evenodd"/></svg>
+1
assets/icons/key_stroke2_corner2_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="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" clip-rule="evenodd"/></svg>
+1
assets/icons/macintosh_stroke2_corner2_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M4 5a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v11a3 3 0 0 1-1 2.236V20a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-1.764c-.614-.55-1-1.348-1-2.236V5Zm3 14v1h10v-1H7ZM7 4a1 1 0 0 0-1 1v11a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H7Zm0 2a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V6Zm2 1v4h6V7H9Zm4 8a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Z" clip-rule="evenodd"/></svg>
+1
assets/icons/newspaper_stroke2_corner2_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M1 6.5A2.5 2.5 0 0 1 3.5 4H9a4 4 0 0 1 3 1.354A4 4 0 0 1 15 4h5.5A2.5 2.5 0 0 1 23 6.5v11a2.5 2.5 0 0 1-2.5 2.5h-5.223c-.52 0-1 .125-1.4.372-.421.26-.761.633-.983 1.075a1 1 0 0 1-1.788 0 2.66 2.66 0 0 0-.983-1.075c-.4-.247-.88-.372-1.4-.372H3.5A2.5 2.5 0 0 1 1 17.5v-11ZM11 8a2 2 0 0 0-2-2H3.5a.5.5 0 0 0-.5.5v11a.5.5 0 0 0 .5.5h5.223c.776 0 1.564.173 2.277.569V8Zm2 10.569A4.7 4.7 0 0 1 15.277 18H20.5a.5.5 0 0 0 .5-.5v-11a.5.5 0 0 0-.5-.5H15a2 2 0 0 0-2 2v10.569Z" clip-rule="evenodd"/></svg>
+1
assets/icons/pencilLine_stroke2_corner2_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="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" clip-rule="evenodd"/></svg>
+1
assets/icons/personGroup_stroke2_corner2_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="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" clip-rule="evenodd"/></svg>
+1
assets/icons/raisingHand4finger_stroke2_corner2_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12.5 4a.5.5 0 0 0-.5.5V10a1 1 0 1 1-2 0V5.5a.5.5 0 0 0-1 0V11a1 1 0 1 1-2 0V7.5a.5.5 0 0 0-1 0v6a6.5 6.5 0 1 0 13 0V9a1.996 1.996 0 0 0-2 2v.838c0 .826-.529 1.559-1.312 1.82A2.47 2.47 0 0 0 14 16a1 1 0 1 1-2 0 4.47 4.47 0 0 1 3-4.22V11c0-1.014.379-1.941 1-2.646V5.5a.5.5 0 0 0-1 0V10a1 1 0 1 1-2 0V4.5a.5.5 0 0 0-.5-.5Zm2.112-.838A2.5 2.5 0 0 1 18 5.5v1.626q.481-.124 1-.126a2 2 0 0 1 2 2v4.5a8.5 8.5 0 0 1-17 0v-6a2.5 2.5 0 0 1 3.039-2.442 2.5 2.5 0 0 1 3.349-1.896 2.498 2.498 0 0 1 4.224 0Z" clip-rule="evenodd"/></svg>
+1
assets/icons/wrench_stroke2_corner2_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M14.5 4a5.5 5.5 0 0 0-5.078 7.616 1 1 0 0 1-.216 1.092L4.37 17.543a1 1 0 0 0 0 1.414l.672.672a1 1 0 0 0 1.414 0l4.835-4.835a1 1 0 0 1 1.092-.216A5.5 5.5 0 0 0 20 9.414l-1.293 1.293a3.828 3.828 0 1 1-5.414-5.414L14.585 4H14.5ZM7 9.5a7.5 7.5 0 0 1 9.969-7.084 1 1 0 0 1 .378 1.651l-2.64 2.64a1.829 1.829 0 0 0 2.586 2.586l2.64-2.64a1 1 0 0 1 1.65.378 7.5 7.5 0 0 1-9.328 9.627l-4.384 4.385a3 3 0 0 1-4.242 0l-.672-.672a3 3 0 0 1 0-4.242l4.385-4.385A7.5 7.5 0 0 1 7 9.5Z" clip-rule="evenodd"/></svg>
+4
bskyweb/cmd/bskyweb/server.go
··· 253 253 e.GET("/settings/external-embeds", server.WebGeneric) 254 254 e.GET("/settings/accessibility", server.WebGeneric) 255 255 e.GET("/settings/appearance", server.WebGeneric) 256 + e.GET("/settings/account", server.WebGeneric) 257 + e.GET("/settings/privacy-and-security", server.WebGeneric) 258 + e.GET("/settings/content-and-media", server.WebGeneric) 259 + e.GET("/settings/about", server.WebGeneric) 256 260 e.GET("/sys/debug", server.WebGeneric) 257 261 e.GET("/sys/debug-mod", server.WebGeneric) 258 262 e.GET("/sys/log", server.WebGeneric)
+37 -1
src/Navigation.tsx
··· 95 95 import {useTheme} from '#/alf' 96 96 import {router} from '#/routes' 97 97 import {Referrer} from '../modules/expo-bluesky-swiss-army' 98 + import {AboutSettingsScreen} from './screens/Settings/AboutSettings' 99 + import {AccountSettingsScreen} from './screens/Settings/AccountSettings' 100 + import {ContentAndMediaSettingsScreen} from './screens/Settings/ContentAndMediaSettings' 101 + import {PrivacyAndSecuritySettingsScreen} from './screens/Settings/PrivacyAndSecuritySettings' 98 102 99 103 const navigationRef = createNavigationContainerRef<AllNavigatorParams>() 100 104 ··· 322 326 name="AppearanceSettings" 323 327 getComponent={() => AppearanceSettingsScreen} 324 328 options={{ 325 - title: title(msg`Appearance Settings`), 329 + title: title(msg`Appearance`), 330 + requireAuth: true, 331 + }} 332 + /> 333 + <Stack.Screen 334 + name="AccountSettings" 335 + getComponent={() => AccountSettingsScreen} 336 + options={{ 337 + title: title(msg`Account`), 338 + requireAuth: true, 339 + }} 340 + /> 341 + <Stack.Screen 342 + name="PrivacyAndSecuritySettings" 343 + getComponent={() => PrivacyAndSecuritySettingsScreen} 344 + options={{ 345 + title: title(msg`Privacy and Security`), 346 + requireAuth: true, 347 + }} 348 + /> 349 + <Stack.Screen 350 + name="ContentAndMediaSettings" 351 + getComponent={() => ContentAndMediaSettingsScreen} 352 + options={{ 353 + title: title(msg`Content and Media`), 354 + requireAuth: true, 355 + }} 356 + /> 357 + <Stack.Screen 358 + name="AboutSettings" 359 + getComponent={() => AboutSettingsScreen} 360 + options={{ 361 + title: title(msg`About`), 326 362 requireAuth: true, 327 363 }} 328 364 />
+3
src/alf/atoms.ts
··· 102 102 /* 103 103 * Flex 104 104 */ 105 + gap_0: { 106 + gap: 0, 107 + }, 105 108 gap_2xs: { 106 109 gap: tokens.space._2xs, 107 110 },
+72 -13
src/components/Layout.tsx
··· 1 - import React from 'react' 1 + import React, {useContext, useMemo} from 'react' 2 2 import {View, ViewStyle} from 'react-native' 3 3 import {StyleProp} from 'react-native' 4 4 import {useSafeAreaInsets} from 'react-native-safe-area-context' 5 5 6 + import {ViewHeader} from '#/view/com/util/ViewHeader' 7 + import {ScrollView} from '#/view/com/util/Views' 8 + import {CenteredView} from '#/view/com/util/Views' 6 9 import {atoms as a} from '#/alf' 7 10 8 11 // Every screen should have a Layout component wrapping it. 9 12 // This component provides a default padding for the top of the screen. 10 13 // This allows certain screens to avoid the top padding if they want to. 11 - // 12 - // In a future PR I will add a unified header component to this file and 13 - // things like a preconfigured scrollview. 14 + 15 + const LayoutContext = React.createContext({ 16 + withinScreen: false, 17 + topPaddingDisabled: false, 18 + withinScrollView: false, 19 + }) 14 20 15 21 /** 16 22 * Every screen should have a Layout.Screen component wrapping it. ··· 18 24 * and height/minHeight 19 25 */ 20 26 let Screen = ({ 21 - disableTopPadding, 27 + disableTopPadding = false, 22 28 style, 23 29 ...props 24 30 }: React.ComponentProps<typeof View> & { ··· 26 32 style?: StyleProp<ViewStyle> 27 33 }): React.ReactNode => { 28 34 const {top} = useSafeAreaInsets() 35 + const context = useMemo( 36 + () => ({ 37 + withinScreen: true, 38 + topPaddingDisabled: disableTopPadding, 39 + withinScrollView: false, 40 + }), 41 + [disableTopPadding], 42 + ) 29 43 return ( 30 - <View 31 - style={[ 32 - {paddingTop: disableTopPadding ? 0 : top}, 33 - a.util_screen_outer, 34 - style, 35 - ]} 36 - {...props} 37 - /> 44 + <LayoutContext.Provider value={context}> 45 + <View 46 + style={[ 47 + {paddingTop: disableTopPadding ? 0 : top}, 48 + a.util_screen_outer, 49 + style, 50 + ]} 51 + {...props} 52 + /> 53 + </LayoutContext.Provider> 38 54 ) 39 55 } 40 56 Screen = React.memo(Screen) 41 57 export {Screen} 58 + 59 + let Header = ( 60 + props: React.ComponentProps<typeof ViewHeader>, 61 + ): React.ReactNode => { 62 + const {withinScrollView} = useContext(LayoutContext) 63 + if (!withinScrollView) { 64 + return ( 65 + <CenteredView topBorder={false} sideBorders> 66 + <ViewHeader showOnDesktop showBorder {...props} /> 67 + </CenteredView> 68 + ) 69 + } else { 70 + return <ViewHeader showOnDesktop showBorder {...props} /> 71 + } 72 + } 73 + Header = React.memo(Header) 74 + export {Header} 75 + 76 + let Content = ({ 77 + style, 78 + contentContainerStyle, 79 + ...props 80 + }: React.ComponentProps<typeof ScrollView> & { 81 + style?: StyleProp<ViewStyle> 82 + contentContainerStyle?: StyleProp<ViewStyle> 83 + }): React.ReactNode => { 84 + const context = useContext(LayoutContext) 85 + const newContext = useMemo( 86 + () => ({...context, withinScrollView: true}), 87 + [context], 88 + ) 89 + return ( 90 + <LayoutContext.Provider value={newContext}> 91 + <ScrollView 92 + style={[a.flex_1, style]} 93 + contentContainerStyle={[{paddingBottom: 100}, contentContainerStyle]} 94 + {...props} 95 + /> 96 + </LayoutContext.Provider> 97 + ) 98 + } 99 + Content = React.memo(Content) 100 + export {Content}
+1 -1
src/components/dialogs/BirthDateSettings.tsx
··· 29 29 const {isLoading, error, data: preferences} = usePreferencesQuery() 30 30 31 31 return ( 32 - <Dialog.Outer control={control}> 32 + <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 33 33 <Dialog.Handle /> 34 34 <Dialog.ScrollableInner label={_(msg`My Birthday`)}> 35 35 <View style={[a.gap_sm, a.pb_lg]}>
+2
src/components/dialogs/SwitchAccount.tsx
··· 56 56 pendingDid={pendingDid} 57 57 /> 58 58 </View> 59 + 60 + <Dialog.Close /> 59 61 </Dialog.ScrollableInner> 60 62 </Dialog.Outer> 61 63 )
+4
src/components/icons/Bubble.tsx
··· 11 11 export const Bubble_Stroke2_Corner3_Rounded = createSinglePathSVG({ 12 12 path: 'M2.002 7a4 4 0 0 1 4-4h12a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H12.28l-4.762 2.858A1 1 0 0 1 6.002 21v-2a4 4 0 0 1-4-4V7Zm4-2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h1a1 1 0 0 1 1 1v1.234l3.486-2.092a1 1 0 0 1 .514-.142h6a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-12Z', 13 13 }) 14 + 15 + export const Bubbles_Stroke2_Corner2_Rounded = createSinglePathSVG({ 16 + path: 'M6.002 6a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-1v1a3 3 0 0 1-3 3h-4.24l-4.274 2.374a1 1 0 0 1-1.486-.874V19a3 3 0 0 1-3-3v-6a3 3 0 0 1 3-3h1V6Zm-1 3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h1a1 1 0 0 1 1 1v.8l3.015-1.674a1 1 0 0 1 .485-.126h4.5a1 1 0 0 0 1-1v-1.933a1 1 0 0 1 0-.134V10a1 1 0 0 0-1-1h-10Zm13 4v-3a3 3 0 0 0-3-3h-7V6a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-1Z', 17 + })
+5
src/components/icons/Car.tsx
··· 1 + import {createSinglePathSVG} from './TEMPLATE' 2 + 3 + export const Car_Stroke2_Corner2_Rounded = createSinglePathSVG({ 4 + 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', 5 + })
+1 -1
src/components/icons/CircleQuestion.tsx
··· 1 1 import {createSinglePathSVG} from './TEMPLATE' 2 2 3 3 export const CircleQuestion_Stroke2_Corner2_Rounded = createSinglePathSVG({ 4 - 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"/><path fill="#000" fill-rule="evenodd" d="M12 9a1 1 0 0 0-.879.522 1 1 0 0 1-1.754-.96A3 3 0 0 1 12 7c1.515 0 2.567 1.006 2.866 2.189.302 1.189-.156 2.574-1.524 3.258A.62.62 0 0 0 13 13a1 1 0 1 1-2 0c0-.992.56-1.898 1.447-2.342.455-.227.572-.618.48-.978C12.836 9.314 12.529 9 12 9Z', 4 + 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 M12 9a1 1 0 0 0-.879.522 1 1 0 0 1-1.754-.96A3 3 0 0 1 12 7c1.515 0 2.567 1.006 2.866 2.189.302 1.189-.156 2.574-1.524 3.258A.62.62 0 0 0 13 13a1 1 0 1 1-2 0c0-.992.56-1.898 1.447-2.342.455-.227.572-.618.48-.978C12.836 9.314 12.529 9 12 9Z M13 16a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z', 5 5 })
+4
src/components/icons/CodeBrackets.tsx
··· 3 3 export const CodeBrackets_Stroke2_Corner0_Rounded = createSinglePathSVG({ 4 4 path: 'M14.242 3.03a1 1 0 0 1 .728 1.213l-4 16a1 1 0 1 1-1.94-.485l4-16a1 1 0 0 1 1.213-.728ZM6.707 7.293a1 1 0 0 1 0 1.414L3.414 12l3.293 3.293a1 1 0 1 1-1.414 1.414l-4-4a1 1 0 0 1 0-1.414l4-4a1 1 0 0 1 1.414 0Zm10.586 0a1 1 0 0 1 1.414 0l4 4a1 1 0 0 1 0 1.414l-4 4a1 1 0 1 1-1.414-1.414L20.586 12l-3.293-3.293a1 1 0 0 1 0-1.414Z', 5 5 }) 6 + 7 + export const CodeBrackets_Stroke2_Corner2_Rounded = createSinglePathSVG({ 8 + path: 'M14.243 3.03a1 1 0 0 1 .727 1.213l-4 16a1 1 0 1 1-1.94-.485l4-16a1 1 0 0 1 1.213-.728ZM6.707 7.293a1 1 0 0 1 0 1.414l-2.586 2.586a1 1 0 0 0 0 1.414l2.586 2.586a1 1 0 1 1-1.414 1.414l-2.586-2.586a3 3 0 0 1 0-4.242l2.586-2.586a1 1 0 0 1 1.414 0Zm10.586 0a1 1 0 0 1 1.414 0l2.586 2.586a3 3 0 0 1 0 4.242l-2.586 2.586a1 1 0 1 1-1.414-1.414l2.586-2.586a1 1 0 0 0 0-1.414l-2.586-2.586a1 1 0 0 1 0-1.414Z', 9 + })
+5
src/components/icons/CodeLines.tsx
··· 1 + import {createSinglePathSVG} from './TEMPLATE' 2 + 3 + export const CodeLines_Stroke2_Corner2_Rounded = createSinglePathSVG({ 4 + path: 'M2 5a1 1 0 0 1 1-1h10a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm15 0a1 1 0 0 1 1-1h3a1 1 0 1 1 0 2h-3a1 1 0 0 1-1-1ZM2 12a1 1 0 0 1 1-1h5a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm10 0a1 1 0 0 1 1-1h8a1 1 0 1 1 0 2h-8a1 1 0 0 1-1-1ZM2 19a1 1 0 0 1 1-1h7a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm12 0a1 1 0 0 1 1-1h6a1 1 0 1 1 0 2h-6a1 1 0 0 1-1-1Z', 5 + })
+5
src/components/icons/Freeze.tsx
··· 1 + import {createSinglePathSVG} from './TEMPLATE' 2 + 3 + export const Freeze_Stroke2_Corner2_Rounded = createSinglePathSVG({ 4 + 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', 5 + })
+4
src/components/icons/Globe.tsx
··· 7 7 export const Earth_Stroke2_Corner0_Rounded = createSinglePathSVG({ 8 8 path: 'M4.4 9.493C4.14 10.28 4 11.124 4 12a8 8 0 1 0 10.899-7.459l-.953 3.81a1 1 0 0 1-.726.727l-3.444.866-.772 1.533a1 1 0 0 1-1.493.35L4.4 9.493Zm.883-1.84L7.756 9.51l.44-.874a1 1 0 0 1 .649-.52l3.306-.832.807-3.227a7.993 7.993 0 0 0-7.676 3.597ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm8.43.162a1 1 0 0 1 .77-.29l1.89.121a1 1 0 0 1 .494.168l2.869 1.928a1 1 0 0 1 .336 1.277l-.973 1.946a1 1 0 0 1-.894.553h-2.92a1 1 0 0 1-.831-.445L9.225 14.5a1 1 0 0 1 .126-1.262l1.08-1.076Zm.915 1.913.177-.177 1.171.074 1.914 1.286-.303.607h-1.766l-1.194-1.79Z', 9 9 }) 10 + 11 + export const Earth_Stroke2_Corner2_Rounded = createSinglePathSVG({ 12 + path: 'M4.4 9.493C4.14 10.28 4 11.124 4 12a8 8 0 1 0 10.899-7.459l-.67 2.679a2.95 2.95 0 0 1-2.14 2.142l-2.173.547a.32.32 0 0 0-.205.164 2.316 2.316 0 0 1-3.457.81L4.4 9.493Zm.883-1.84 2.171 1.63a.315.315 0 0 0 .471-.11c.303-.6.851-1.04 1.503-1.204l2.174-.546a.95.95 0 0 0 .687-.688l.97.242-.97-.242.67-2.678a7.993 7.993 0 0 0-7.676 3.597ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm8.048.543a2.2 2.2 0 0 1 1.69-.636l.827.053c.52.033 1.023.204 1.456.495l1.37.921a2.453 2.453 0 0 1-1.367 4.489h-.98a2.95 2.95 0 0 1-2.45-1.312L9.77 15.32a2.2 2.2 0 0 1 .278-2.776Zm1.563 1.36a.197.197 0 0 0-.177.306l.823 1.235c.176.263.471.42.787.42h.98a.453.453 0 0 0 .252-.828l-1.37-.921a.95.95 0 0 0-.468-.159l-.827-.053Z', 13 + })
+5
src/components/icons/Haptic.tsx
··· 1 + import {createSinglePathSVG} from './TEMPLATE' 2 + 3 + export const Haptic_Stroke2_Corner2_Rounded = createSinglePathSVG({ 4 + path: 'M9 5a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H9ZM6 6a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H9a3 3 0 0 1-3-3V6Zm4 1a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Zm-5.793.293a1 1 0 0 1 0 1.414L3.155 9.76a.546.546 0 0 0-.05.713c.678.906.678 2.15 0 3.056a.546.546 0 0 0 .05.713l1.052 1.052a1 1 0 1 1-1.414 1.414L1.74 15.655a2.546 2.546 0 0 1-.237-3.327.55.55 0 0 0 0-.655 2.546 2.546 0 0 1 .237-3.328l1.052-1.052a1 1 0 0 1 1.414 0Zm15.586 0a1 1 0 0 1 1.414 0l1.052 1.052c.896.896.997 2.314.237 3.327a.55.55 0 0 0 0 .656 2.546 2.546 0 0 1-.237 3.327l-1.052 1.052a1 1 0 0 1-1.414-1.414l1.052-1.052a.546.546 0 0 0 .05-.713 2.55 2.55 0 0 1 0-3.056.546.546 0 0 0-.05-.713l-1.052-1.052a1 1 0 0 1 0-1.414Z', 5 + })
+5 -1
src/components/icons/Home.tsx
··· 1 1 import {createSinglePathSVG} from './TEMPLATE' 2 2 3 3 export const Home_Stroke2_Corner0_Rounded = createSinglePathSVG({ 4 - path: 'M11.46 1.362a2 2 0 0 1 1.08 0c.249.07.448.188.611.301.146.102.306.232.467.363l6.421 5.218.046.036c.169.137.38.308.54.53a2 2 0 0 1 .304.64c.073.264.072.536.071.753v9.229c0 .252 0 .498-.017.706a2.023 2.023 0 0 1-.201.77 2 2 0 0 1-.874.874 2.02 2.02 0 0 1-.77.201c-.208.017-.454.017-.706.017H5.568c-.252 0-.498 0-.706-.017a2.02 2.02 0 0 1-.77-.201 2 2 0 0 1-.874-.874 2.022 2.022 0 0 1-.201-.77C3 18.93 3 18.684 3 18.432V9.203c0-.217-.002-.49.07-.754a2 2 0 0 1 .304-.638c.16-.223.372-.394.541-.53l.045-.037 6.422-5.218c.161-.13.321-.26.467-.362.163-.114.362-.232.612-.302Zm.532 1.943c-.077.054-.18.136-.37.29l-6.4 5.2a6.315 6.315 0 0 0-.215.18c-.002 0-.003.002-.004.003v.004C5 9.036 5 9.112 5 9.262V18.4a8.18 8.18 0 0 0 .011.588l.014.002c.116.01.278.01.575.01H8v-5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v5h2.4a8.207 8.207 0 0 0 .589-.012v-.013c.01-.116.011-.279.011-.575V9.262c0-.15 0-.226-.003-.28v-.004l-.003-.003a6.448 6.448 0 0 0-.216-.18l-6.4-5.2a7.373 7.373 0 0 0-.37-.29L12 3.299l-.008.006ZM14 19v-5h-4v5h4Z', 4 + path: 'M11.37 1.724a1 1 0 0 1 1.26 0l8 6.5A1 1 0 0 1 21 9v11a1 1 0 0 1-1 1h-6a1 1 0 0 1-1-1v-5h-2v5a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 .37-.776l8-6.5ZM5 9.476V19h4v-5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v5h4V9.476l-7-5.688-7 5.688Z', 5 + }) 6 + 7 + export const Home_Stroke2_Corner2_Rounded = createSinglePathSVG({ 8 + path: 'M12.659 3.905a1 1 0 0 0-1.318 0l-6 5.25A1 1 0 0 0 5 9.907V18a1 1 0 0 0 1 1h3v-3a3 3 0 0 1 6 0v3h3a1 1 0 0 0 1-1V9.907a1 1 0 0 0-.341-.752l-6-5.25ZM10.024 2.4a3 3 0 0 1 3.952 0l6 5.25A3 3 0 0 1 21 9.907V18a3 3 0 0 1-3 3h-3a2 2 0 0 1-2-2v-3a1 1 0 0 0-2 0v3a2 2 0 0 1-2 2H6a3 3 0 0 1-3-3V9.907A3 3 0 0 1 4.024 7.65l6-5.25Z', 5 9 }) 6 10 7 11 export const Home_Filled_Corner0_Rounded = createSinglePathSVG({
+5
src/components/icons/Key.tsx
··· 1 + import {createSinglePathSVG} from './TEMPLATE' 2 + 3 + export const Key_Stroke2_Corner2_Rounded = createSinglePathSVG({ 4 + 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', 5 + })
+5
src/components/icons/Macintosh.tsx
··· 1 + import {createSinglePathSVG} from './TEMPLATE' 2 + 3 + export const Macintosh_Stroke2_Corner2_Rounded = createSinglePathSVG({ 4 + path: 'M4 5a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v11c0 .889-.386 1.687-1 2.236V20a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-1.764c-.614-.55-1-1.348-1-2.236V5Zm3 14v1h10v-1H7ZM7 4a1 1 0 0 0-1 1v11a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H7Zm0 2a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V6Zm2 1v4h6V7H9Zm4 8a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Z', 5 + })
+5
src/components/icons/Newspaper.tsx
··· 1 + import {createSinglePathSVG} from './TEMPLATE' 2 + 3 + export const Newspaper_Stroke2_Corner2_Rounded = createSinglePathSVG({ 4 + path: 'M1 6.5A2.5 2.5 0 0 1 3.5 4H9a4 4 0 0 1 3 1.354A4 4 0 0 1 15 4h5.5A2.5 2.5 0 0 1 23 6.5v11a2.5 2.5 0 0 1-2.5 2.5h-5.223c-.52 0-1 .125-1.4.372-.421.26-.761.633-.983 1.075a1 1 0 0 1-1.788 0 2.66 2.66 0 0 0-.983-1.075c-.4-.247-.88-.372-1.4-.372H3.5A2.5 2.5 0 0 1 1 17.5v-11ZM11 8a2 2 0 0 0-2-2H3.5a.5.5 0 0 0-.5.5v11a.5.5 0 0 0 .5.5h5.223c.776 0 1.564.173 2.277.569V8Zm2 10.569A4.7 4.7 0 0 1 15.277 18H20.5a.5.5 0 0 0 .5-.5v-11a.5.5 0 0 0-.5-.5H15a2 2 0 0 0-2 2v10.569Z', 5 + })
+4
src/components/icons/Pencil.tsx
··· 7 7 export const PencilLine_Stroke2_Corner0_Rounded = createSinglePathSVG({ 8 8 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', 9 9 }) 10 + 11 + export const PencilLine_Stroke2_Corner2_Rounded = createSinglePathSVG({ 12 + 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', 13 + })
+4
src/components/icons/Person.tsx
··· 23 23 export const PersonPlus_Filled_Stroke2_Corner0_Rounded = createSinglePathSVG({ 24 24 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', 25 25 }) 26 + 27 + export const PersonGroup_Stroke2_Corner2_Rounded = createSinglePathSVG({ 28 + 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', 29 + })
+5 -1
src/components/icons/RaisingHand.tsx
··· 1 1 import {createSinglePathSVG} from './TEMPLATE' 2 2 3 - export const RaisingHande4Finger_Stroke2_Corner0_Rounded = createSinglePathSVG({ 3 + export const RaisingHand4Finger_Stroke2_Corner0_Rounded = createSinglePathSVG({ 4 4 path: 'M10.25 4a.75.75 0 0 0-.75.75V11a1 1 0 1 1-2 0V6.75a.75.75 0 0 0-1.5 0V14a6 6 0 0 0 12 0V9a2 2 0 0 0-2 2v1.5a1 1 0 0 1-.684.949l-.628.21A2.469 2.469 0 0 0 13 16a1 1 0 1 1-2 0 4.469 4.469 0 0 1 3-4.22V11c0-.703.181-1.364.5-1.938V5.75a.75.75 0 0 0-1.5 0V9a1 1 0 1 1-2 0V4.75a.75.75 0 0 0-.75-.75Zm2.316-.733A2.75 2.75 0 0 1 16.5 5.75v1.54c.463-.187.97-.29 1.5-.29h1a1 1 0 0 1 1 1v6a8 8 0 1 1-16 0V6.75a2.75 2.75 0 0 1 3.571-2.625 2.751 2.751 0 0 1 4.995-.858Z', 5 5 }) 6 + 7 + export const RaisingHand4Finger_Stroke2_Corner2_Rounded = createSinglePathSVG({ 8 + path: 'M12.5 4a.5.5 0 0 0-.5.5V10a1 1 0 1 1-2 0V5.5a.5.5 0 0 0-1 0V11a1 1 0 1 1-2 0V7.5a.5.5 0 0 0-1 0v6a6.5 6.5 0 1 0 13 0V9c-.513 0-.979.192-1.333.509-.41.368-.667.899-.667 1.491v.838c0 .826-.529 1.559-1.312 1.82A2.47 2.47 0 0 0 14 16a1 1 0 1 1-2 0 4.47 4.47 0 0 1 3-4.22V11c0-1.014.379-1.941 1-2.646V5.5a.5.5 0 0 0-1 0V10a1 1 0 1 1-2 0V4.5a.5.5 0 0 0-.5-.5Zm2.112-.838A2.5 2.5 0 0 1 18 5.5v1.626q.481-.124 1-.126a2 2 0 0 1 2 2v4.5a8.5 8.5 0 0 1-17 0v-6a2.5 2.5 0 0 1 3.039-2.442 2.5 2.5 0 0 1 3.349-1.896 2.498 2.498 0 0 1 4.224 0Z', 9 + })
+5
src/components/icons/Wrench.tsx
··· 1 + import {createSinglePathSVG} from './TEMPLATE' 2 + 3 + export const Wrench_Stroke2_Corner2_Rounded = createSinglePathSVG({ 4 + path: 'M14.5 4a5.5 5.5 0 0 0-5.078 7.616 1 1 0 0 1-.216 1.092L4.37 17.543a1 1 0 0 0 0 1.414l.672.672a1 1 0 0 0 1.414 0l4.835-4.835a1 1 0 0 1 1.092-.216A5.5 5.5 0 0 0 20 9.414l-1.293 1.293a3.828 3.828 0 1 1-5.414-5.414L14.585 4 14.5 4ZM7 9.5a7.5 7.5 0 0 1 9.969-7.084 1 1 0 0 1 .378 1.651l-2.64 2.64a1.829 1.829 0 0 0 2.586 2.586l2.64-2.64a1 1 0 0 1 1.65.378 7.5 7.5 0 0 1-9.328 9.627l-4.384 4.385a3 3 0 0 1-4.242 0l-.672-.672a3 3 0 0 1 0-4.242l4.385-4.385A7.5 7.5 0 0 1 7 9.5Z', 5 + })
+1
src/lib/app-info.web.ts
··· 3 3 export const BUILD_ENV = process.env.EXPO_PUBLIC_ENV 4 4 export const IS_DEV = process.env.EXPO_PUBLIC_ENV === 'development' 5 5 export const IS_TESTFLIGHT = false 6 + export const IS_INTERNAL = IS_DEV 6 7 7 8 // This is the commit hash that the current bundle was made from. The user can see the commit hash in the app's settings 8 9 // along with the other version info. Useful for debugging/reporting.
+5 -1
src/lib/routes/types.ts
··· 11 11 ModerationMutedAccounts: undefined 12 12 ModerationBlockedAccounts: undefined 13 13 Settings: undefined 14 - LanguageSettings: undefined 15 14 Profile: {name: string; hideBackButton?: boolean} 16 15 ProfileFollowers: {name: string} 17 16 ProfileFollows: {name: string} ··· 33 32 TermsOfService: undefined 34 33 CommunityGuidelines: undefined 35 34 CopyrightPolicy: undefined 35 + LanguageSettings: undefined 36 36 AppPasswords: undefined 37 37 SavedFeeds: undefined 38 38 PreferencesFollowingFeed: undefined ··· 40 40 PreferencesExternalEmbeds: undefined 41 41 AccessibilitySettings: undefined 42 42 AppearanceSettings: undefined 43 + AccountSettings: undefined 44 + PrivacyAndSecuritySettings: undefined 45 + ContentAndMediaSettings: undefined 46 + AboutSettings: undefined 43 47 Search: {q?: string} 44 48 Hashtag: {tag: string; author?: string} 45 49 MessagesConversation: {conversation: string; embed?: string}
+15 -2
src/routes.ts
··· 1 - import {Router} from 'lib/routes/router' 1 + import {Router} from '#/lib/routes/router' 2 2 3 3 export const router = new Router({ 4 4 Home: '/', ··· 7 7 Notifications: '/notifications', 8 8 NotificationsSettings: '/notifications/settings', 9 9 Settings: '/settings', 10 - LanguageSettings: '/settings/language', 11 10 Lists: '/lists', 11 + // moderation 12 12 Moderation: '/moderation', 13 13 ModerationModlists: '/moderation/modlists', 14 14 ModerationMutedAccounts: '/moderation/muted-accounts', 15 15 ModerationBlockedAccounts: '/moderation/blocked-accounts', 16 + // profiles, threads, lists 16 17 Profile: ['/profile/:name', '/profile/:name/rss'], 17 18 ProfileFollowers: '/profile/:name/followers', 18 19 ProfileFollows: '/profile/:name/follows', ··· 25 26 ProfileFeed: '/profile/:name/feed/:rkey', 26 27 ProfileFeedLikedBy: '/profile/:name/feed/:rkey/liked-by', 27 28 ProfileLabelerLikedBy: '/profile/:name/labeler/liked-by', 29 + // debug 28 30 Debug: '/sys/debug', 29 31 DebugMod: '/sys/debug-mod', 30 32 Log: '/sys/log', 33 + // settings 34 + LanguageSettings: '/settings/language', 31 35 AppPasswords: '/settings/app-passwords', 32 36 PreferencesFollowingFeed: '/settings/following-feed', 33 37 PreferencesThreads: '/settings/threads', ··· 35 39 AccessibilitySettings: '/settings/accessibility', 36 40 AppearanceSettings: '/settings/appearance', 37 41 SavedFeeds: '/settings/saved-feeds', 42 + // new settings 43 + AccountSettings: '/settings/account', 44 + PrivacyAndSecuritySettings: '/settings/privacy-and-security', 45 + ContentAndMediaSettings: '/settings/content-and-media', 46 + AboutSettings: '/settings/about', 47 + // support 38 48 Support: '/support', 39 49 PrivacyPolicy: '/support/privacy', 40 50 TermsOfService: '/support/tos', 41 51 CommunityGuidelines: '/support/community-guidelines', 42 52 CopyrightPolicy: '/support/copyright', 53 + // hashtags 43 54 Hashtag: '/hashtag/:tag', 55 + // DMs 44 56 Messages: '/messages', 45 57 MessagesSettings: '/messages/settings', 46 58 MessagesConversation: '/messages/:conversation', 59 + // starter packs 47 60 Start: '/start/:name/:rkey', 48 61 StarterPackEdit: '/starter-pack/edit/:rkey', 49 62 StarterPack: '/starter-pack/:name/:rkey',
+16 -11
src/screens/Moderation/index.tsx
··· 7 7 import {useLingui} from '@lingui/react' 8 8 import {useFocusEffect} from '@react-navigation/native' 9 9 10 + import {IS_INTERNAL} from '#/lib/app-info' 10 11 import {getLabelingServiceTitle} from '#/lib/moderation' 11 12 import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 12 13 import {logger} from '#/logger' ··· 469 470 </View> 470 471 )} 471 472 472 - <Text 473 - style={[ 474 - a.text_md, 475 - a.font_bold, 476 - a.pt_2xl, 477 - a.pb_md, 478 - t.atoms.text_contrast_high, 479 - ]}> 480 - <Trans>Logged-out visibility</Trans> 481 - </Text> 473 + {!IS_INTERNAL && ( 474 + <> 475 + <Text 476 + style={[ 477 + a.text_md, 478 + a.font_bold, 479 + a.pt_2xl, 480 + a.pb_md, 481 + t.atoms.text_contrast_high, 482 + ]}> 483 + <Trans>Logged-out visibility</Trans> 484 + </Text> 482 485 483 - <PwiOptOut /> 486 + <PwiOptOut /> 487 + </> 488 + )} 484 489 485 490 <View style={{height: 200}} /> 486 491 </ScrollView>
+78
src/screens/Settings/AboutSettings.tsx
··· 1 + import React from 'react' 2 + import {Platform} from 'react-native' 3 + import {setStringAsync} from 'expo-clipboard' 4 + import {msg, Trans} from '@lingui/macro' 5 + import {useLingui} from '@lingui/react' 6 + import {NativeStackScreenProps} from '@react-navigation/native-stack' 7 + 8 + import {appVersion, BUNDLE_DATE, bundleInfo} from '#/lib/app-info' 9 + import {STATUS_PAGE_URL} from '#/lib/constants' 10 + import {CommonNavigatorParams} from '#/lib/routes/types' 11 + import * as Toast from '#/view/com/util/Toast' 12 + import * as SettingsList from '#/screens/Settings/components/SettingsList' 13 + import {CodeLines_Stroke2_Corner2_Rounded as CodeLinesIcon} from '#/components/icons/CodeLines' 14 + import {Globe_Stroke2_Corner0_Rounded as GlobeIcon} from '#/components/icons/Globe' 15 + import {Newspaper_Stroke2_Corner2_Rounded as NewspaperIcon} from '#/components/icons/Newspaper' 16 + import {Wrench_Stroke2_Corner2_Rounded as WrenchIcon} from '#/components/icons/Wrench' 17 + import * as Layout from '#/components/Layout' 18 + 19 + type Props = NativeStackScreenProps<CommonNavigatorParams, 'AboutSettings'> 20 + export function AboutSettingsScreen({}: Props) { 21 + const {_} = useLingui() 22 + 23 + return ( 24 + <Layout.Screen> 25 + <Layout.Header title={_(msg`About`)} /> 26 + <Layout.Content> 27 + <SettingsList.Container> 28 + <SettingsList.LinkItem 29 + to="https://bsky.social/about/support/tos" 30 + label={_(msg`Terms of Service`)}> 31 + <SettingsList.ItemIcon icon={NewspaperIcon} /> 32 + <SettingsList.ItemText> 33 + <Trans>Terms of Service</Trans> 34 + </SettingsList.ItemText> 35 + </SettingsList.LinkItem> 36 + <SettingsList.LinkItem 37 + to="https://bsky.social/about/support/privacy-policy" 38 + label={_(msg`Privacy Policy`)}> 39 + <SettingsList.ItemIcon icon={NewspaperIcon} /> 40 + <SettingsList.ItemText> 41 + <Trans>Privacy Policy</Trans> 42 + </SettingsList.ItemText> 43 + </SettingsList.LinkItem> 44 + <SettingsList.LinkItem 45 + to={STATUS_PAGE_URL} 46 + label={_(msg`Status Page`)}> 47 + <SettingsList.ItemIcon icon={GlobeIcon} /> 48 + <SettingsList.ItemText> 49 + <Trans>Status Page</Trans> 50 + </SettingsList.ItemText> 51 + </SettingsList.LinkItem> 52 + <SettingsList.Divider /> 53 + <SettingsList.LinkItem to="/sys/log" label={_(msg`System log`)}> 54 + <SettingsList.ItemIcon icon={CodeLinesIcon} /> 55 + <SettingsList.ItemText> 56 + <Trans>System log</Trans> 57 + </SettingsList.ItemText> 58 + </SettingsList.LinkItem> 59 + <SettingsList.PressableItem 60 + label={_(msg`Version ${appVersion}`)} 61 + accessibilityHint={_(msg`Copy build version to clipboard`)} 62 + onPress={() => { 63 + setStringAsync( 64 + `Build version: ${appVersion}; Bundle info: ${bundleInfo}; Bundle date: ${BUNDLE_DATE}; Platform: ${Platform.OS}; Platform version: ${Platform.Version}`, 65 + ) 66 + Toast.show(_(msg`Copied build version to clipboard`)) 67 + }}> 68 + <SettingsList.ItemIcon icon={WrenchIcon} /> 69 + <SettingsList.ItemText> 70 + <Trans>Version {appVersion}</Trans> 71 + </SettingsList.ItemText> 72 + <SettingsList.BadgeText>{bundleInfo}</SettingsList.BadgeText> 73 + </SettingsList.PressableItem> 74 + </SettingsList.Container> 75 + </Layout.Content> 76 + </Layout.Screen> 77 + ) 78 + }
+113
src/screens/Settings/AccessibilitySettings.tsx
··· 1 + import React from 'react' 2 + import {msg, Trans} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 4 + import {NativeStackScreenProps} from '@react-navigation/native-stack' 5 + 6 + import {CommonNavigatorParams} from '#/lib/routes/types' 7 + import {isNative} from '#/platform/detection' 8 + import { 9 + useHapticsDisabled, 10 + useRequireAltTextEnabled, 11 + useSetHapticsDisabled, 12 + useSetRequireAltTextEnabled, 13 + } from '#/state/preferences' 14 + import { 15 + useLargeAltBadgeEnabled, 16 + useSetLargeAltBadgeEnabled, 17 + } from '#/state/preferences/large-alt-badge' 18 + import * as SettingsList from '#/screens/Settings/components/SettingsList' 19 + import {atoms as a} from '#/alf' 20 + import {Admonition} from '#/components/Admonition' 21 + import * as Toggle from '#/components/forms/Toggle' 22 + import {Accessibility_Stroke2_Corner2_Rounded as AccessibilityIcon} from '#/components/icons/Accessibility' 23 + import {Haptic_Stroke2_Corner2_Rounded as HapticIcon} from '#/components/icons/Haptic' 24 + import * as Layout from '#/components/Layout' 25 + import {InlineLinkText} from '#/components/Link' 26 + 27 + type Props = NativeStackScreenProps< 28 + CommonNavigatorParams, 29 + 'AccessibilitySettings' 30 + > 31 + export function AccessibilitySettingsScreen({}: Props) { 32 + const {_} = useLingui() 33 + 34 + const requireAltTextEnabled = useRequireAltTextEnabled() 35 + const setRequireAltTextEnabled = useSetRequireAltTextEnabled() 36 + const hapticsDisabled = useHapticsDisabled() 37 + const setHapticsDisabled = useSetHapticsDisabled() 38 + const largeAltBadgeEnabled = useLargeAltBadgeEnabled() 39 + const setLargeAltBadgeEnabled = useSetLargeAltBadgeEnabled() 40 + 41 + return ( 42 + <Layout.Screen> 43 + <Layout.Header title={_(msg`Accessibility`)} /> 44 + <Layout.Content> 45 + <SettingsList.Container> 46 + <SettingsList.Group contentContainerStyle={[a.gap_sm]}> 47 + <SettingsList.ItemIcon icon={AccessibilityIcon} /> 48 + <SettingsList.ItemText> 49 + <Trans>Alt text</Trans> 50 + </SettingsList.ItemText> 51 + <Toggle.Item 52 + name="require_alt_text" 53 + label={_(msg`Require alt text before posting`)} 54 + value={requireAltTextEnabled ?? false} 55 + onChange={value => setRequireAltTextEnabled(value)} 56 + style={[a.w_full]}> 57 + <Toggle.LabelText style={[a.flex_1]}> 58 + <Trans>Require alt text before posting</Trans> 59 + </Toggle.LabelText> 60 + <Toggle.Platform /> 61 + </Toggle.Item> 62 + <Toggle.Item 63 + name="large_alt_badge" 64 + label={_(msg`Display larger alt text badges`)} 65 + value={!!largeAltBadgeEnabled} 66 + onChange={value => setLargeAltBadgeEnabled(value)} 67 + style={[a.w_full]}> 68 + <Toggle.LabelText style={[a.flex_1]}> 69 + <Trans>Display larger alt text badges</Trans> 70 + </Toggle.LabelText> 71 + <Toggle.Platform /> 72 + </Toggle.Item> 73 + </SettingsList.Group> 74 + {isNative && ( 75 + <> 76 + <SettingsList.Divider /> 77 + <SettingsList.Group contentContainerStyle={[a.gap_sm]}> 78 + <SettingsList.ItemIcon icon={HapticIcon} /> 79 + <SettingsList.ItemText> 80 + <Trans>Haptics</Trans> 81 + </SettingsList.ItemText> 82 + <Toggle.Item 83 + name="haptics" 84 + label={_(msg`Disable haptic feedback`)} 85 + value={hapticsDisabled ?? false} 86 + onChange={value => setHapticsDisabled(value)} 87 + style={[a.w_full]}> 88 + <Toggle.LabelText style={[a.flex_1]}> 89 + <Trans>Disable haptic feedback</Trans> 90 + </Toggle.LabelText> 91 + <Toggle.Platform /> 92 + </Toggle.Item> 93 + </SettingsList.Group> 94 + </> 95 + )} 96 + <SettingsList.Item> 97 + <Admonition type="info" style={[a.flex_1]}> 98 + <Trans> 99 + Autoplay options have moved to the{' '} 100 + <InlineLinkText 101 + to="/settings/content-and-media" 102 + label={_(msg`Content and media`)}> 103 + Content and Media settings 104 + </InlineLinkText> 105 + . 106 + </Trans> 107 + </Admonition> 108 + </SettingsList.Item> 109 + </SettingsList.Container> 110 + </Layout.Content> 111 + </Layout.Screen> 112 + ) 113 + }
+180
src/screens/Settings/AccountSettings.tsx
··· 1 + import React from 'react' 2 + import {msg, Trans} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 4 + import {NativeStackScreenProps} from '@react-navigation/native-stack' 5 + import {useQueryClient} from '@tanstack/react-query' 6 + 7 + import {CommonNavigatorParams} from '#/lib/routes/types' 8 + import {useModalControls} from '#/state/modals' 9 + import {RQKEY as RQKEY_PROFILE} from '#/state/queries/profile' 10 + import {useProfileQuery} from '#/state/queries/profile' 11 + import {useSession} from '#/state/session' 12 + import {ExportCarDialog} from '#/view/screens/Settings/ExportCarDialog' 13 + import * as SettingsList from '#/screens/Settings/components/SettingsList' 14 + import {atoms as a, useTheme} from '#/alf' 15 + import {useDialogControl} from '#/components/Dialog' 16 + import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings' 17 + import {At_Stroke2_Corner2_Rounded as AtIcon} from '#/components/icons/At' 18 + import {BirthdayCake_Stroke2_Corner2_Rounded as BirthdayCakeIcon} from '#/components/icons/BirthdayCake' 19 + import {Car_Stroke2_Corner2_Rounded as CarIcon} from '#/components/icons/Car' 20 + import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' 21 + import {Envelope_Stroke2_Corner2_Rounded as EnvelopeIcon} from '#/components/icons/Envelope' 22 + import {Freeze_Stroke2_Corner2_Rounded as FreezeIcon} from '#/components/icons/Freeze' 23 + import {Lock_Stroke2_Corner2_Rounded as LockIcon} from '#/components/icons/Lock' 24 + import {PencilLine_Stroke2_Corner2_Rounded as PencilIcon} from '#/components/icons/Pencil' 25 + import {Trash_Stroke2_Corner2_Rounded} from '#/components/icons/Trash' 26 + import {Verified_Stroke2_Corner2_Rounded as VerifiedIcon} from '#/components/icons/Verified' 27 + import * as Layout from '#/components/Layout' 28 + import {DeactivateAccountDialog} from './components/DeactivateAccountDialog' 29 + 30 + type Props = NativeStackScreenProps<CommonNavigatorParams, 'AccountSettings'> 31 + export function AccountSettingsScreen({}: Props) { 32 + const t = useTheme() 33 + const {_} = useLingui() 34 + const {currentAccount} = useSession() 35 + const queryClient = useQueryClient() 36 + const {data: profile} = useProfileQuery({did: currentAccount?.did}) 37 + const {openModal} = useModalControls() 38 + const birthdayControl = useDialogControl() 39 + const exportCarControl = useDialogControl() 40 + const deactivateAccountControl = useDialogControl() 41 + 42 + return ( 43 + <Layout.Screen> 44 + <Layout.Header title={_(msg`Account`)} /> 45 + <Layout.Content> 46 + <SettingsList.Container> 47 + <SettingsList.Item> 48 + <SettingsList.ItemIcon icon={EnvelopeIcon} /> 49 + <SettingsList.ItemText> 50 + <Trans>Email</Trans> 51 + </SettingsList.ItemText> 52 + {currentAccount && ( 53 + <> 54 + <SettingsList.BadgeText> 55 + {currentAccount.email || <Trans>(no email)</Trans>} 56 + </SettingsList.BadgeText> 57 + {currentAccount.emailConfirmed ? ( 58 + <CheckIcon color={t.palette.positive_500} size="sm" /> 59 + ) : ( 60 + <SettingsList.BadgeButton 61 + label={_(msg`Verify`)} 62 + onPress={() => {}} 63 + /> 64 + )} 65 + </> 66 + )} 67 + </SettingsList.Item> 68 + <SettingsList.PressableItem 69 + label={_(msg`Change email`)} 70 + onPress={() => openModal({name: 'change-email'})}> 71 + <SettingsList.ItemIcon icon={PencilIcon} /> 72 + <SettingsList.ItemText> 73 + <Trans>Change email</Trans> 74 + </SettingsList.ItemText> 75 + <SettingsList.Chevron /> 76 + </SettingsList.PressableItem> 77 + <SettingsList.LinkItem 78 + to="/settings/privacy-and-security" 79 + label={_(msg`Protect your account`)} 80 + style={[ 81 + a.my_xs, 82 + a.mx_lg, 83 + a.rounded_md, 84 + {backgroundColor: t.palette.primary_50}, 85 + ]} 86 + chevronColor={t.palette.primary_500} 87 + hoverStyle={[{backgroundColor: t.palette.primary_100}]} 88 + contentContainerStyle={[a.rounded_md, a.px_lg]}> 89 + <SettingsList.ItemIcon 90 + icon={VerifiedIcon} 91 + color={t.palette.primary_500} 92 + /> 93 + <SettingsList.ItemText 94 + style={[{color: t.palette.primary_500}, a.font_bold]}> 95 + <Trans>Protect your account</Trans> 96 + </SettingsList.ItemText> 97 + </SettingsList.LinkItem> 98 + <SettingsList.Divider /> 99 + <SettingsList.Item> 100 + <SettingsList.ItemIcon icon={BirthdayCakeIcon} /> 101 + <SettingsList.ItemText> 102 + <Trans>Birthday</Trans> 103 + </SettingsList.ItemText> 104 + <SettingsList.BadgeButton 105 + label={_(msg`Edit`)} 106 + onPress={() => birthdayControl.open()} 107 + /> 108 + </SettingsList.Item> 109 + <SettingsList.PressableItem 110 + label={_(msg`Password`)} 111 + onPress={() => openModal({name: 'change-password'})}> 112 + <SettingsList.ItemIcon icon={LockIcon} /> 113 + <SettingsList.ItemText> 114 + <Trans>Password</Trans> 115 + </SettingsList.ItemText> 116 + <SettingsList.Chevron /> 117 + </SettingsList.PressableItem> 118 + <SettingsList.PressableItem 119 + label={_(msg`Handle`)} 120 + onPress={() => 121 + openModal({ 122 + name: 'change-handle', 123 + onChanged() { 124 + if (currentAccount) { 125 + // refresh my profile 126 + queryClient.invalidateQueries({ 127 + queryKey: RQKEY_PROFILE(currentAccount.did), 128 + }) 129 + } 130 + }, 131 + }) 132 + }> 133 + <SettingsList.ItemIcon icon={AtIcon} /> 134 + <SettingsList.ItemText> 135 + <Trans>Handle</Trans> 136 + </SettingsList.ItemText> 137 + {profile && ( 138 + <SettingsList.BadgeText>@{profile.handle}</SettingsList.BadgeText> 139 + )} 140 + <SettingsList.Chevron /> 141 + </SettingsList.PressableItem> 142 + <SettingsList.Divider /> 143 + <SettingsList.PressableItem 144 + label={_(msg`Export my data`)} 145 + onPress={() => exportCarControl.open()}> 146 + <SettingsList.ItemIcon icon={CarIcon} /> 147 + <SettingsList.ItemText> 148 + <Trans>Export my data</Trans> 149 + </SettingsList.ItemText> 150 + <SettingsList.Chevron /> 151 + </SettingsList.PressableItem> 152 + <SettingsList.PressableItem 153 + label={_(msg`Deactivate account`)} 154 + onPress={() => deactivateAccountControl.open()} 155 + destructive> 156 + <SettingsList.ItemIcon icon={FreezeIcon} /> 157 + <SettingsList.ItemText> 158 + <Trans>Deactivate account</Trans> 159 + </SettingsList.ItemText> 160 + <SettingsList.Chevron /> 161 + </SettingsList.PressableItem> 162 + <SettingsList.PressableItem 163 + label={_(msg`Delete account`)} 164 + onPress={() => openModal({name: 'delete-account'})} 165 + destructive> 166 + <SettingsList.ItemIcon icon={Trash_Stroke2_Corner2_Rounded} /> 167 + <SettingsList.ItemText> 168 + <Trans>Delete account</Trans> 169 + </SettingsList.ItemText> 170 + <SettingsList.Chevron /> 171 + </SettingsList.PressableItem> 172 + </SettingsList.Container> 173 + </Layout.Content> 174 + 175 + <BirthDateSettingsDialog control={birthdayControl} /> 176 + <ExportCarDialog control={exportCarControl} /> 177 + <DeactivateAccountDialog control={deactivateAccountControl} /> 178 + </Layout.Screen> 179 + ) 180 + }
+76 -89
src/screens/Settings/AppearanceSettings.tsx
··· 1 1 import React, {useCallback} from 'react' 2 - import {View} from 'react-native' 3 2 import Animated, { 4 - FadeInDown, 5 - FadeOutDown, 3 + FadeInUp, 4 + FadeOutUp, 6 5 LayoutAnimationConfig, 6 + LinearTransition, 7 7 } from 'react-native-reanimated' 8 - import {msg, Trans} from '@lingui/macro' 8 + import {msg} from '@lingui/macro' 9 9 import {useLingui} from '@lingui/react' 10 10 11 - import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 12 11 import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 13 12 import {useSetThemePrefs, useThemePrefs} from '#/state/shell' 14 - import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader' 15 - import {ScrollView} from '#/view/com/util/Views' 16 13 import {atoms as a, native, useAlf, useTheme} from '#/alf' 17 14 import * as ToggleButton from '#/components/forms/ToggleButton' 18 15 import {Props as SVGIconProps} from '#/components/icons/common' ··· 22 19 import {TitleCase_Stroke2_Corner0_Rounded as Aa} from '#/components/icons/TitleCase' 23 20 import * as Layout from '#/components/Layout' 24 21 import {Text} from '#/components/Typography' 22 + import * as SettingsList from './components/SettingsList' 25 23 26 24 type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppearanceSettings'> 27 25 export function AppearanceSettingsScreen({}: Props) { 28 - const t = useTheme() 29 26 const {_} = useLingui() 30 - const {isTabletOrMobile} = useWebMediaQueries() 31 27 const {fonts} = useAlf() 32 28 33 29 const {colorMode, darkTheme} = useThemePrefs() ··· 77 73 return ( 78 74 <LayoutAnimationConfig skipExiting skipEntering> 79 75 <Layout.Screen testID="preferencesThreadsScreen"> 80 - <ScrollView 81 - // @ts-ignore web only -prf 82 - dataSet={{'stable-gutters': 1}} 83 - contentContainerStyle={{paddingBottom: 75}}> 84 - <SimpleViewHeader 85 - showBackButton={isTabletOrMobile} 86 - style={[t.atoms.border_contrast_medium, a.border_b]}> 87 - <View style={a.flex_1}> 88 - <Text style={[a.text_2xl, a.font_bold]}> 89 - <Trans>Appearance</Trans> 90 - </Text> 91 - </View> 92 - </SimpleViewHeader> 76 + <Layout.Header title={_(msg`Appearance`)} /> 77 + <Layout.Content> 78 + <SettingsList.Container> 79 + <AppearanceToggleButtonGroup 80 + title={_(msg`Color mode`)} 81 + icon={PhoneIcon} 82 + items={[ 83 + { 84 + label: _(msg`System`), 85 + name: 'system', 86 + }, 87 + { 88 + label: _(msg`Light`), 89 + name: 'light', 90 + }, 91 + { 92 + label: _(msg`Dark`), 93 + name: 'dark', 94 + }, 95 + ]} 96 + values={[colorMode]} 97 + onChange={onChangeAppearance} 98 + /> 93 99 94 - <View style={[a.gap_3xl, a.pt_xl, a.px_xl]}> 95 - <View style={[a.gap_lg]}> 96 - <AppearanceToggleButtonGroup 97 - title={_(msg`Color mode`)} 98 - icon={PhoneIcon} 99 - items={[ 100 - { 101 - label: _(msg`System`), 102 - name: 'system', 103 - }, 104 - { 105 - label: _(msg`Light`), 106 - name: 'light', 107 - }, 108 - { 109 - label: _(msg`Dark`), 110 - name: 'dark', 111 - }, 112 - ]} 113 - values={[colorMode]} 114 - onChange={onChangeAppearance} 115 - /> 100 + {colorMode !== 'light' && ( 101 + <Animated.View 102 + entering={native(FadeInUp)} 103 + exiting={native(FadeOutUp)}> 104 + <AppearanceToggleButtonGroup 105 + title={_(msg`Dark theme`)} 106 + icon={MoonIcon} 107 + items={[ 108 + { 109 + label: _(msg`Dim`), 110 + name: 'dim', 111 + }, 112 + { 113 + label: _(msg`Dark`), 114 + name: 'dark', 115 + }, 116 + ]} 117 + values={[darkTheme ?? 'dim']} 118 + onChange={onChangeDarkTheme} 119 + /> 120 + </Animated.View> 121 + )} 116 122 117 - {colorMode !== 'light' && ( 118 - <Animated.View 119 - entering={native(FadeInDown)} 120 - exiting={native(FadeOutDown)}> 121 - <AppearanceToggleButtonGroup 122 - title={_(msg`Dark theme`)} 123 - icon={MoonIcon} 124 - items={[ 125 - { 126 - label: _(msg`Dim`), 127 - name: 'dim', 128 - }, 129 - { 130 - label: _(msg`Dark`), 131 - name: 'dark', 132 - }, 133 - ]} 134 - values={[darkTheme ?? 'dim']} 135 - onChange={onChangeDarkTheme} 136 - /> 137 - </Animated.View> 138 - )} 139 - 123 + <Animated.View layout={native(LinearTransition)}> 140 124 <AppearanceToggleButtonGroup 141 125 title={_(msg`Font`)} 142 126 description={_( ··· 177 161 values={[fonts.scale]} 178 162 onChange={onChangeFontScale} 179 163 /> 180 - </View> 181 - </View> 182 - </ScrollView> 164 + </Animated.View> 165 + </SettingsList.Container> 166 + </Layout.Content> 183 167 </Layout.Screen> 184 168 </LayoutAnimationConfig> 185 169 ) ··· 205 189 }) { 206 190 const t = useTheme() 207 191 return ( 208 - <View style={[a.gap_sm]}> 209 - <View style={[a.gap_xs]}> 210 - <View style={[a.flex_row, a.align_center, a.gap_md]}> 211 - <Icon style={t.atoms.text} /> 212 - <Text style={[a.text_md, a.font_bold]}>{title}</Text> 213 - </View> 192 + <> 193 + <SettingsList.Group contentContainerStyle={[a.gap_sm]} iconInset={false}> 194 + <SettingsList.ItemIcon icon={Icon} /> 195 + <SettingsList.ItemText>{title}</SettingsList.ItemText> 214 196 {description && ( 215 197 <Text 216 - style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}> 198 + style={[ 199 + a.text_sm, 200 + a.leading_snug, 201 + t.atoms.text_contrast_medium, 202 + a.w_full, 203 + ]}> 217 204 {description} 218 205 </Text> 219 206 )} 220 - </View> 221 - <ToggleButton.Group label={title} values={values} onChange={onChange}> 222 - {items.map(item => ( 223 - <ToggleButton.Button 224 - key={item.name} 225 - label={item.label} 226 - name={item.name}> 227 - <ToggleButton.ButtonText>{item.label}</ToggleButton.ButtonText> 228 - </ToggleButton.Button> 229 - ))} 230 - </ToggleButton.Group> 231 - </View> 207 + <ToggleButton.Group label={title} values={values} onChange={onChange}> 208 + {items.map(item => ( 209 + <ToggleButton.Button 210 + key={item.name} 211 + label={item.label} 212 + name={item.name}> 213 + <ToggleButton.ButtonText>{item.label}</ToggleButton.ButtonText> 214 + </ToggleButton.Button> 215 + ))} 216 + </ToggleButton.Group> 217 + </SettingsList.Group> 218 + </> 232 219 ) 233 220 }
+104
src/screens/Settings/ContentAndMediaSettings.tsx
··· 1 + import React from 'react' 2 + import {msg, Trans} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 4 + import {NativeStackScreenProps} from '@react-navigation/native-stack' 5 + 6 + import {CommonNavigatorParams} from '#/lib/routes/types' 7 + import {isNative} from '#/platform/detection' 8 + import {useAutoplayDisabled, useSetAutoplayDisabled} from '#/state/preferences' 9 + import { 10 + useInAppBrowser, 11 + useSetInAppBrowser, 12 + } from '#/state/preferences/in-app-browser' 13 + import * as SettingsList from '#/screens/Settings/components/SettingsList' 14 + import * as Toggle from '#/components/forms/Toggle' 15 + import {Bubbles_Stroke2_Corner2_Rounded as BubblesIcon} from '#/components/icons/Bubble' 16 + import {Hashtag_Stroke2_Corner0_Rounded as HashtagIcon} from '#/components/icons/Hashtag' 17 + import {Home_Stroke2_Corner2_Rounded as HomeIcon} from '#/components/icons/Home' 18 + import {Macintosh_Stroke2_Corner2_Rounded as MacintoshIcon} from '#/components/icons/Macintosh' 19 + import {Play_Stroke2_Corner2_Rounded as PlayIcon} from '#/components/icons/Play' 20 + import {Window_Stroke2_Corner2_Rounded as WindowIcon} from '#/components/icons/Window' 21 + import * as Layout from '#/components/Layout' 22 + 23 + type Props = NativeStackScreenProps< 24 + CommonNavigatorParams, 25 + 'ContentAndMediaSettings' 26 + > 27 + export function ContentAndMediaSettingsScreen({}: Props) { 28 + const {_} = useLingui() 29 + const autoplayDisabledPref = useAutoplayDisabled() 30 + const setAutoplayDisabledPref = useSetAutoplayDisabled() 31 + const inAppBrowserPref = useInAppBrowser() 32 + const setUseInAppBrowser = useSetInAppBrowser() 33 + 34 + return ( 35 + <Layout.Screen> 36 + <Layout.Header title={_(msg`Content and Media`)} /> 37 + <Layout.Content> 38 + <SettingsList.Container> 39 + <SettingsList.LinkItem 40 + to="/settings/saved-feeds" 41 + label={_(msg`Manage saved feeds`)}> 42 + <SettingsList.ItemIcon icon={HashtagIcon} /> 43 + <SettingsList.ItemText> 44 + <Trans>Manage saved feeds</Trans> 45 + </SettingsList.ItemText> 46 + </SettingsList.LinkItem> 47 + <SettingsList.LinkItem 48 + to="/settings/threads" 49 + label={_(msg`Thread preferences`)}> 50 + <SettingsList.ItemIcon icon={BubblesIcon} /> 51 + <SettingsList.ItemText> 52 + <Trans>Thread preferences</Trans> 53 + </SettingsList.ItemText> 54 + </SettingsList.LinkItem> 55 + <SettingsList.LinkItem 56 + to="/settings/following-feed" 57 + label={_(msg`Following feed preferences`)}> 58 + <SettingsList.ItemIcon icon={HomeIcon} /> 59 + <SettingsList.ItemText> 60 + <Trans>Following feed preferences</Trans> 61 + </SettingsList.ItemText> 62 + </SettingsList.LinkItem> 63 + <SettingsList.LinkItem 64 + to="/settings/external-embeds" 65 + label={_(msg`External media`)}> 66 + <SettingsList.ItemIcon icon={MacintoshIcon} /> 67 + <SettingsList.ItemText> 68 + <Trans>External media</Trans> 69 + </SettingsList.ItemText> 70 + </SettingsList.LinkItem> 71 + <SettingsList.Divider /> 72 + {isNative && ( 73 + <Toggle.Item 74 + name="use_in_app_browser" 75 + label={_(msg`Use in-app browser to open links`)} 76 + value={inAppBrowserPref ?? false} 77 + onChange={value => setUseInAppBrowser(value)}> 78 + <SettingsList.Item> 79 + <SettingsList.ItemIcon icon={WindowIcon} /> 80 + <SettingsList.ItemText> 81 + <Trans>Use in-app browser to open links</Trans> 82 + </SettingsList.ItemText> 83 + <Toggle.Platform /> 84 + </SettingsList.Item> 85 + </Toggle.Item> 86 + )} 87 + <Toggle.Item 88 + name="disable_autoplay" 89 + label={_(msg`Disable autoplay for videos and GIFs`)} 90 + value={autoplayDisabledPref} 91 + onChange={value => setAutoplayDisabledPref(value)}> 92 + <SettingsList.Item> 93 + <SettingsList.ItemIcon icon={PlayIcon} /> 94 + <SettingsList.ItemText> 95 + <Trans>Disable autoplay for videos and GIFs</Trans> 96 + </SettingsList.ItemText> 97 + <Toggle.Platform /> 98 + </SettingsList.Item> 99 + </Toggle.Item> 100 + </SettingsList.Container> 101 + </Layout.Content> 102 + </Layout.Screen> 103 + ) 104 + }
+91
src/screens/Settings/PrivacyAndSecuritySettings.tsx
··· 1 + import React from 'react' 2 + import {View} from 'react-native' 3 + import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 + import {NativeStackScreenProps} from '@react-navigation/native-stack' 6 + 7 + import {CommonNavigatorParams} from '#/lib/routes/types' 8 + import {useAppPasswordsQuery} from '#/state/queries/app-passwords' 9 + import * as SettingsList from '#/screens/Settings/components/SettingsList' 10 + import {atoms as a} from '#/alf' 11 + import * as Admonition from '#/components/Admonition' 12 + import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlashIcon} from '#/components/icons/EyeSlash' 13 + import {Key_Stroke2_Corner2_Rounded as KeyIcon} from '#/components/icons/Key' 14 + import {Verified_Stroke2_Corner2_Rounded as VerifiedIcon} from '#/components/icons/Verified' 15 + import * as Layout from '#/components/Layout' 16 + import {InlineLinkText} from '#/components/Link' 17 + import {Email2FAToggle} from './components/Email2FAToggle' 18 + import {PwiOptOut} from './components/PwiOptOut' 19 + 20 + type Props = NativeStackScreenProps< 21 + CommonNavigatorParams, 22 + 'PrivacyAndSecuritySettings' 23 + > 24 + export function PrivacyAndSecuritySettingsScreen({}: Props) { 25 + const {_} = useLingui() 26 + const {data: appPasswords} = useAppPasswordsQuery() 27 + return ( 28 + <Layout.Screen> 29 + <Layout.Header title={_(msg`Privacy and Security`)} /> 30 + <Layout.Content> 31 + <SettingsList.Container> 32 + <SettingsList.Item> 33 + <SettingsList.ItemIcon icon={VerifiedIcon} /> 34 + <SettingsList.ItemText> 35 + <Trans>Two-factor authentication (2FA)</Trans> 36 + </SettingsList.ItemText> 37 + <Email2FAToggle /> 38 + </SettingsList.Item> 39 + <SettingsList.LinkItem 40 + to="/settings/app-passwords" 41 + label={_(msg`App passwords`)}> 42 + <SettingsList.ItemIcon icon={KeyIcon} /> 43 + <SettingsList.ItemText> 44 + <Trans>App passwords</Trans> 45 + </SettingsList.ItemText> 46 + {appPasswords && appPasswords.length > 0 && ( 47 + <SettingsList.BadgeText> 48 + {appPasswords.length} 49 + </SettingsList.BadgeText> 50 + )} 51 + </SettingsList.LinkItem> 52 + <SettingsList.Divider /> 53 + <SettingsList.Group> 54 + <SettingsList.ItemIcon icon={EyeSlashIcon} /> 55 + <SettingsList.ItemText> 56 + <Trans>Logged-out visibility</Trans> 57 + </SettingsList.ItemText> 58 + <PwiOptOut /> 59 + </SettingsList.Group> 60 + <SettingsList.Item> 61 + <Admonition.Outer type="tip" style={[a.flex_1]}> 62 + <Admonition.Row> 63 + <Admonition.Icon /> 64 + <View style={[a.flex_1, a.gap_sm]}> 65 + <Admonition.Text> 66 + <Trans> 67 + Note: Bluesky is an open and public network. This setting 68 + only limits the visibility of your content on the Bluesky 69 + app and website, and other apps may not respect this 70 + setting. Your content may still be shown to logged-out 71 + users by other apps and websites. 72 + </Trans> 73 + </Admonition.Text> 74 + <Admonition.Text> 75 + <InlineLinkText 76 + label={_( 77 + msg`Learn more about what is public on Bluesky.`, 78 + )} 79 + to="https://blueskyweb.zendesk.com/hc/en-us/articles/15835264007693-Data-Privacy"> 80 + <Trans>Learn more about what is public on Bluesky.</Trans> 81 + </InlineLinkText> 82 + </Admonition.Text> 83 + </View> 84 + </Admonition.Row> 85 + </Admonition.Outer> 86 + </SettingsList.Item> 87 + </SettingsList.Container> 88 + </Layout.Content> 89 + </Layout.Screen> 90 + ) 91 + }
+282
src/screens/Settings/Settings.tsx
··· 1 + import React from 'react' 2 + import {View} from 'react-native' 3 + import {Linking} from 'react-native' 4 + import {AppBskyActorDefs, moderateProfile} from '@atproto/api' 5 + import {msg, Trans} from '@lingui/macro' 6 + import {useLingui} from '@lingui/react' 7 + import {NativeStackScreenProps} from '@react-navigation/native-stack' 8 + 9 + import {HELP_DESK_URL} from '#/lib/constants' 10 + import {CommonNavigatorParams} from '#/lib/routes/types' 11 + import {useProfileShadow} from '#/state/cache/profile-shadow' 12 + import {useModerationOpts} from '#/state/preferences/moderation-opts' 13 + import {useProfileQuery, useProfilesQuery} from '#/state/queries/profile' 14 + import {useSession, useSessionApi} from '#/state/session' 15 + import {useLoggedOutViewControls} from '#/state/shell/logged-out' 16 + import {useCloseAllActiveElements} from '#/state/util' 17 + import {UserAvatar} from '#/view/com/util/UserAvatar' 18 + import {ProfileHeaderDisplayName} from '#/screens/Profile/Header/DisplayName' 19 + import {ProfileHeaderHandle} from '#/screens/Profile/Header/Handle' 20 + import * as SettingsList from '#/screens/Settings/components/SettingsList' 21 + import {atoms as a, useTheme} from '#/alf' 22 + import {useDialogControl} from '#/components/Dialog' 23 + import {SwitchAccountDialog} from '#/components/dialogs/SwitchAccount' 24 + import {Accessibility_Stroke2_Corner2_Rounded as AccessibilityIcon} from '#/components/icons/Accessibility' 25 + import {BubbleInfo_Stroke2_Corner2_Rounded as BubbleInfoIcon} from '#/components/icons/BubbleInfo' 26 + import {CircleQuestion_Stroke2_Corner2_Rounded as CircleQuestionIcon} from '#/components/icons/CircleQuestion' 27 + import {Earth_Stroke2_Corner2_Rounded as EarthIcon} from '#/components/icons/Globe' 28 + import {Lock_Stroke2_Corner2_Rounded as LockIcon} from '#/components/icons/Lock' 29 + import {PaintRoller_Stroke2_Corner2_Rounded as PaintRollerIcon} from '#/components/icons/PaintRoller' 30 + import { 31 + Person_Stroke2_Corner2_Rounded as PersonIcon, 32 + PersonGroup_Stroke2_Corner2_Rounded as PersonGroupIcon, 33 + } from '#/components/icons/Person' 34 + import {RaisingHand4Finger_Stroke2_Corner2_Rounded as HandIcon} from '#/components/icons/RaisingHand' 35 + import {Window_Stroke2_Corner2_Rounded as WindowIcon} from '#/components/icons/Window' 36 + import * as Layout from '#/components/Layout' 37 + import * as Prompt from '#/components/Prompt' 38 + 39 + type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'> 40 + export function SettingsScreen({}: Props) { 41 + const {_} = useLingui() 42 + const {logoutEveryAccount} = useSessionApi() 43 + const {accounts, currentAccount} = useSession() 44 + const switchAccountControl = useDialogControl() 45 + const signOutPromptControl = Prompt.usePromptControl() 46 + const {data: profile} = useProfileQuery({did: currentAccount?.did}) 47 + const {setShowLoggedOut} = useLoggedOutViewControls() 48 + const closeEverything = useCloseAllActiveElements() 49 + 50 + const onAddAnotherAccount = () => { 51 + setShowLoggedOut(true) 52 + closeEverything() 53 + } 54 + 55 + return ( 56 + <Layout.Screen> 57 + <Layout.Header title={_(msg`Settings`)} /> 58 + <Layout.Content> 59 + <SettingsList.Container> 60 + <View 61 + style={[ 62 + a.px_xl, 63 + a.pb_md, 64 + a.w_full, 65 + a.gap_2xs, 66 + a.align_center, 67 + {minHeight: 160}, 68 + ]}> 69 + {profile && <ProfilePreview profile={profile} />} 70 + </View> 71 + <SettingsList.PressableItem 72 + label={ 73 + accounts.length > 1 74 + ? _(msg`Switch account`) 75 + : _(msg`Add another account`) 76 + } 77 + onPress={() => 78 + accounts.length > 1 79 + ? switchAccountControl.open() 80 + : onAddAnotherAccount() 81 + }> 82 + <SettingsList.ItemIcon icon={PersonGroupIcon} /> 83 + <SettingsList.ItemText> 84 + {accounts.length > 1 ? ( 85 + <Trans>Switch account</Trans> 86 + ) : ( 87 + <Trans>Add another account</Trans> 88 + )} 89 + </SettingsList.ItemText> 90 + {accounts.length > 1 && ( 91 + <AvatarStack 92 + profiles={accounts 93 + .map(acc => acc.did) 94 + .filter(did => did !== currentAccount?.did) 95 + .slice(0, 5)} 96 + /> 97 + )} 98 + </SettingsList.PressableItem> 99 + <SettingsList.Divider /> 100 + <SettingsList.LinkItem to="/settings/account" label={_(msg`Account`)}> 101 + <SettingsList.ItemIcon icon={PersonIcon} /> 102 + <SettingsList.ItemText> 103 + <Trans>Account</Trans> 104 + </SettingsList.ItemText> 105 + </SettingsList.LinkItem> 106 + <SettingsList.LinkItem 107 + to="/settings/privacy-and-security" 108 + label={_(msg`Privacy and security`)}> 109 + <SettingsList.ItemIcon icon={LockIcon} /> 110 + <SettingsList.ItemText> 111 + <Trans>Privacy and security</Trans> 112 + </SettingsList.ItemText> 113 + </SettingsList.LinkItem> 114 + <SettingsList.LinkItem to="/moderation" label={_(msg`Moderation`)}> 115 + <SettingsList.ItemIcon icon={HandIcon} /> 116 + <SettingsList.ItemText> 117 + <Trans>Moderation</Trans> 118 + </SettingsList.ItemText> 119 + </SettingsList.LinkItem> 120 + <SettingsList.LinkItem 121 + to="/settings/content-and-media" 122 + label={_(msg`Content and media`)}> 123 + <SettingsList.ItemIcon icon={WindowIcon} /> 124 + <SettingsList.ItemText> 125 + <Trans>Content and media</Trans> 126 + </SettingsList.ItemText> 127 + </SettingsList.LinkItem> 128 + <SettingsList.LinkItem 129 + to="/settings/appearance" 130 + label={_(msg`Appearance`)}> 131 + <SettingsList.ItemIcon icon={PaintRollerIcon} /> 132 + <SettingsList.ItemText> 133 + <Trans>Appearance</Trans> 134 + </SettingsList.ItemText> 135 + </SettingsList.LinkItem> 136 + <SettingsList.LinkItem 137 + to="/settings/accessibility" 138 + label={_(msg`Accessibility`)}> 139 + <SettingsList.ItemIcon icon={AccessibilityIcon} /> 140 + <SettingsList.ItemText> 141 + <Trans>Accessibility</Trans> 142 + </SettingsList.ItemText> 143 + </SettingsList.LinkItem> 144 + <SettingsList.LinkItem 145 + to="/settings/language" 146 + label={_(msg`Languages`)}> 147 + <SettingsList.ItemIcon icon={EarthIcon} /> 148 + <SettingsList.ItemText> 149 + <Trans>Languages</Trans> 150 + </SettingsList.ItemText> 151 + </SettingsList.LinkItem> 152 + <SettingsList.PressableItem 153 + onPress={() => Linking.openURL(HELP_DESK_URL)} 154 + label={_(msg`Help`)} 155 + accessibilityHint={_(msg`Open helpdesk in browser`)}> 156 + <SettingsList.ItemIcon icon={CircleQuestionIcon} /> 157 + <SettingsList.ItemText> 158 + <Trans>Help</Trans> 159 + </SettingsList.ItemText> 160 + <SettingsList.Chevron /> 161 + </SettingsList.PressableItem> 162 + <SettingsList.LinkItem to="/settings/about" label={_(msg`About`)}> 163 + <SettingsList.ItemIcon icon={BubbleInfoIcon} /> 164 + <SettingsList.ItemText> 165 + <Trans>About</Trans> 166 + </SettingsList.ItemText> 167 + </SettingsList.LinkItem> 168 + <SettingsList.Divider /> 169 + <SettingsList.PressableItem 170 + destructive 171 + onPress={() => signOutPromptControl.open()} 172 + label={_(msg`Sign out`)}> 173 + <SettingsList.ItemText> 174 + <Trans>Sign out</Trans> 175 + </SettingsList.ItemText> 176 + </SettingsList.PressableItem> 177 + </SettingsList.Container> 178 + </Layout.Content> 179 + 180 + <Prompt.Basic 181 + control={signOutPromptControl} 182 + title={_(msg`Sign out?`)} 183 + description={_(msg`You will be signed out of all your accounts.`)} 184 + onConfirm={() => logoutEveryAccount('Settings')} 185 + confirmButtonCta={_(msg`Sign out`)} 186 + cancelButtonCta={_(msg`Cancel`)} 187 + confirmButtonColor="negative" 188 + /> 189 + 190 + <SwitchAccountDialog control={switchAccountControl} /> 191 + </Layout.Screen> 192 + ) 193 + } 194 + 195 + function ProfilePreview({ 196 + profile, 197 + }: { 198 + profile: AppBskyActorDefs.ProfileViewDetailed 199 + }) { 200 + const shadow = useProfileShadow(profile) 201 + const moderationOpts = useModerationOpts() 202 + 203 + if (!moderationOpts) return null 204 + 205 + const moderation = moderateProfile(profile, moderationOpts) 206 + 207 + return ( 208 + <> 209 + <UserAvatar 210 + size={80} 211 + avatar={shadow.avatar} 212 + moderation={moderation.ui('avatar')} 213 + /> 214 + <ProfileHeaderDisplayName profile={shadow} moderation={moderation} /> 215 + <ProfileHeaderHandle profile={shadow} /> 216 + </> 217 + ) 218 + } 219 + 220 + const AVI_SIZE = 26 221 + const HALF_AVI_SIZE = AVI_SIZE / 2 222 + 223 + function AvatarStack({profiles}: {profiles: string[]}) { 224 + const {data, error} = useProfilesQuery({handles: profiles}) 225 + const t = useTheme() 226 + const moderationOpts = useModerationOpts() 227 + 228 + if (error) { 229 + console.error(error) 230 + return null 231 + } 232 + 233 + const isPending = !data || !moderationOpts 234 + 235 + const items = isPending 236 + ? Array.from({length: profiles.length}).map((_, i) => ({ 237 + key: i, 238 + profile: null, 239 + moderation: null, 240 + })) 241 + : data.profiles.map(item => ({ 242 + key: item.did, 243 + profile: item, 244 + moderation: moderateProfile(item, moderationOpts), 245 + })) 246 + 247 + return ( 248 + <View 249 + style={[ 250 + a.flex_row, 251 + a.align_center, 252 + a.relative, 253 + {width: AVI_SIZE + (items.length - 1) * HALF_AVI_SIZE}, 254 + ]}> 255 + {items.map((item, i) => ( 256 + <View 257 + key={item.key} 258 + style={[ 259 + t.atoms.bg_contrast_25, 260 + a.relative, 261 + { 262 + width: AVI_SIZE, 263 + height: AVI_SIZE, 264 + left: i * -HALF_AVI_SIZE, 265 + borderWidth: 1, 266 + borderColor: t.atoms.bg.backgroundColor, 267 + borderRadius: 999, 268 + zIndex: 3 - i, 269 + }, 270 + ]}> 271 + {item.profile && ( 272 + <UserAvatar 273 + size={AVI_SIZE - 2} 274 + avatar={item.profile.avatar} 275 + moderation={item.moderation.ui('avatar')} 276 + /> 277 + )} 278 + </View> 279 + ))} 280 + </View> 281 + ) 282 + }
+66
src/screens/Settings/components/Email2FAToggle.tsx
··· 1 + import React from 'react' 2 + import {msg} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 4 + 5 + import {useModalControls} from '#/state/modals' 6 + import {useAgent, useSession} from '#/state/session' 7 + import {DisableEmail2FADialog} from '#/view/screens/Settings/DisableEmail2FADialog' 8 + import {useDialogControl} from '#/components/Dialog' 9 + import * as Prompt from '#/components/Prompt' 10 + import * as SettingsList from './SettingsList' 11 + 12 + export function Email2FAToggle() { 13 + const {_} = useLingui() 14 + const {currentAccount} = useSession() 15 + const {openModal} = useModalControls() 16 + const disableDialogControl = useDialogControl() 17 + const enableDialogControl = useDialogControl() 18 + const agent = useAgent() 19 + 20 + const enableEmailAuthFactor = React.useCallback(async () => { 21 + if (currentAccount?.email) { 22 + await agent.com.atproto.server.updateEmail({ 23 + email: currentAccount.email, 24 + emailAuthFactor: true, 25 + }) 26 + await agent.resumeSession(agent.session!) 27 + } 28 + }, [currentAccount, agent]) 29 + 30 + const onToggle = React.useCallback(() => { 31 + if (!currentAccount) { 32 + return 33 + } 34 + if (currentAccount.emailAuthFactor) { 35 + disableDialogControl.open() 36 + } else { 37 + if (!currentAccount.emailConfirmed) { 38 + openModal({ 39 + name: 'verify-email', 40 + onSuccess: enableDialogControl.open, 41 + }) 42 + return 43 + } 44 + enableDialogControl.open() 45 + } 46 + }, [currentAccount, enableDialogControl, openModal, disableDialogControl]) 47 + 48 + return ( 49 + <> 50 + <DisableEmail2FADialog control={disableDialogControl} /> 51 + <Prompt.Basic 52 + control={enableDialogControl} 53 + title={_(msg`Enable Email 2FA`)} 54 + description={_(msg`Require an email code to log in to your account.`)} 55 + onConfirm={enableEmailAuthFactor} 56 + confirmButtonCta={_(msg`Enable`)} 57 + /> 58 + <SettingsList.BadgeButton 59 + label={ 60 + currentAccount?.emailAuthFactor ? _(msg`Disable`) : _(msg`Enable`) 61 + } 62 + onPress={onToggle} 63 + /> 64 + </> 65 + ) 66 + }
+100
src/screens/Settings/components/PwiOptOut.tsx
··· 1 + import React from 'react' 2 + import {View} from 'react-native' 3 + import {ComAtprotoLabelDefs} from '@atproto/api' 4 + import {msg, Trans} from '@lingui/macro' 5 + import {useLingui} from '@lingui/react' 6 + 7 + import { 8 + useProfileQuery, 9 + useProfileUpdateMutation, 10 + } from '#/state/queries/profile' 11 + import {useSession} from '#/state/session' 12 + import {atoms as a, useTheme} from '#/alf' 13 + import * as Toggle from '#/components/forms/Toggle' 14 + import {Text} from '#/components/Typography' 15 + 16 + export function PwiOptOut() { 17 + const t = useTheme() 18 + const {_} = useLingui() 19 + const {currentAccount} = useSession() 20 + const {data: profile} = useProfileQuery({did: currentAccount?.did}) 21 + const updateProfile = useProfileUpdateMutation() 22 + 23 + const isOptedOut = 24 + profile?.labels?.some(l => l.val === '!no-unauthenticated') || false 25 + const canToggle = profile && !updateProfile.isPending 26 + 27 + const onToggleOptOut = React.useCallback(() => { 28 + if (!profile) { 29 + return 30 + } 31 + let wasAdded = false 32 + updateProfile.mutate({ 33 + profile, 34 + updates: existing => { 35 + // create labels attr if needed 36 + existing.labels = ComAtprotoLabelDefs.isSelfLabels(existing.labels) 37 + ? existing.labels 38 + : { 39 + $type: 'com.atproto.label.defs#selfLabels', 40 + values: [], 41 + } 42 + 43 + // toggle the label 44 + const hasLabel = existing.labels.values.some( 45 + l => l.val === '!no-unauthenticated', 46 + ) 47 + if (hasLabel) { 48 + wasAdded = false 49 + existing.labels.values = existing.labels.values.filter( 50 + l => l.val !== '!no-unauthenticated', 51 + ) 52 + } else { 53 + wasAdded = true 54 + existing.labels.values.push({val: '!no-unauthenticated'}) 55 + } 56 + 57 + // delete if no longer needed 58 + if (existing.labels.values.length === 0) { 59 + delete existing.labels 60 + } 61 + return existing 62 + }, 63 + checkCommitted: res => { 64 + const exists = !!res.data.labels?.some( 65 + l => l.val === '!no-unauthenticated', 66 + ) 67 + return exists === wasAdded 68 + }, 69 + }) 70 + }, [updateProfile, profile]) 71 + 72 + return ( 73 + <View style={[a.flex_1, a.gap_sm]}> 74 + <Toggle.Item 75 + name="logged_out_visibility" 76 + disabled={!canToggle || updateProfile.isPending} 77 + value={isOptedOut} 78 + onChange={onToggleOptOut} 79 + label={_( 80 + msg`Discourage apps from showing my account to logged-out users`, 81 + )} 82 + style={[a.w_full]}> 83 + <Toggle.LabelText style={[a.flex_1]}> 84 + <Trans> 85 + Discourage apps from showing my account to logged-out users 86 + </Trans> 87 + </Toggle.LabelText> 88 + <Toggle.Platform /> 89 + </Toggle.Item> 90 + 91 + <Text style={[a.leading_snug, t.atoms.text_contrast_high]}> 92 + <Trans> 93 + Bluesky will not show your profile and posts to logged-out users. 94 + Other apps may not honor this request. This does not make your account 95 + private. 96 + </Trans> 97 + </Text> 98 + </View> 99 + ) 100 + }
+300
src/screens/Settings/components/SettingsList.tsx
··· 1 + import React, {useContext, useMemo} from 'react' 2 + import {GestureResponderEvent, StyleProp, View, ViewStyle} from 'react-native' 3 + 4 + import {HITSLOP_10} from '#/lib/constants' 5 + import {atoms as a, useTheme} from '#/alf' 6 + import * as Button from '#/components/Button' 7 + import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRightIcon} from '#/components/icons/Chevron' 8 + import {Link, LinkProps} from '#/components/Link' 9 + import {createPortalGroup} from '#/components/Portal' 10 + import {Text} from '#/components/Typography' 11 + 12 + const ItemContext = React.createContext({ 13 + destructive: false, 14 + withinGroup: false, 15 + }) 16 + 17 + const Portal = createPortalGroup() 18 + 19 + export function Container({children}: {children: React.ReactNode}) { 20 + return <View style={[a.flex_1, a.py_lg]}>{children}</View> 21 + } 22 + 23 + /** 24 + * This uses `Portal` magic ✨ to render the icons and title correctly. ItemIcon and ItemText components 25 + * get teleported to the top row, leaving the rest of the children in the bottom row. 26 + */ 27 + export function Group({ 28 + children, 29 + destructive = false, 30 + iconInset = true, 31 + style, 32 + contentContainerStyle, 33 + }: { 34 + children: React.ReactNode 35 + destructive?: boolean 36 + iconInset?: boolean 37 + style?: StyleProp<ViewStyle> 38 + contentContainerStyle?: StyleProp<ViewStyle> 39 + }) { 40 + const context = useMemo( 41 + () => ({destructive, withinGroup: true}), 42 + [destructive], 43 + ) 44 + return ( 45 + <View style={[a.w_full, style]}> 46 + <Portal.Provider> 47 + <ItemContext.Provider value={context}> 48 + <Item style={[a.pb_2xs, {minHeight: 42}]}> 49 + <Portal.Outlet /> 50 + </Item> 51 + <Item 52 + style={[ 53 + a.flex_col, 54 + a.pt_2xs, 55 + a.align_start, 56 + a.gap_0, 57 + contentContainerStyle, 58 + ]} 59 + iconInset={iconInset}> 60 + {children} 61 + </Item> 62 + </ItemContext.Provider> 63 + </Portal.Provider> 64 + </View> 65 + ) 66 + } 67 + 68 + export function Item({ 69 + children, 70 + destructive, 71 + iconInset = false, 72 + style, 73 + }: { 74 + children?: React.ReactNode 75 + destructive?: boolean 76 + /** 77 + * Adds left padding so that the content will be aligned with other Items that contain icons 78 + * @default false 79 + */ 80 + iconInset?: boolean 81 + style?: StyleProp<ViewStyle> 82 + }) { 83 + const context = useContext(ItemContext) 84 + const childContext = useMemo(() => { 85 + if (typeof destructive !== 'boolean') return context 86 + return {...context, destructive} 87 + }, [context, destructive]) 88 + return ( 89 + <View 90 + style={[ 91 + a.px_xl, 92 + a.py_sm, 93 + a.align_center, 94 + a.gap_md, 95 + a.w_full, 96 + a.flex_row, 97 + {minHeight: 48}, 98 + iconInset && { 99 + paddingLeft: 100 + // existing padding 101 + a.pl_xl.paddingLeft + 102 + // icon 103 + 28 + 104 + // gap 105 + a.gap_md.gap, 106 + }, 107 + style, 108 + ]}> 109 + <ItemContext.Provider value={childContext}> 110 + {children} 111 + </ItemContext.Provider> 112 + </View> 113 + ) 114 + } 115 + 116 + export function LinkItem({ 117 + children, 118 + destructive = false, 119 + contentContainerStyle, 120 + chevronColor, 121 + ...props 122 + }: LinkProps & { 123 + contentContainerStyle?: StyleProp<ViewStyle> 124 + destructive?: boolean 125 + chevronColor?: string 126 + }) { 127 + const t = useTheme() 128 + 129 + return ( 130 + <Link color="secondary" {...props}> 131 + {args => ( 132 + <Item 133 + destructive={destructive} 134 + style={[ 135 + (args.hovered || args.pressed) && [t.atoms.bg_contrast_25], 136 + contentContainerStyle, 137 + ]}> 138 + {typeof children === 'function' ? children(args) : children} 139 + <Chevron color={chevronColor} /> 140 + </Item> 141 + )} 142 + </Link> 143 + ) 144 + } 145 + 146 + export function PressableItem({ 147 + children, 148 + destructive = false, 149 + contentContainerStyle, 150 + hoverStyle, 151 + ...props 152 + }: Button.ButtonProps & { 153 + contentContainerStyle?: StyleProp<ViewStyle> 154 + destructive?: boolean 155 + }) { 156 + const t = useTheme() 157 + return ( 158 + <Button.Button {...props}> 159 + {args => ( 160 + <Item 161 + destructive={destructive} 162 + style={[ 163 + (args.hovered || args.pressed) && [ 164 + t.atoms.bg_contrast_25, 165 + hoverStyle, 166 + ], 167 + contentContainerStyle, 168 + ]}> 169 + {typeof children === 'function' ? children(args) : children} 170 + </Item> 171 + )} 172 + </Button.Button> 173 + ) 174 + } 175 + 176 + export function ItemIcon({ 177 + icon: Comp, 178 + size = 'xl', 179 + color: colorProp, 180 + }: Omit<React.ComponentProps<typeof Button.ButtonIcon>, 'position'> & { 181 + color?: string 182 + }) { 183 + const t = useTheme() 184 + const {destructive, withinGroup} = useContext(ItemContext) 185 + 186 + /* 187 + * Copied here from icons/common.tsx so we can tweak if we need to, but 188 + * also so that we can calculate transforms. 189 + */ 190 + const iconSize = { 191 + xs: 12, 192 + sm: 16, 193 + md: 20, 194 + lg: 24, 195 + xl: 28, 196 + '2xl': 32, 197 + }[size] 198 + 199 + const color = 200 + colorProp ?? (destructive ? t.palette.negative_500 : t.atoms.text.color) 201 + 202 + const content = ( 203 + <View style={[a.z_20, {width: iconSize, height: iconSize}]}> 204 + <Comp width={iconSize} style={[{color}]} /> 205 + </View> 206 + ) 207 + 208 + if (withinGroup) { 209 + return <Portal.Portal>{content}</Portal.Portal> 210 + } else { 211 + return content 212 + } 213 + } 214 + 215 + export function ItemText({ 216 + // eslint-disable-next-line react/prop-types 217 + style, 218 + ...props 219 + }: React.ComponentProps<typeof Button.ButtonText>) { 220 + const t = useTheme() 221 + const {destructive, withinGroup} = useContext(ItemContext) 222 + 223 + const content = ( 224 + <Button.ButtonText 225 + style={[ 226 + a.text_md, 227 + a.font_normal, 228 + a.text_left, 229 + a.flex_1, 230 + destructive ? {color: t.palette.negative_500} : t.atoms.text, 231 + style, 232 + ]} 233 + {...props} 234 + /> 235 + ) 236 + 237 + if (withinGroup) { 238 + return <Portal.Portal>{content}</Portal.Portal> 239 + } else { 240 + return content 241 + } 242 + } 243 + 244 + export function Divider() { 245 + const t = useTheme() 246 + return ( 247 + <View 248 + style={[a.border_t, t.atoms.border_contrast_medium, a.w_full, a.my_sm]} 249 + /> 250 + ) 251 + } 252 + 253 + export function Chevron({color: colorProp}: {color?: string}) { 254 + const {destructive} = useContext(ItemContext) 255 + const t = useTheme() 256 + const color = 257 + colorProp ?? (destructive ? t.palette.negative_500 : t.palette.contrast_500) 258 + return <ItemIcon icon={ChevronRightIcon} size="md" color={color} /> 259 + } 260 + 261 + export function BadgeText({children}: {children: React.ReactNode}) { 262 + const t = useTheme() 263 + return ( 264 + <Text 265 + style={[ 266 + t.atoms.text_contrast_low, 267 + a.text_md, 268 + a.text_right, 269 + a.leading_snug, 270 + ]} 271 + numberOfLines={1}> 272 + {children} 273 + </Text> 274 + ) 275 + } 276 + 277 + export function BadgeButton({ 278 + label, 279 + onPress, 280 + }: { 281 + label: string 282 + onPress: (evt: GestureResponderEvent) => void 283 + }) { 284 + const t = useTheme() 285 + return ( 286 + <Button.Button label={label} onPress={onPress} hitSlop={HITSLOP_10}> 287 + {({pressed}) => ( 288 + <Button.ButtonText 289 + style={[ 290 + a.text_md, 291 + a.font_normal, 292 + a.text_right, 293 + {color: pressed ? t.palette.contrast_300 : t.palette.primary_500}, 294 + ]}> 295 + {label} 296 + </Button.ButtonText> 297 + )} 298 + </Button.Button> 299 + ) 300 + }
+7 -6
src/view/com/modals/DeleteAccount.tsx
··· 10 10 import {msg, Trans} from '@lingui/macro' 11 11 import {useLingui} from '@lingui/react' 12 12 13 + import {usePalette} from '#/lib/hooks/usePalette' 14 + import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 15 + import {cleanError} from '#/lib/strings/errors' 16 + import {colors, gradients, s} from '#/lib/styles' 17 + import {useTheme} from '#/lib/ThemeContext' 18 + import {isAndroid, isWeb} from '#/platform/detection' 13 19 import {useModalControls} from '#/state/modals' 14 20 import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' 15 21 import {useAgent, useSession, useSessionApi} from '#/state/session' 16 - import {usePalette} from 'lib/hooks/usePalette' 17 - import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 18 - import {cleanError} from 'lib/strings/errors' 19 - import {colors, gradients, s} from 'lib/styles' 20 - import {useTheme} from 'lib/ThemeContext' 21 - import {isAndroid, isWeb} from 'platform/detection' 22 22 import {DeactivateAccountDialog} from '#/screens/Settings/components/DeactivateAccountDialog' 23 23 import {atoms as a, useTheme as useNewTheme} from '#/alf' 24 24 import {useDialogControl} from '#/components/Dialog' ··· 210 210 to="#" 211 211 onPress={e => { 212 212 e.preventDefault() 213 + closeModal() 213 214 deactivateAccountControl.open() 214 215 return false 215 216 }}>
+11 -1
src/view/screens/AccessibilitySettings.tsx
··· 4 4 import {useLingui} from '@lingui/react' 5 5 import {useFocusEffect} from '@react-navigation/native' 6 6 7 + import {IS_INTERNAL} from '#/lib/app-info' 7 8 import {usePalette} from '#/lib/hooks/usePalette' 8 9 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 9 10 import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' ··· 26 27 import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader' 27 28 import {Text} from '#/view/com/util/text/Text' 28 29 import {ScrollView} from '#/view/com/util/Views' 30 + import {AccessibilitySettingsScreen as NewAccessibilitySettingsScreen} from '#/screens/Settings/AccessibilitySettings' 29 31 import {atoms as a} from '#/alf' 30 32 import * as Layout from '#/components/Layout' 31 33 ··· 33 35 CommonNavigatorParams, 34 36 'AccessibilitySettings' 35 37 > 36 - export function AccessibilitySettingsScreen({}: Props) { 38 + export function AccessibilitySettingsScreen(props: Props) { 39 + return IS_INTERNAL ? ( 40 + <NewAccessibilitySettingsScreen {...props} /> 41 + ) : ( 42 + <LegacyAccessibilitySettingsScreen {...props} /> 43 + ) 44 + } 45 + 46 + function LegacyAccessibilitySettingsScreen({}: Props) { 37 47 const pal = usePalette('default') 38 48 const setMinimalShellMode = useSetMinimalShellMode() 39 49 const {isMobile, isTabletOrMobile} = useWebMediaQueries()
+10 -2
src/view/screens/Settings/DisableEmail2FADialog.tsx
··· 108 108 {error ? <ErrorMessage message={error} /> : undefined} 109 109 110 110 {stage === Stages.Email ? ( 111 - <View style={gtMobile && [a.flex_row, a.justify_end, a.gap_md]}> 111 + <View 112 + style={[ 113 + a.gap_sm, 114 + gtMobile && [a.flex_row, a.justify_end, a.gap_md], 115 + ]}> 112 116 <Button 113 117 testID="sendEmailButton" 114 118 variant="solid" ··· 157 161 /> 158 162 </TextField.Root> 159 163 </View> 160 - <View style={gtMobile && [a.flex_row, a.justify_end]}> 164 + <View 165 + style={[ 166 + a.gap_sm, 167 + gtMobile && [a.flex_row, a.justify_end, a.gap_md], 168 + ]}> 161 169 <Button 162 170 testID="resendCodeBtn" 163 171 variant="ghost"
+2
src/view/screens/Settings/ExportCarDialog.tsx
··· 10 10 import {atoms as a, useTheme} from '#/alf' 11 11 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 12 12 import * as Dialog from '#/components/Dialog' 13 + import {Download_Stroke2_Corner0_Rounded as DownloadIcon} from '#/components/icons/Download' 13 14 import {InlineLinkText} from '#/components/Link' 14 15 import {Loader} from '#/components/Loader' 15 16 import {Text} from '#/components/Typography' ··· 76 77 label={_(msg`Download CAR file`)} 77 78 disabled={loading} 78 79 onPress={download}> 80 + <ButtonIcon icon={DownloadIcon} /> 79 81 <ButtonText> 80 82 <Trans>Download CAR file</Trans> 81 83 </ButtonText>
+11 -2
src/view/screens/Settings/index.tsx
··· 18 18 import {useFocusEffect, useNavigation} from '@react-navigation/native' 19 19 import {useQueryClient} from '@tanstack/react-query' 20 20 21 - import {appVersion, BUNDLE_DATE, bundleInfo} from '#/lib/app-info' 21 + import {appVersion, BUNDLE_DATE, bundleInfo, IS_INTERNAL} from '#/lib/app-info' 22 22 import {STATUS_PAGE_URL} from '#/lib/constants' 23 23 import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' 24 24 import {useCustomPalette} from '#/lib/hooks/useCustomPalette' ··· 53 53 import {UserAvatar} from '#/view/com/util/UserAvatar' 54 54 import {ScrollView} from '#/view/com/util/Views' 55 55 import {DeactivateAccountDialog} from '#/screens/Settings/components/DeactivateAccountDialog' 56 + import {SettingsScreen as NewSettingsScreen} from '#/screens/Settings/Settings' 56 57 import {atoms as a, useTheme} from '#/alf' 57 58 import {useDialogControl} from '#/components/Dialog' 58 59 import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings' ··· 137 138 } 138 139 139 140 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'> 140 - export function SettingsScreen({}: Props) { 141 + export function SettingsScreen(props: Props) { 142 + return IS_INTERNAL ? ( 143 + <NewSettingsScreen {...props} /> 144 + ) : ( 145 + <LegacySettingsScreen {...props} /> 146 + ) 147 + } 148 + 149 + function LegacySettingsScreen({}: Props) { 141 150 const queryClient = useQueryClient() 142 151 const pal = usePalette('default') 143 152 const {_} = useLingui()
+134
src/view/screens/Storybook/Settings.tsx
··· 1 + import React from 'react' 2 + import {View} from 'react-native' 3 + 4 + import * as Toast from '#/view/com/util/Toast' 5 + import * as SettingsList from '#/screens/Settings/components/SettingsList' 6 + import {atoms as a, useTheme} from '#/alf' 7 + import {Alien_Stroke2_Corner0_Rounded as AlienIcon} from '#/components/icons/Alien' 8 + import {BirthdayCake_Stroke2_Corner2_Rounded as BirthdayCakeIcon} from '#/components/icons/BirthdayCake' 9 + import {BubbleInfo_Stroke2_Corner2_Rounded as BubbleInfoIcon} from '#/components/icons/BubbleInfo' 10 + import {CircleQuestion_Stroke2_Corner2_Rounded as CircleQuestionIcon} from '#/components/icons/CircleQuestion' 11 + import {Envelope_Stroke2_Corner2_Rounded as EnvelopeIcon} from '#/components/icons/Envelope' 12 + import {Explosion_Stroke2_Corner0_Rounded as ExplosionIcon} from '#/components/icons/Explosion' 13 + import {Earth_Stroke2_Corner2_Rounded as EarthIcon} from '#/components/icons/Globe' 14 + import {PaintRoller_Stroke2_Corner2_Rounded as PaintRollerIcon} from '#/components/icons/PaintRoller' 15 + import {Person_Stroke2_Corner2_Rounded as PersonIcon} from '#/components/icons/Person' 16 + import {Pizza_Stroke2_Corner0_Rounded as PizzaIcon} from '#/components/icons/Pizza' 17 + import {RaisingHand4Finger_Stroke2_Corner2_Rounded as HandIcon} from '#/components/icons/RaisingHand' 18 + import {Verified_Stroke2_Corner2_Rounded as VerifiedIcon} from '#/components/icons/Verified' 19 + import {Window_Stroke2_Corner2_Rounded as WindowIcon} from '#/components/icons/Window' 20 + import {Text} from '#/components/Typography' 21 + 22 + export function Settings() { 23 + const t = useTheme() 24 + return ( 25 + <View style={{marginLeft: -20, marginRight: -20}}> 26 + <Text style={{marginLeft: 20, paddingBottom: 12}}>Settings</Text> 27 + <SettingsList.LinkItem to="/settings" label="Account"> 28 + <SettingsList.ItemIcon icon={PersonIcon} /> 29 + <SettingsList.ItemText>Account</SettingsList.ItemText> 30 + </SettingsList.LinkItem> 31 + <SettingsList.LinkItem to="/settings" label="Privacy and security"> 32 + <SettingsList.ItemIcon icon={PaintRollerIcon} /> 33 + <SettingsList.ItemText>Privacy and security</SettingsList.ItemText> 34 + </SettingsList.LinkItem> 35 + <SettingsList.LinkItem to="/settings" label="Moderation"> 36 + <SettingsList.ItemIcon icon={HandIcon} /> 37 + <SettingsList.ItemText>Moderation</SettingsList.ItemText> 38 + </SettingsList.LinkItem> 39 + <SettingsList.LinkItem to="/settings" label="Content and media"> 40 + <SettingsList.ItemIcon icon={WindowIcon} /> 41 + <SettingsList.ItemText>Content and media</SettingsList.ItemText> 42 + </SettingsList.LinkItem> 43 + <SettingsList.LinkItem 44 + to="/settings" 45 + label="Accessibility and appearance"> 46 + <SettingsList.ItemIcon icon={PaintRollerIcon} /> 47 + <SettingsList.ItemText> 48 + Accessibilty and appearance 49 + </SettingsList.ItemText> 50 + </SettingsList.LinkItem> 51 + <SettingsList.LinkItem to="/settings" label="Languages"> 52 + <SettingsList.ItemIcon icon={EarthIcon} /> 53 + <SettingsList.ItemText>Languages</SettingsList.ItemText> 54 + </SettingsList.LinkItem> 55 + <SettingsList.LinkItem to="/settings" label="Help"> 56 + <SettingsList.ItemIcon icon={CircleQuestionIcon} /> 57 + <SettingsList.ItemText>Help</SettingsList.ItemText> 58 + </SettingsList.LinkItem> 59 + <SettingsList.LinkItem to="/settings" label="About"> 60 + <SettingsList.ItemIcon icon={BubbleInfoIcon} /> 61 + <SettingsList.ItemText>About</SettingsList.ItemText> 62 + </SettingsList.LinkItem> 63 + <SettingsList.Divider /> 64 + <SettingsList.PressableItem 65 + destructive 66 + onPress={() => Toast.show('Sign out pressed')} 67 + label="Sign out"> 68 + <SettingsList.ItemText>Sign out</SettingsList.ItemText> 69 + </SettingsList.PressableItem> 70 + <SettingsList.Item style={[a.mt_xl]}> 71 + <SettingsList.ItemIcon icon={PizzaIcon} /> 72 + <SettingsList.ItemText>Not pressable</SettingsList.ItemText> 73 + </SettingsList.Item> 74 + <SettingsList.PressableItem 75 + onPress={() => Toast.show('Pressable pressed')} 76 + label="Pressable"> 77 + <SettingsList.ItemIcon icon={AlienIcon} /> 78 + <SettingsList.ItemText>Pressable</SettingsList.ItemText> 79 + </SettingsList.PressableItem> 80 + <SettingsList.LinkItem 81 + to="/settings" 82 + label="Destructive link" 83 + destructive> 84 + <SettingsList.ItemIcon icon={ExplosionIcon} /> 85 + <SettingsList.ItemText>Destructive link</SettingsList.ItemText> 86 + </SettingsList.LinkItem> 87 + <SettingsList.PressableItem 88 + label="Email" 89 + onPress={() => Toast.show('Email change dialog goes here')}> 90 + <SettingsList.ItemIcon icon={EnvelopeIcon} /> 91 + <SettingsList.ItemText>Email</SettingsList.ItemText> 92 + <SettingsList.BadgeText>hello@example.com</SettingsList.BadgeText> 93 + </SettingsList.PressableItem> 94 + <SettingsList.PressableItem 95 + onPress={() => Toast.show('Pressable pressed')} 96 + label="Protect your account" 97 + style={[ 98 + a.my_sm, 99 + a.mx_lg, 100 + a.rounded_md, 101 + {backgroundColor: t.palette.primary_50}, 102 + ]} 103 + hoverStyle={[{backgroundColor: t.palette.primary_100}]} 104 + contentContainerStyle={[a.rounded_md, a.px_lg]}> 105 + <SettingsList.ItemIcon 106 + icon={VerifiedIcon} 107 + color={t.palette.primary_500} 108 + /> 109 + <SettingsList.ItemText 110 + style={[{color: t.palette.primary_500}, a.font_bold]}> 111 + Protect your account 112 + </SettingsList.ItemText> 113 + <SettingsList.Chevron color={t.palette.primary_500} /> 114 + </SettingsList.PressableItem> 115 + <SettingsList.Divider /> 116 + <SettingsList.Item> 117 + <SettingsList.ItemIcon icon={BirthdayCakeIcon} /> 118 + <SettingsList.ItemText>Birthday</SettingsList.ItemText> 119 + <SettingsList.BadgeButton 120 + label="Edit" 121 + onPress={() => Toast.show('Show edit birthday dialog')} 122 + /> 123 + </SettingsList.Item> 124 + <SettingsList.LinkItem to="/settings" label="Long test"> 125 + <SettingsList.ItemIcon icon={ExplosionIcon} /> 126 + <SettingsList.ItemText> 127 + long long long long long long long long long long long long long long 128 + long long long long long long long long long long long long long long 129 + long long long long long long long long long 130 + </SettingsList.ItemText> 131 + </SettingsList.LinkItem> 132 + </View> 133 + ) 134 + }
+3
src/view/screens/Storybook/index.tsx
··· 18 18 import {Icons} from './Icons' 19 19 import {Links} from './Links' 20 20 import {Menus} from './Menus' 21 + import {Settings} from './Settings' 21 22 import {Shadows} from './Shadows' 22 23 import {Spacing} from './Spacing' 23 24 import {Theming} from './Theming' ··· 100 101 </Button> 101 102 102 103 <Admonitions /> 104 + 105 + <Settings /> 103 106 104 107 <ThemeProvider theme="light"> 105 108 <Theming />