Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: composer account switcher

a prerequisite to implementing other cross-account actions

authored by

Callie Peden and committed by
xan.lol
8be3eb4c 1633c1f9

+361 -57
+1 -1
flake.nix
··· 85 85 just 86 86 fastmod 87 87 nodejs 88 - yarn 88 + pnpm 89 89 crowdin-cli 90 90 eas-cli 91 91
+190 -23
pnpm-lock.yaml
··· 5 5 excludeLinksFromLockfile: false 6 6 7 7 overrides: 8 - '@react-native/babel-preset': 0.81.5 9 - '@react-native/normalize-colors': 0.81.5 10 - '@expo/image-utils': 0.8.12 11 - multiformats: 9.9.0 12 - unicode-segmenter: 0.14.5 13 - '@types/estree': 1.0.6 14 - metro: 0.83.3 15 - metro-core: 0.83.3 16 - metro-config: 0.83.3 17 - metro-runtime: 0.83.3 18 - metro-source-map: 0.83.3 19 - react-native-mmkv: npm:@bsky.app/react-native-mmkv@2.12.5 20 - react-native-compressor: 1.13.0 21 - sonner-native: 0.21.0 8 + '@babel/helper-define-polyfill-provider>@babel/helper-compilation-targets': ^7.28.6 22 9 23 10 importers: 24 11 ··· 2638 2625 '@expo/html-elements@0.12.5': 2639 2626 resolution: {integrity: sha512-28KWO88YKykKU7ke5sEQs5TivFRMs1Aktz13xxgqAf5rTgb+lka0VKVt3W2fG7ksbUQ407rtUqz7SEAq298NvQ==} 2640 2627 2628 + '@expo/image-utils@0.3.23': 2629 + resolution: {integrity: sha512-nhUVvW0TrRE4jtWzHQl8TR4ox7kcmrc2I0itaeJGjxF5A54uk7avgA0wRt7jP1rdvqQo1Ke1lXyLYREdhN9tPw==} 2630 + 2641 2631 '@expo/image-utils@0.8.12': 2642 2632 resolution: {integrity: sha512-3KguH7kyKqq7pNwLb9j6BBdD/bjmNwXZG/HPWT6GWIXbwrvAJt2JNyYTP5agWJ8jbbuys1yuCzmkX+TU6rmI7A==} 2643 2633 ··· 2682 2672 '@expo/sdk-runtime-versions@1.0.0': 2683 2673 resolution: {integrity: sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==} 2684 2674 2675 + '@expo/spawn-async@1.5.0': 2676 + resolution: {integrity: sha512-LB7jWkqrHo+5fJHNrLAFdimuSXQ2MQ4lA7SQW5bf/HbsXuV2VrT/jN/M8f/KoWt0uJMGN4k/j7Opx4AvOOxSew==} 2677 + engines: {node: '>=4'} 2678 + 2685 2679 '@expo/spawn-async@1.7.2': 2686 2680 resolution: {integrity: sha512-QdWi16+CHB9JYP7gma19OVVg0BFkvU8zNj9GjWorYI8Iv8FUxjOCcYRuAmX4s/h91e4e7BPsskc8cSrZYho9Ew==} 2687 2681 engines: {node: '>=12'} ··· 4081 4075 resolution: {integrity: sha512-fB7M1CMOCIUudTRuj7kzxIBTVw2KXnsgbQ6+4cbqSxo8NmRRhA0Ul4ZUzZj3rFd3VznTL4Brmocv1oiN0bWZ8w==} 4082 4076 engines: {node: '>= 20.19.4'} 4083 4077 4078 + '@react-native/normalize-colors@0.74.89': 4079 + resolution: {integrity: sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg==} 4080 + 4084 4081 '@react-native/normalize-colors@0.81.5': 4085 4082 resolution: {integrity: sha512-0HuJ8YtqlTVRXGZuGeBejLE04wSQsibpTI+RGOyVqxZvgtlLLC/Ssw0UmbHhT4lYMp2fhdtvKZSs5emWB1zR/g==} 4086 4083 ··· 5298 5295 '@types/estree@1.0.6': 5299 5296 resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} 5300 5297 5298 + '@types/estree@1.0.8': 5299 + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} 5300 + 5301 5301 '@types/express-serve-static-core@4.19.8': 5302 5302 resolution: {integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==} 5303 5303 ··· 6026 6026 asynckit@0.4.0: 6027 6027 resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} 6028 6028 6029 + at-least-node@1.0.0: 6030 + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} 6031 + engines: {node: '>= 4.0.0'} 6032 + 6029 6033 atomic-sleep@1.0.0: 6030 6034 resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} 6031 6035 engines: {node: '>=8.0.0'} ··· 6679 6683 cross-fetch@3.2.0: 6680 6684 resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==} 6681 6685 6686 + cross-spawn@6.0.6: 6687 + resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} 6688 + engines: {node: '>=4.8'} 6689 + 6682 6690 cross-spawn@7.0.6: 6683 6691 resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 6684 6692 engines: {node: '>= 8'} 6693 + 6694 + crypto-random-string@1.0.0: 6695 + resolution: {integrity: sha512-GsVpkFPlycH7/fRR7Dhcmnoii54gV1nz7y4CWyeFS14N+JVBBhY+r8amRHE4BwSYal7BPTDp8isvAlCxyFt3Hg==} 6696 + engines: {node: '>=4'} 6685 6697 6686 6698 css-background-parser@0.1.0: 6687 6699 resolution: {integrity: sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==} ··· 7918 7930 resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} 7919 7931 engines: {node: '>=6 <7 || >=8'} 7920 7932 7933 + fs-extra@9.0.0: 7934 + resolution: {integrity: sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==} 7935 + engines: {node: '>=10'} 7936 + 7921 7937 fs-monkey@1.1.0: 7922 7938 resolution: {integrity: sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==} 7923 7939 ··· 9312 9328 engines: {node: '>=4'} 9313 9329 hasBin: true 9314 9330 9331 + mime@2.6.0: 9332 + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} 9333 + engines: {node: '>=4.0.0'} 9334 + hasBin: true 9335 + 9315 9336 mimic-fn@1.2.0: 9316 9337 resolution: {integrity: sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==} 9317 9338 engines: {node: '>=4'} ··· 9402 9423 resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} 9403 9424 hasBin: true 9404 9425 9426 + multiformats@13.4.2: 9427 + resolution: {integrity: sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==} 9428 + 9405 9429 multiformats@9.9.0: 9406 9430 resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} 9407 9431 ··· 9450 9474 nested-error-stacks@2.0.1: 9451 9475 resolution: {integrity: sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==} 9452 9476 9477 + nice-try@1.0.5: 9478 + resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} 9479 + 9453 9480 no-case@3.0.4: 9454 9481 resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} 9455 9482 ··· 9750 9777 9751 9778 path-is-inside@1.0.2: 9752 9779 resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==} 9780 + 9781 + path-key@2.0.1: 9782 + resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} 9783 + engines: {node: '>=4'} 9753 9784 9754 9785 path-key@3.1.1: 9755 9786 resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} ··· 10516 10547 react-native: '*' 10517 10548 react-native-reanimated: '>=3.0.0' 10518 10549 10550 + react-native-mmkv@3.3.3: 10551 + resolution: {integrity: sha512-GMsfOmNzx0p5+CtrCFRVtpOOMYNJXuksBVARSQrCFaZwjUyHJdQzcN900GGaFFNTxw2fs8s5Xje//RDKj9+PZA==} 10552 + peerDependencies: 10553 + react: '*' 10554 + react-native: '*' 10555 + 10519 10556 react-native-pager-view@6.8.0: 10520 10557 resolution: {integrity: sha512-/wgFV8nB4TLnQ6j9e3TvNrHbPF5TINMZHaXt86GOV0NSJNMVGkWguniJVKrYLm85LL8KVhRkgdh43Rdu7PvW1A==} 10521 10558 peerDependencies: ··· 10950 10987 semver-compare@1.0.0: 10951 10988 resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} 10952 10989 10990 + semver@5.7.2: 10991 + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} 10992 + hasBin: true 10993 + 10953 10994 semver@6.3.1: 10954 10995 resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 10996 + hasBin: true 10997 + 10998 + semver@7.3.2: 10999 + resolution: {integrity: sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==} 11000 + engines: {node: '>=10'} 10955 11001 hasBin: true 10956 11002 10957 11003 semver@7.5.4: ··· 11026 11072 resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} 11027 11073 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 11028 11074 11075 + shebang-command@1.2.0: 11076 + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} 11077 + engines: {node: '>=0.10.0'} 11078 + 11029 11079 shebang-command@2.0.0: 11030 11080 resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 11031 11081 engines: {node: '>=8'} 11032 11082 11083 + shebang-regex@1.0.0: 11084 + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} 11085 + engines: {node: '>=0.10.0'} 11086 + 11033 11087 shebang-regex@3.0.0: 11034 11088 resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 11035 11089 engines: {node: '>=8'} ··· 11431 11485 tdigest@0.1.2: 11432 11486 resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==} 11433 11487 11488 + temp-dir@1.0.0: 11489 + resolution: {integrity: sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==} 11490 + engines: {node: '>=4'} 11491 + 11492 + tempy@0.3.0: 11493 + resolution: {integrity: sha512-WrH/pui8YCwmeiAoxV+lpRH9HpRtgBhSR2ViBPgpGb/wnYDzp21R4MN45fsCGvLROvY67o3byhJRYRONJyImVQ==} 11494 + engines: {node: '>=8'} 11495 + 11434 11496 terminal-link@2.1.1: 11435 11497 resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} 11436 11498 engines: {node: '>=8'} ··· 11607 11669 type-fest@0.21.3: 11608 11670 resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} 11609 11671 engines: {node: '>=10'} 11672 + 11673 + type-fest@0.3.1: 11674 + resolution: {integrity: sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==} 11675 + engines: {node: '>=6'} 11610 11676 11611 11677 type-fest@0.7.1: 11612 11678 resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} ··· 11729 11795 unicode-trie@2.0.0: 11730 11796 resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} 11731 11797 11798 + unique-string@1.0.0: 11799 + resolution: {integrity: sha512-ODgiYu03y5g76A1I9Gt0/chLCzQjvzDy7DsZGsLOE/1MrF6wriEskSncj1+/C58Xk/kPZDppSctDybCwOSaGAg==} 11800 + engines: {node: '>=4'} 11801 + 11732 11802 universalify@0.1.2: 11733 11803 resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} 11734 11804 engines: {node: '>= 4.0.0'} ··· 11736 11806 universalify@0.2.0: 11737 11807 resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} 11738 11808 engines: {node: '>= 4.0.0'} 11809 + 11810 + universalify@1.0.0: 11811 + resolution: {integrity: sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==} 11812 + engines: {node: '>= 10.0.0'} 11739 11813 11740 11814 universalify@2.0.1: 11741 11815 resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} ··· 12115 12189 which-typed-array@1.1.20: 12116 12190 resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} 12117 12191 engines: {node: '>= 0.4'} 12192 + 12193 + which@1.3.1: 12194 + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} 12195 + hasBin: true 12118 12196 12119 12197 which@2.0.2: 12120 12198 resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} ··· 12742 12820 expo: 54.0.33(@babel/core@7.25.2)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0) 12743 12821 expo-web-browser: 15.0.10(expo@54.0.33(@babel/core@7.25.2)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0)) 12744 12822 react-native: 0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0) 12745 - react-native-mmkv: '@bsky.app/react-native-mmkv@2.12.5(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0)' 12823 + react-native-mmkv: 3.3.3(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0) 12746 12824 react-native-url-polyfill: 3.0.0(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0)) 12747 12825 transitivePeerDependencies: 12748 12826 - react ··· 15596 15674 15597 15675 '@expo/html-elements@0.12.5': {} 15598 15676 15677 + '@expo/image-utils@0.3.23': 15678 + dependencies: 15679 + '@expo/spawn-async': 1.5.0 15680 + chalk: 4.1.2 15681 + fs-extra: 9.0.0 15682 + getenv: 1.0.0 15683 + jimp-compact: 0.16.1 15684 + mime: 2.6.0 15685 + node-fetch: 2.7.0 15686 + parse-png: 2.1.0 15687 + resolve-from: 5.0.0 15688 + semver: 7.3.2 15689 + tempy: 0.3.0 15690 + transitivePeerDependencies: 15691 + - encoding 15692 + 15599 15693 '@expo/image-utils@0.8.12': 15600 15694 dependencies: 15601 15695 '@expo/spawn-async': 1.7.2 ··· 15711 15805 '@expo/schema-utils@0.1.8': {} 15712 15806 15713 15807 '@expo/sdk-runtime-versions@1.0.0': {} 15808 + 15809 + '@expo/spawn-async@1.5.0': 15810 + dependencies: 15811 + cross-spawn: 6.0.6 15714 15812 15715 15813 '@expo/spawn-async@1.7.2': 15716 15814 dependencies: ··· 15758 15856 - clean-css 15759 15857 - csso 15760 15858 - debug 15859 + - encoding 15761 15860 - esbuild 15762 15861 - supports-color 15763 15862 - uglify-js ··· 16026 16125 '@ipld/dag-cbor@9.2.6': 16027 16126 dependencies: 16028 16127 cborg: 5.1.0 16029 - multiformats: 9.9.0 16128 + multiformats: 13.4.2 16030 16129 16031 16130 '@isaacs/cliui@8.0.2': 16032 16131 dependencies: ··· 17379 17478 17380 17479 '@react-native/js-polyfills@0.81.5': {} 17381 17480 17481 + '@react-native/normalize-colors@0.74.89': {} 17482 + 17382 17483 '@react-native/normalize-colors@0.81.5': {} 17383 17484 17384 17485 '@react-native/typescript-config@0.81.6': {} ··· 18568 18669 '@types/eslint-scope@3.7.7': 18569 18670 dependencies: 18570 18671 '@types/eslint': 9.6.1 18571 - '@types/estree': 1.0.6 18672 + '@types/estree': 1.0.8 18572 18673 18573 18674 '@types/eslint@9.6.1': 18574 18675 dependencies: 18575 - '@types/estree': 1.0.6 18676 + '@types/estree': 1.0.8 18576 18677 '@types/json-schema': 7.0.15 18577 18678 18578 18679 '@types/estree@1.0.6': {} 18680 + 18681 + '@types/estree@1.0.8': {} 18579 18682 18580 18683 '@types/express-serve-static-core@4.19.8': 18581 18684 dependencies: ··· 19405 19508 19406 19509 asynckit@0.4.0: {} 19407 19510 19511 + at-least-node@1.0.0: {} 19512 + 19408 19513 atomic-sleep@1.0.0: {} 19409 19514 19410 19515 autoprefixer@10.5.0(postcss@8.5.10): ··· 20179 20284 transitivePeerDependencies: 20180 20285 - encoding 20181 20286 20287 + cross-spawn@6.0.6: 20288 + dependencies: 20289 + nice-try: 1.0.5 20290 + path-key: 2.0.1 20291 + semver: 5.7.2 20292 + shebang-command: 1.2.0 20293 + which: 1.3.1 20294 + 20182 20295 cross-spawn@7.0.6: 20183 20296 dependencies: 20184 20297 path-key: 3.1.1 20185 20298 shebang-command: 2.0.0 20186 20299 which: 2.0.2 20300 + 20301 + crypto-random-string@1.0.0: {} 20187 20302 20188 20303 css-background-parser@0.1.0: {} 20189 20304 ··· 21312 21427 21313 21428 expo-pwa@0.0.127(expo@54.0.33(@babel/core@7.25.2)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0)): 21314 21429 dependencies: 21315 - '@expo/image-utils': 0.8.12 21430 + '@expo/image-utils': 0.3.23 21316 21431 chalk: 4.1.2 21317 21432 commander: 2.20.0 21318 21433 expo: 54.0.33(@babel/core@7.25.2)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0) 21319 21434 update-check: 1.5.3 21435 + transitivePeerDependencies: 21436 + - encoding 21320 21437 21321 21438 expo-screen-orientation@9.0.8(expo@54.0.33(@babel/core@7.25.2)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0)): 21322 21439 dependencies: ··· 21693 21810 jsonfile: 4.0.0 21694 21811 universalify: 0.1.2 21695 21812 21813 + fs-extra@9.0.0: 21814 + dependencies: 21815 + at-least-node: 1.0.0 21816 + graceful-fs: 4.2.11 21817 + jsonfile: 6.2.1 21818 + universalify: 1.0.0 21819 + 21696 21820 fs-monkey@1.1.0: {} 21697 21821 21698 21822 fs.realpath@1.0.0: {} ··· 23443 23567 23444 23568 mime@1.6.0: {} 23445 23569 23570 + mime@2.6.0: {} 23571 + 23446 23572 mimic-fn@1.2.0: {} 23447 23573 23448 23574 mimic-fn@2.1.0: {} ··· 23508 23634 dns-packet: 5.6.1 23509 23635 thunky: 1.1.0 23510 23636 23637 + multiformats@13.4.2: {} 23638 + 23511 23639 multiformats@9.9.0: {} 23512 23640 23513 23641 murmurhash@2.0.1: {} ··· 23537 23665 neo-async@2.6.2: {} 23538 23666 23539 23667 nested-error-stacks@2.0.1: {} 23668 + 23669 + nice-try@1.0.5: {} 23540 23670 23541 23671 no-case@3.0.4: 23542 23672 dependencies: ··· 23858 23988 path-is-absolute@1.0.1: {} 23859 23989 23860 23990 path-is-inside@1.0.2: {} 23991 + 23992 + path-key@2.0.1: {} 23861 23993 23862 23994 path-key@3.1.1: {} 23863 23995 ··· 24687 24819 react-native-is-edge-to-edge: 1.3.1(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0) 24688 24820 react-native-reanimated: 3.19.5(@babel/core@7.25.2)(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0) 24689 24821 24822 + react-native-mmkv@3.3.3(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0): 24823 + dependencies: 24824 + react: 19.1.0 24825 + react-native: 0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0) 24826 + 24690 24827 react-native-pager-view@6.8.0(react-native@0.81.5(@babel/core@7.25.2)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0): 24691 24828 dependencies: 24692 24829 react: 19.1.0 ··· 24772 24909 react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): 24773 24910 dependencies: 24774 24911 '@babel/runtime': 7.25.9 24775 - '@react-native/normalize-colors': 0.81.5 24912 + '@react-native/normalize-colors': 0.74.89 24776 24913 fbjs: 3.0.5 24777 24914 inline-style-prefixer: 7.0.1 24778 24915 memoize-one: 6.0.0 ··· 25121 25258 25122 25259 rollup@4.60.2: 25123 25260 dependencies: 25124 - '@types/estree': 1.0.6 25261 + '@types/estree': 1.0.8 25125 25262 optionalDependencies: 25126 25263 '@rollup/rollup-android-arm-eabi': 4.60.2 25127 25264 '@rollup/rollup-android-arm64': 4.60.2 ··· 25249 25386 node-forge: 1.4.0 25250 25387 25251 25388 semver-compare@1.0.0: {} 25389 + 25390 + semver@5.7.2: {} 25252 25391 25253 25392 semver@6.3.1: {} 25254 25393 25394 + semver@7.3.2: {} 25395 + 25255 25396 semver@7.5.4: 25256 25397 dependencies: 25257 25398 lru-cache: 6.0.0 ··· 25388 25529 '@img/sharp-wasm32': 0.33.5 25389 25530 '@img/sharp-win32-ia32': 0.33.5 25390 25531 '@img/sharp-win32-x64': 0.33.5 25532 + 25533 + shebang-command@1.2.0: 25534 + dependencies: 25535 + shebang-regex: 1.0.0 25391 25536 25392 25537 shebang-command@2.0.0: 25393 25538 dependencies: 25394 25539 shebang-regex: 3.0.0 25395 25540 25541 + shebang-regex@1.0.0: {} 25542 + 25396 25543 shebang-regex@3.0.0: {} 25397 25544 25398 25545 shell-quote@1.8.3: {} ··· 25858 26005 dependencies: 25859 26006 bintrees: 1.0.2 25860 26007 26008 + temp-dir@1.0.0: {} 26009 + 26010 + tempy@0.3.0: 26011 + dependencies: 26012 + temp-dir: 1.0.0 26013 + type-fest: 0.3.1 26014 + unique-string: 1.0.0 26015 + 25861 26016 terminal-link@2.1.1: 25862 26017 dependencies: 25863 26018 ansi-escapes: 4.3.2 ··· 26060 26215 26061 26216 type-fest@0.21.3: {} 26062 26217 26218 + type-fest@0.3.1: {} 26219 + 26063 26220 type-fest@0.7.1: {} 26064 26221 26065 26222 type-fest@1.4.0: {} ··· 26151 26308 26152 26309 uint8arrays@5.1.1: 26153 26310 dependencies: 26154 - multiformats: 9.9.0 26311 + multiformats: 13.4.2 26155 26312 26156 26313 unbox-primitive@1.1.0: 26157 26314 dependencies: ··· 26186 26343 pako: 0.2.9 26187 26344 tiny-inflate: 1.0.3 26188 26345 26346 + unique-string@1.0.0: 26347 + dependencies: 26348 + crypto-random-string: 1.0.0 26349 + 26189 26350 universalify@0.1.2: {} 26190 26351 26191 26352 universalify@0.2.0: {} 26353 + 26354 + universalify@1.0.0: {} 26192 26355 26193 26356 universalify@2.0.1: {} 26194 26357 ··· 26515 26678 webpack@5.106.2(@swc/core@1.15.30)(esbuild@0.27.7): 26516 26679 dependencies: 26517 26680 '@types/eslint-scope': 3.7.7 26518 - '@types/estree': 1.0.6 26681 + '@types/estree': 1.0.8 26519 26682 '@types/json-schema': 7.0.15 26520 26683 '@webassemblyjs/ast': 1.14.1 26521 26684 '@webassemblyjs/wasm-edit': 1.14.1 ··· 26617 26780 get-proto: 1.0.1 26618 26781 gopd: 1.2.0 26619 26782 has-tostringtag: 1.0.2 26783 + 26784 + which@1.3.1: 26785 + dependencies: 26786 + isexe: 2.0.0 26620 26787 26621 26788 which@2.0.2: 26622 26789 dependencies:
+34 -1
src/state/session/index.tsx
··· 1 1 import { 2 2 createContext, 3 + type PropsWithChildren, 3 4 useCallback, 4 5 useContext, 5 6 useEffect, ··· 64 65 resumeSession: async () => {}, 65 66 removeAccount: () => {}, 66 67 partialRefreshSession: async () => {}, 68 + createEphemeralAgent: async () => { 69 + throw new Error('Not implemented') 70 + }, 67 71 }) 68 72 ApiContext.displayName = 'SessionApiContext' 69 73 ··· 108 112 } 109 113 } 110 114 111 - export function Provider({children}: React.PropsWithChildren<{}>) { 115 + export function Provider({children}: PropsWithChildren<{}>) { 112 116 const ax = useAnalyticsBase() 113 117 const cancelPendingTask = useOneTaskAtATime() 114 118 // eslint-disable-next-line react/hook-use-state ··· 314 318 }) 315 319 }, [store, state, cancelPendingTask]) 316 320 321 + const createEphemeralAgent = useCallback< 322 + SessionApiContext['createEphemeralAgent'] 323 + >( 324 + async storedAccount => { 325 + if (storedAccount.isOauthSession) { 326 + const {agent} = await oauthResumeSession(storedAccount) 327 + return agent 328 + } 329 + const {agent} = await createAgentAndResume( 330 + storedAccount, 331 + (ephemeralAgent, accountDid, sessionEvent) => { 332 + const refreshedAccount = agentToSessionAccount(ephemeralAgent) 333 + 334 + store.dispatch({ 335 + type: 'received-agent-event', 336 + agent: ephemeralAgent as any, 337 + refreshedAccount, 338 + accountDid, 339 + sessionEvent, 340 + }) 341 + }, 342 + ) 343 + return agent 344 + }, 345 + [store], 346 + ) 347 + 317 348 const removeAccount = useCallback<SessionApiContext['removeAccount']>( 318 349 account => { 319 350 addSessionDebugLog({ ··· 388 419 resumeSession, 389 420 removeAccount, 390 421 partialRefreshSession, 422 + createEphemeralAgent, 391 423 }), 392 424 [ 393 425 createAccount, ··· 397 429 resumeSession, 398 430 removeAccount, 399 431 partialRefreshSession, 432 + createEphemeralAgent, 400 433 ], 401 434 ) 402 435
+3
src/state/session/types.ts
··· 54 54 * `persistSessionHandler`. 55 55 */ 56 56 partialRefreshSession: () => Promise<void> 57 + createEphemeralAgent: ( 58 + account: SessionAccount, 59 + ) => Promise<import('@atproto/api').BskyAgent> 57 60 }
+104 -20
src/view/com/composer/Composer.tsx
··· 1 1 import { 2 2 Fragment, 3 3 memo, 4 + type ReactNode, 5 + type RefObject, 4 6 useCallback, 5 7 useEffect, 6 8 useImperativeHandle, ··· 106 108 useOpenRouterModel, 107 109 } from '#/state/preferences/openrouter' 108 110 import {usePreferencesQuery} from '#/state/queries/preferences' 109 - import {useProfileQuery} from '#/state/queries/profile' 111 + import {useProfileQuery, useProfilesQuery} from '#/state/queries/profile' 110 112 import {type Gif} from '#/state/queries/tenor' 111 - import {useAgent, useSession} from '#/state/session' 113 + import {useAgent, useSession, useSessionApi} from '#/state/session' 112 114 import {useComposerControls} from '#/state/shell/composer' 115 + import {useLoggedOutViewControls} from '#/state/shell/logged-out' 113 116 import {type ComposerOpts, type OnPostSuccessData} from '#/state/shell/composer' 114 117 import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' 115 118 import {ComposerReplyTo} from '#/view/com/composer/ComposerReplyTo' ··· 134 137 import {VideoPreview} from '#/view/com/composer/videos/VideoPreview' 135 138 import {VideoTranscodeProgress} from '#/view/com/composer/videos/VideoTranscodeProgress' 136 139 import {UserAvatar} from '#/view/com/util/UserAvatar' 140 + import {SwitchMenuItems} from '#/view/shell/desktop/LeftNav' 137 141 import {atoms as a, native, useBreakpoints, useTheme, web} from '#/alf' 138 142 import {Admonition} from '#/components/Admonition' 139 143 import {Button, ButtonIcon, ButtonText} from '#/components/Button' ··· 142 146 import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmileIcon} from '#/components/icons/Emoji' 143 147 import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' 144 148 import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' 149 + import * as Menu from '#/components/Menu' 145 150 import {LazyQuoteEmbed} from '#/components/Post/Embed/LazyQuoteEmbed' 146 151 import * as Prompt from '#/components/Prompt' 147 152 import * as Toast from '#/components/Toast' ··· 205 210 logContext, 206 211 cancelRef, 207 212 }: Props & { 208 - cancelRef?: React.RefObject<CancelRef | null> 213 + cancelRef?: RefObject<CancelRef | null> 209 214 }) => { 210 - const {currentAccount} = useSession() 215 + const {currentAccount, accounts} = useSession() 211 216 const t = useTheme() 212 217 const ax = useAnalytics() 213 218 const agent = useAgent() 219 + const sessionApi = useSessionApi() 214 220 const queryClient = useQueryClient() 215 221 const currentDid = currentAccount!.did 222 + 223 + const [activeAccountDid, setActiveAccountDid] = useState<string>(currentDid) 224 + 216 225 const {closeComposer} = useComposerControls() 226 + const {requestSwitchToAccount} = useLoggedOutViewControls() 217 227 const {t: l, i18n} = useLingui() 218 228 const requireAltTextEnabled = useRequireAltTextEnabled() 219 229 const omitViaField = useOmitViaField() ··· 345 355 texts: splitPosts, 346 356 }) 347 357 }, [activePost.id, activePost.richtext.text, composerDispatch]) 348 - 349 358 const selectVideo = useCallback( 350 359 (postId: string, asset: ImagePickerAsset) => { 351 360 const abortController = new AbortController() ··· 902 911 setError('') 903 912 setIsPublishing(true) 904 913 914 + let currentAgent = agent 915 + let ephemeralAgent: BskyAgent | undefined 905 916 let postUri: string | undefined 906 917 let postSuccessData: OnPostSuccessData 907 918 try { 919 + if (activeAccountDid && activeAccountDid !== currentAccount?.did) { 920 + const activeAccount = accounts.find(a => a.did === activeAccountDid) 921 + if (activeAccount) { 922 + try { 923 + ephemeralAgent = await sessionApi.createEphemeralAgent(activeAccount) 924 + currentAgent = ephemeralAgent 925 + } catch (e) { 926 + logger.error('Composer: failed to create ephemeral agent for account switch', { 927 + message: e instanceof Error ? e.message : String(e), 928 + }) 929 + setIsPublishing(false) 930 + requestSwitchToAccount({requestedAccount: activeAccount.did}) 931 + Toast.show(l`Please sign in as @${activeAccount.handle} to post as them`, { 932 + type: 'warning', 933 + }) 934 + return 935 + } 936 + } 937 + } 938 + 908 939 logger.info(`composer: posting...`) 909 940 postUri = ( 910 941 await apilib.post( 911 - agent, 942 + currentAgent, 912 943 queryClient, 913 944 { 914 945 thread: filteredThread, ··· 940 971 5, 941 972 _e => true, 942 973 async () => { 943 - const res = await agent.app.bsky.unspecced.getPostThreadV2({ 974 + const res = await currentAgent.app.bsky.unspecced.getPostThreadV2({ 944 975 anchor: postUri!, 945 976 above: false, 946 977 below: filteredThread.posts.length - 1, ··· 988 1019 setIsPublishing(false) 989 1020 return 990 1021 } finally { 1022 + if (ephemeralAgent && 'dispose' in ephemeralAgent) { 1023 + // @ts-ignore 1024 + ephemeralAgent.dispose() 1025 + } 1026 + 991 1027 if (postUri) { 992 1028 let index = 0 993 1029 for (let post of filteredThread.posts) { ··· 1096 1132 onPostSuccess, 1097 1133 initQuote, 1098 1134 replyTo, 1099 - setLangPrefs, 1135 + setPublishOnUpload, 1100 1136 queryClient, 1101 1137 navigation, 1102 1138 composerState.draftId, ··· 1105 1141 cleanupPublishedDraft, 1106 1142 loadedDraftCreatedAt, 1107 1143 emptyPostsPromptControl, 1144 + setLangPrefs, 1145 + accounts, 1146 + activeAccountDid, 1147 + currentAccount?.did, 1148 + sessionApi, 1149 + requestSwitchToAccount, 1108 1150 ]) 1109 1151 1110 1152 const handleConfirmSkipEmpty = () => { ··· 1305 1347 canRemoveQuote={index > 0 || !initQuote} 1306 1348 onSelectVideo={selectVideo} 1307 1349 onClearVideo={clearVideo} 1308 - onPublish={onComposerPostPublish} 1309 1350 onError={setError} 1351 + onPublish={onComposerPostPublish} 1352 + activeAccountDid={activeAccountDid} 1353 + setActiveAccountDid={setActiveAccountDid} 1310 1354 /> 1311 1355 {IS_WEBFooterSticky && post.id === activePost.id && ( 1312 1356 <View style={styles.stickyFooterWeb}>{footer}</View> ··· 1403 1447 onSelectVideo, 1404 1448 onError, 1405 1449 onPublish, 1450 + activeAccountDid, 1451 + setActiveAccountDid, 1406 1452 }: { 1407 1453 post: PostDraft 1408 1454 dispatch: (action: ComposerAction) => void ··· 1418 1464 onSelectVideo: (postId: string, asset: ImagePickerAsset) => void 1419 1465 onError: (error: string) => void 1420 1466 onPublish: (richtext: RichText) => void 1467 + activeAccountDid: string 1468 + setActiveAccountDid: (did: string) => void 1421 1469 }) { 1422 - const {currentAccount} = useSession() 1423 - const currentDid = currentAccount!.did 1470 + const {currentAccount, accounts} = useSession() 1424 1471 const {t: l} = useLingui() 1425 - const {data: currentProfile} = useProfileQuery({did: currentDid}) 1472 + const {data: currentProfile} = useProfileQuery({did: activeAccountDid}) 1426 1473 const richtext = post.richtext 1427 1474 const isTextOnly = !post.embed.link && !post.embed.quote && !post.embed.media 1428 1475 const forceMinHeight = IS_WEB && isTextOnly && isActive ··· 1432 1479 : l`Add another post` 1433 1480 : l`Anything but skeet` 1434 1481 const discardPromptControl = Prompt.usePromptControl() 1482 + const signOutPromptControl = Prompt.usePromptControl() 1435 1483 1436 1484 const enableSquareButtons = useEnableSquareButtons() 1437 1485 ··· 1490 1538 [post.id, onSelectVideo, onImageAdd, l], 1491 1539 ) 1492 1540 1541 + const {data} = useProfilesQuery({ 1542 + handles: accounts.map(acc => acc.did), 1543 + }) 1544 + const profiles = data?.profiles 1545 + 1546 + const allAccounts = accounts 1547 + .map(account => ({ 1548 + account, 1549 + profile: profiles?.find(p => p.did === account.did), 1550 + })) 1551 + 1493 1552 useHideKeyboardOnBackground() 1494 1553 1495 1554 return ( ··· 1502 1561 isTextOnly && isLastPost && IS_NATIVE && a.flex_grow, 1503 1562 ]}> 1504 1563 <View style={[a.flex_row, IS_NATIVE && a.flex_1]}> 1505 - <UserAvatar 1506 - avatar={currentProfile?.avatar} 1507 - size={42} 1508 - type={currentProfile?.associated?.labeler ? 'labeler' : 'user'} 1509 - style={[a.mt_xs]} 1510 - /> 1564 + <Menu.Root> 1565 + <Menu.Trigger label={l`Switch accounts`}> 1566 + {({props}) => ( 1567 + <Button 1568 + label={props.accessibilityLabel} 1569 + {...props} 1570 + style={[ 1571 + a.transition_color, 1572 + enableSquareButtons ? a.rounded_sm : a.rounded_full, 1573 + a.self_start, 1574 + ]}> 1575 + <UserAvatar 1576 + avatar={currentProfile?.avatar} 1577 + size={42} 1578 + type={ 1579 + currentProfile?.associated?.labeler ? 'labeler' : 'user' 1580 + } 1581 + style={[a.mt_xs]} 1582 + /> 1583 + </Button> 1584 + )} 1585 + </Menu.Trigger> 1586 + { 1587 + <SwitchMenuItems 1588 + accounts={allAccounts} 1589 + signOutPromptControl={signOutPromptControl} 1590 + showExtraButtons={false} 1591 + onSelectAccount={account => setActiveAccountDid(account.did)} 1592 + /> 1593 + } 1594 + </Menu.Root> 1511 1595 <TextInput 1512 1596 ref={textInputRef} 1513 1597 style={[a.pt_xs]} ··· 1632 1716 canSaveDraft: boolean 1633 1717 textLength: number 1634 1718 topBarAnimatedStyle: StyleProp<ViewStyle> 1635 - children?: React.ReactNode 1719 + children?: ReactNode 1636 1720 }) { 1637 1721 const t = useTheme() 1638 1722 const {t: l} = useLingui() ··· 2738 2822 children, 2739 2823 }: { 2740 2824 style: StyleProp<ViewStyle> 2741 - children: React.ReactNode 2825 + children: ReactNode 2742 2826 }) { 2743 2827 if (IS_WEB) return children 2744 2828 return (
+29 -12
src/view/shell/desktop/LeftNav.tsx
··· 1 - import {type JSX, useCallback, useMemo, useState} from 'react' 1 + import {type JSX, type MouseEvent, useCallback, useMemo, useState} from 'react' 2 2 import {StyleSheet, View} from 'react-native' 3 3 import {type AppBskyActorDefs} from '@atproto/api' 4 4 import {msg, plural} from '@lingui/core/macro' ··· 222 222 ) 223 223 } 224 224 225 - function SwitchMenuItems({ 225 + export function SwitchMenuItems({ 226 226 accounts, 227 227 signOutPromptControl, 228 + showExtraButtons, 229 + onSelectAccount, 228 230 }: { 229 231 accounts: 230 232 | { ··· 233 235 }[] 234 236 | undefined 235 237 signOutPromptControl: DialogControlProps 238 + showExtraButtons?: boolean 239 + onSelectAccount?: (account: SessionAccount) => void 236 240 }) { 237 241 const {_} = useLingui() 238 242 const {setShowLoggedOut} = useLoggedOutViewControls() 239 243 const closeEverything = useCloseAllActiveElements() 244 + 245 + showExtraButtons = showExtraButtons ?? true 240 246 241 247 const onAddAnotherAccount = () => { 242 248 setShowLoggedOut(true) ··· 256 262 key={other.account.did} 257 263 account={other.account} 258 264 profile={other.profile} 265 + onSelectAccount={onSelectAccount} 259 266 /> 260 267 ))} 261 268 </Menu.Group> 262 269 <Menu.Divider /> 263 270 </> 264 271 )} 265 - <SwitcherMenuProfileLink /> 272 + {showExtraButtons ? <SwitcherMenuProfileLink /> : undefined} 266 273 <Menu.Item 267 274 label={_(msg`Add another account`)} 268 275 onPress={onAddAnotherAccount}> ··· 271 278 <Trans>Add another account</Trans> 272 279 </Menu.ItemText> 273 280 </Menu.Item> 274 - <Menu.Item label={_(msg`Sign out`)} onPress={signOutPromptControl.open}> 275 - <Menu.ItemIcon icon={LeaveIcon} /> 276 - <Menu.ItemText> 277 - <Trans>Sign out</Trans> 278 - </Menu.ItemText> 279 - </Menu.Item> 281 + {showExtraButtons ? ( 282 + <Menu.Item label={_(msg`Sign out`)} onPress={signOutPromptControl.open}> 283 + <Menu.ItemIcon icon={LeaveIcon} /> 284 + <Menu.ItemText> 285 + <Trans>Sign out</Trans> 286 + </Menu.ItemText> 287 + </Menu.Item> 288 + ) : undefined} 280 289 </Menu.Outer> 281 290 ) 282 291 } ··· 301 310 currentAccount?.handle 302 311 : isTab(currentRouteInfo.name, pathName) 303 312 const onProfilePress = useCallback( 304 - (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => { 313 + (e: MouseEvent<HTMLAnchorElement>) => { 305 314 if (e.ctrlKey || e.metaKey || e.altKey) { 306 315 return 307 316 } ··· 334 343 function SwitchMenuItem({ 335 344 account, 336 345 profile, 346 + onSelectAccount, 337 347 }: { 338 348 account: SessionAccount 339 349 profile: AppBskyActorDefs.ProfileViewDetailed | undefined 350 + onSelectAccount?: (account: SessionAccount) => void 340 351 }) { 341 352 const {_} = useLingui() 342 353 const {onPressSwitchAccount, pendingDid} = useAccountSwitcher() ··· 353 364 '@', 354 365 )}`, 355 366 )} 356 - onPress={() => void onPressSwitchAccount(account, 'SwitchAccount')}> 367 + onPress={() => { 368 + if (onSelectAccount) { 369 + onSelectAccount(account) 370 + } else { 371 + void onPressSwitchAccount(account, 'SwitchAccount') 372 + } 373 + }}> 357 374 <View> 358 375 <UserAvatar 359 376 avatar={profile?.avatar} ··· 403 420 : isTab(currentRouteInfo.name, pathName) 404 421 const navigation = useNavigation<NavigationProp>() 405 422 const onPressWrapped = useCallback( 406 - (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => { 423 + (e: MouseEvent<HTMLAnchorElement>) => { 407 424 if (e.ctrlKey || e.metaKey || e.altKey) { 408 425 return 409 426 }